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.wipe=Erase Flash
menu.sdk=Espressif FW menu.sdk=Espressif FW
menu.ssl=SSL Support menu.ssl=SSL Support
menu.waveform=Waveform Flavour
############################################################## ##############################################################
generic.name=Generic ESP8266 Module 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.all.build.sslflags=
generic.menu.ssl.basic=Basic SSL ciphers (lower ROM use) generic.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
generic.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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=dtr (aka nodemcu)
generic.menu.ResetMethod.nodemcu.upload.resetmethod=--before default_reset --after hard_reset generic.menu.ResetMethod.nodemcu.upload.resetmethod=--before default_reset --after hard_reset
generic.menu.ResetMethod.ck=no dtr (aka ck) 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.all.build.sslflags=
esp8285.menu.ssl.basic=Basic SSL ciphers (lower ROM use) esp8285.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
esp8285.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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=dtr (aka nodemcu)
esp8285.menu.ResetMethod.nodemcu.upload.resetmethod=--before default_reset --after hard_reset esp8285.menu.ResetMethod.nodemcu.upload.resetmethod=--before default_reset --after hard_reset
esp8285.menu.ResetMethod.ck=no dtr (aka ck) 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.all.build.sslflags=
gen4iod.menu.ssl.basic=Basic SSL ciphers (lower ROM use) gen4iod.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
gen4iod.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before default_reset --after hard_reset
gen4iod.menu.FlashMode.dout=DOUT (compatible) gen4iod.menu.FlashMode.dout=DOUT (compatible)
gen4iod.menu.FlashMode.dout.build.flash_mode=dout 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.all.build.sslflags=
huzzah.menu.ssl.basic=Basic SSL ciphers (lower ROM use) huzzah.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
huzzah.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before default_reset --after hard_reset
huzzah.build.flash_mode=qio huzzah.build.flash_mode=qio
huzzah.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
wifi_slot.menu.ssl.basic=Basic SSL ciphers (lower ROM use) wifi_slot.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
wifi_slot.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before default_reset --after hard_reset
wifi_slot.menu.FlashFreq.40=40MHz wifi_slot.menu.FlashFreq.40=40MHz
wifi_slot.menu.FlashFreq.40.build.flash_freq=40 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.all.build.sslflags=
arduino-esp8266.menu.ssl.basic=Basic SSL ciphers (lower ROM use) arduino-esp8266.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
arduino-esp8266.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before no_reset --after soft_reset
arduino-esp8266.build.flash_mode=qio arduino-esp8266.build.flash_mode=qio
arduino-esp8266.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
espmxdevkit.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espmxdevkit.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
espmxdevkit.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before default_reset --after hard_reset
espmxdevkit.build.flash_mode=dout espmxdevkit.build.flash_mode=dout
espmxdevkit.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
oak.menu.ssl.basic=Basic SSL ciphers (lower ROM use) oak.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
oak.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before no_reset --after soft_reset
oak.build.flash_mode=dio oak.build.flash_mode=dio
oak.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
espduino.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espduino.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
espduino.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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_mode=dio
espduino.build.flash_flags=-DFLASHMODE_DIO espduino.build.flash_flags=-DFLASHMODE_DIO
espduino.build.flash_freq=40 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.all.build.sslflags=
espectro.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espectro.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
espectro.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before default_reset --after hard_reset
espectro.build.flash_mode=dio espectro.build.flash_mode=dio
espectro.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
espino.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espino.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
espino.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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=dtr (aka nodemcu)
espino.menu.ResetMethod.nodemcu.upload.resetmethod=--before default_reset --after hard_reset espino.menu.ResetMethod.nodemcu.upload.resetmethod=--before default_reset --after hard_reset
espino.menu.ResetMethod.ck=no dtr (aka ck) 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.all.build.sslflags=
espresso_lite_v1.menu.ssl.basic=Basic SSL ciphers (lower ROM use) 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.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_mode=dio
espresso_lite_v1.build.flash_flags=-DFLASHMODE_DIO espresso_lite_v1.build.flash_flags=-DFLASHMODE_DIO
espresso_lite_v1.build.flash_freq=40 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.all.build.sslflags=
espresso_lite_v2.menu.ssl.basic=Basic SSL ciphers (lower ROM use) 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.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_mode=dio
espresso_lite_v2.build.flash_flags=-DFLASHMODE_DIO espresso_lite_v2.build.flash_flags=-DFLASHMODE_DIO
espresso_lite_v2.build.flash_freq=40 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.all.build.sslflags=
sonoff.menu.ssl.basic=Basic SSL ciphers (lower ROM use) sonoff.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
sonoff.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before no_reset --after soft_reset
sonoff.build.flash_mode=dout sonoff.build.flash_mode=dout
sonoff.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
inventone.menu.ssl.basic=Basic SSL ciphers (lower ROM use) inventone.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
inventone.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before default_reset --after hard_reset
inventone.build.flash_mode=dio inventone.build.flash_mode=dio
inventone.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
d1_mini.menu.ssl.basic=Basic SSL ciphers (lower ROM use) d1_mini.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
d1_mini.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before default_reset --after hard_reset
d1_mini.build.flash_mode=dio d1_mini.build.flash_mode=dio
d1_mini.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
d1_mini_lite.menu.ssl.basic=Basic SSL ciphers (lower ROM use) 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.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.upload.resetmethod=--before default_reset --after hard_reset
d1_mini_lite.build.flash_mode=dout d1_mini_lite.build.flash_mode=dout
d1_mini_lite.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
d1_mini_pro.menu.ssl.basic=Basic SSL ciphers (lower ROM use) 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.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.upload.resetmethod=--before default_reset --after hard_reset
d1_mini_pro.build.flash_mode=dio d1_mini_pro.build.flash_mode=dio
d1_mini_pro.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
d1.menu.ssl.basic=Basic SSL ciphers (lower ROM use) d1.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
d1.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before default_reset --after hard_reset
d1.build.flash_mode=dio d1.build.flash_mode=dio
d1.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
nodemcu.menu.ssl.basic=Basic SSL ciphers (lower ROM use) nodemcu.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
nodemcu.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before default_reset --after hard_reset
nodemcu.build.flash_mode=qio nodemcu.build.flash_mode=qio
nodemcu.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
nodemcuv2.menu.ssl.basic=Basic SSL ciphers (lower ROM use) nodemcuv2.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
nodemcuv2.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before default_reset --after hard_reset
nodemcuv2.build.flash_mode=dio nodemcuv2.build.flash_mode=dio
nodemcuv2.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
modwifi.menu.ssl.basic=Basic SSL ciphers (lower ROM use) modwifi.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
modwifi.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before no_reset --after soft_reset
modwifi.build.flash_mode=qio modwifi.build.flash_mode=qio
modwifi.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
phoenix_v1.menu.ssl.basic=Basic SSL ciphers (lower ROM use) phoenix_v1.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
phoenix_v1.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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_mode=dio
phoenix_v1.build.flash_flags=-DFLASHMODE_DIO phoenix_v1.build.flash_flags=-DFLASHMODE_DIO
phoenix_v1.build.flash_freq=40 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.all.build.sslflags=
phoenix_v2.menu.ssl.basic=Basic SSL ciphers (lower ROM use) phoenix_v2.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
phoenix_v2.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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_mode=dio
phoenix_v2.build.flash_flags=-DFLASHMODE_DIO phoenix_v2.build.flash_flags=-DFLASHMODE_DIO
phoenix_v2.build.flash_freq=40 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.all.build.sslflags=
eduinowifi.menu.ssl.basic=Basic SSL ciphers (lower ROM use) eduinowifi.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
eduinowifi.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before default_reset --after hard_reset
eduinowifi.build.flash_mode=dio eduinowifi.build.flash_mode=dio
eduinowifi.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
wiolink.menu.ssl.basic=Basic SSL ciphers (lower ROM use) wiolink.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
wiolink.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before default_reset --after hard_reset
wiolink.build.flash_mode=qio wiolink.build.flash_mode=qio
wiolink.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
blynk.menu.ssl.basic=Basic SSL ciphers (lower ROM use) blynk.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
blynk.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before default_reset --after hard_reset
blynk.build.flash_mode=qio blynk.build.flash_mode=qio
blynk.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
thing.menu.ssl.basic=Basic SSL ciphers (lower ROM use) thing.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
thing.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before no_reset --after soft_reset
thing.build.flash_mode=qio thing.build.flash_mode=qio
thing.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
thingdev.menu.ssl.basic=Basic SSL ciphers (lower ROM use) thingdev.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
thingdev.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before default_reset --after hard_reset
thingdev.build.flash_mode=dio thingdev.build.flash_mode=dio
thingdev.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
esp210.menu.ssl.basic=Basic SSL ciphers (lower ROM use) esp210.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
esp210.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before no_reset --after soft_reset
esp210.build.flash_mode=qio esp210.build.flash_mode=qio
esp210.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
espinotee.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espinotee.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
espinotee.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before default_reset --after hard_reset
espinotee.build.flash_mode=qio espinotee.build.flash_mode=qio
espinotee.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
wifiduino.menu.ssl.basic=Basic SSL ciphers (lower ROM use) wifiduino.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
wifiduino.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before default_reset --after hard_reset
wifiduino.build.flash_mode=dio wifiduino.build.flash_mode=dio
wifiduino.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
wifinfo.menu.ssl.basic=Basic SSL ciphers (lower ROM use) wifinfo.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
wifinfo.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before default_reset --after hard_reset
wifinfo.build.flash_mode=qio wifinfo.build.flash_mode=qio
wifinfo.build.flash_flags=-DFLASHMODE_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.all.build.sslflags=
cw01.menu.ssl.basic=Basic SSL ciphers (lower ROM use) cw01.menu.ssl.basic=Basic SSL ciphers (lower ROM use)
cw01.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC 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.upload.resetmethod=--before default_reset --after hard_reset
cw01.menu.CrystalFreq.26=26 MHz cw01.menu.CrystalFreq.26=26 MHz
cw01.menu.CrystalFreq.40=40 MHz cw01.menu.CrystalFreq.40=40 MHz

View File

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

View File

@ -25,15 +25,18 @@
#include "core_esp8266_waveform.h" #include "core_esp8266_waveform.h"
#include "user_interface.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) { static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t duration) {
if (_pin > 16) { if (_pin > 16) {
return; 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); pinMode(_pin, OUTPUT);
high = std::max(high, (uint32_t)microsecondsToClockCycles(25)); // new 20KHz maximum tone frequency, 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 = microsecondsToClockCycles(duration * 1000UL);
duration += high + low - 1; duration += high + low - 1;
duration -= duration % (high + low); duration -= duration % (high + low);
if (startWaveformClockCycles(_pin, high, low, duration)) { startWaveformClockCycles(_pin, high, low, duration);
_toneMap |= 1 << _pin;
}
} }
@ -86,6 +87,5 @@ void noTone(uint8_t _pin) {
return; return;
} }
stopWaveform(_pin); stopWaveform(_pin);
_toneMap &= ~(1 << _pin);
digitalWrite(_pin, 0); digitalWrite(_pin, 0);
} }

View File

@ -32,7 +32,7 @@
String::String(const char *cstr) { String::String(const char *cstr) {
init(); init();
if (cstr) if (cstr)
copy(cstr, strlen(cstr)); copy(cstr, strlen_P(cstr));
} }
String::String(const String &value) { String::String(const String &value) {
@ -55,14 +55,6 @@ String::String(StringSumHelper &&rval) noexcept {
move(rval); 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) { String::String(unsigned char value, unsigned char base) {
init(); init();
char buf[1 + 8 * sizeof(unsigned char)]; char buf[1 + 8 * sizeof(unsigned char)];
@ -118,20 +110,10 @@ String::String(double value, unsigned char decimalPlaces) {
*this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf); *this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf);
} }
String::~String() {
invalidate();
}
/*********************************************/ /*********************************************/
/* Memory Management */ /* Memory Management */
/*********************************************/ /*********************************************/
inline void String::init(void) {
setSSO(true);
setLen(0);
wbuffer()[0] = 0;
}
void String::invalidate(void) { void String::invalidate(void) {
if (!isSSO() && wbuffer()) if (!isSSO() && wbuffer())
free(wbuffer()); free(wbuffer());
@ -157,24 +139,22 @@ unsigned char String::changeBuffer(unsigned int maxStrLen) {
uint16_t oldLen = len(); uint16_t oldLen = len();
setSSO(true); setSSO(true);
setLen(oldLen); setLen(oldLen);
return 1;
} else { // if bufptr && !isSSO() } else { // if bufptr && !isSSO()
// Using bufptr, need to shrink into sso.buff // Using bufptr, need to shrink into sso.buff
char temp[sizeof(sso.buff)]; const char *temp = buffer();
memcpy(temp, buffer(), maxStrLen);
free(wbuffer());
uint16_t oldLen = len(); uint16_t oldLen = len();
setSSO(true); setSSO(true);
setLen(oldLen); setLen(oldLen);
memcpy(wbuffer(), temp, maxStrLen); memcpy(wbuffer(), temp, maxStrLen);
return 1; free((void *)temp);
} }
return 1;
} }
// Fallthrough to normal allocator // Fallthrough to normal allocator
size_t newSize = (maxStrLen + 16) & (~0xf); size_t newSize = (maxStrLen + 16) & (~0xf);
// Make sure we can fit newsize in the buffer // Make sure we can fit newsize in the buffer
if (newSize > CAPACITY_MAX) { if (newSize > CAPACITY_MAX) {
return false; return 0;
} }
uint16_t oldLen = len(); uint16_t oldLen = len();
char *newbuffer = (char *)realloc(isSSO() ? nullptr : wbuffer(), newSize); char *newbuffer = (char *)realloc(isSSO() ? nullptr : wbuffer(), newSize);
@ -184,8 +164,7 @@ unsigned char String::changeBuffer(unsigned int maxStrLen) {
// Copy the SSO buffer into allocated space // Copy the SSO buffer into allocated space
memmove_P(newbuffer, sso.buff, sizeof(sso.buff)); memmove_P(newbuffer, sso.buff, sizeof(sso.buff));
} }
if (newSize > oldSize) if (newSize > oldSize) {
{
memset(newbuffer + oldSize, 0, newSize - oldSize); memset(newbuffer + oldSize, 0, newSize - oldSize);
} }
setSSO(false); setSSO(false);
@ -230,12 +209,10 @@ void String::move(String &rhs) noexcept {
String &String::operator =(const String &rhs) { String &String::operator =(const String &rhs) {
if (this == &rhs) if (this == &rhs)
return *this; return *this;
if (rhs.buffer()) if (rhs.buffer())
copy(rhs.buffer(), rhs.len()); copy(rhs.buffer(), rhs.len());
else else
invalidate(); invalidate();
return *this; return *this;
} }
@ -245,26 +222,19 @@ String & String::operator =(String &&rval) noexcept {
return *this; 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) if (cstr)
copy(cstr, strlen(cstr)); copy(cstr, strlen(cstr));
else else
invalidate(); invalidate();
return *this; return *this;
} }
String & String::operator = (const __FlashStringHelper *pstr) String &String::operator =(const __FlashStringHelper *pstr) {
{ if (pstr)
if (pstr) copy(pstr, strlen_P((PGM_P)pstr)); copy(pstr, strlen_P((PGM_P)pstr));
else invalidate(); else
invalidate();
return *this; return *this;
} }
@ -285,7 +255,7 @@ unsigned char String::concat(const String &s) {
return 0; return 0;
memmove_P(wbuffer() + len(), buffer(), len()); memmove_P(wbuffer() + len(), buffer(), len());
setLen(newlen); setLen(newlen);
wbuffer()[len()] = 0; wbuffer()[newlen] = 0;
return 1; return 1;
} else { } else {
return concat(s.buffer(), s.len()); return concat(s.buffer(), s.len());
@ -313,22 +283,17 @@ unsigned char String::concat(const char *cstr) {
} }
unsigned char String::concat(char c) { unsigned char String::concat(char c) {
char buf[2]; return concat(&c, 1);
buf[0] = c;
buf[1] = 0;
return concat(buf, 1);
} }
unsigned char String::concat(unsigned char num) { unsigned char String::concat(unsigned char num) {
char buf[1 + 3 * sizeof(unsigned char)]; char buf[1 + 3 * sizeof(unsigned char)];
sprintf(buf, "%d", num); return concat(buf, sprintf(buf, "%d", num));
return concat(buf, strlen(buf));
} }
unsigned char String::concat(int num) { unsigned char String::concat(int num) {
char buf[2 + 3 * sizeof(int)]; char buf[2 + 3 * sizeof(int)];
sprintf(buf, "%d", num); return concat(buf, sprintf(buf, "%d", num));
return concat(buf, strlen(buf));
} }
unsigned char String::concat(unsigned int 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) { unsigned char String::concat(long num) {
char buf[2 + 3 * sizeof(long)]; char buf[2 + 3 * sizeof(long)];
sprintf(buf, "%ld", num); return concat(buf, sprintf(buf, "%ld", num));
return concat(buf, strlen(buf));
} }
unsigned char String::concat(unsigned long num) { unsigned char String::concat(unsigned long num) {
@ -362,11 +326,14 @@ unsigned char String::concat(double num) {
} }
unsigned char String::concat(const __FlashStringHelper *str) { unsigned char String::concat(const __FlashStringHelper *str) {
if (!str) return 0; if (!str)
return 0;
int length = strlen_P((PGM_P)str); int length = strlen_P((PGM_P)str);
if (length == 0) return 1; if (length == 0)
return 1;
unsigned int newlen = len() + length; unsigned int newlen = len() + length;
if (!reserve(newlen)) return 0; if (!reserve(newlen))
return 0;
memcpy_P(wbuffer() + len(), (PGM_P)str, length + 1); memcpy_P(wbuffer() + len(), (PGM_P)str, length + 1);
setLen(newlen); setLen(newlen);
return 1; return 1;
@ -446,8 +413,7 @@ StringSumHelper & operator +(const StringSumHelper &lhs, double num) {
return a; return a;
} }
StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHelper *rhs) StringSumHelper &operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs) {
{
StringSumHelper &a = const_cast<StringSumHelper &>(lhs); StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
if (!a.concat(rhs)) if (!a.concat(rhs))
a.invalidate(); a.invalidate();
@ -521,7 +487,7 @@ unsigned char String::equalsConstantTime(const String &s2) const {
//at this point lengths are the same //at this point lengths are the same
if (len() == 0) if (len() == 0)
return 1; 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 *p1 = buffer();
const char *p2 = s2.buffer(); const char *p2 = s2.buffer();
unsigned int equalchars = 0; unsigned int equalchars = 0;
@ -562,10 +528,6 @@ unsigned char String::endsWith(const String &s2) const {
/* Character Access */ /* Character Access */
/*********************************************/ /*********************************************/
char String::charAt(unsigned int loc) const {
return operator[](loc);
}
void String::setCharAt(unsigned int loc, char c) { void String::setCharAt(unsigned int loc, char c) {
if (loc < len()) if (loc < len())
wbuffer()[loc] = c; wbuffer()[loc] = c;
@ -604,10 +566,6 @@ void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int ind
/* Search */ /* Search */
/*********************************************/ /*********************************************/
int String::indexOf(char c) const {
return indexOf(c, 0);
}
int String::indexOf(char ch, unsigned int fromIndex) const { int String::indexOf(char ch, unsigned int fromIndex) const {
if (fromIndex >= len()) if (fromIndex >= len())
return -1; return -1;
@ -617,18 +575,6 @@ int String::indexOf(char ch, unsigned int fromIndex) const {
return temp - buffer(); 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 { int String::indexOf(const char *s2, unsigned int fromIndex) const {
if (fromIndex >= len()) if (fromIndex >= len())
return -1; return -1;
@ -638,28 +584,25 @@ int String::indexOf(const char *s2, unsigned int fromIndex) const {
return found - buffer(); return found - buffer();
} }
int String::indexOf(const String &s2) const {
return indexOf(s2, 0);
}
int String::indexOf(const String &s2, unsigned int fromIndex) const { int String::indexOf(const String &s2, unsigned int fromIndex) const {
return indexOf(s2.c_str(), fromIndex); return indexOf(s2.c_str(), fromIndex);
} }
int String::lastIndexOf(char theChar) const { int String::lastIndexOf(char ch) const {
return lastIndexOf(theChar, len() - 1); return lastIndexOf(ch, len() - 1);
} }
int String::lastIndexOf(char ch, unsigned int fromIndex) const { int String::lastIndexOf(char ch, unsigned int fromIndex) const {
if (fromIndex >= len()) if (fromIndex >= len())
return -1; return -1;
char tempchar = buffer()[fromIndex + 1]; char *writeTo = wbuffer();
wbuffer()[fromIndex + 1] = '\0'; char tempchar = writeTo[fromIndex + 1]; // save the replaced character
char* temp = strrchr(wbuffer(), ch); writeTo[fromIndex + 1] = '\0';
wbuffer()[fromIndex + 1] = tempchar; char *temp = strrchr(writeTo, ch);
writeTo[fromIndex + 1] = tempchar; // restore character
if (temp == NULL) if (temp == NULL)
return -1; return -1;
return temp - buffer(); return temp - writeTo;
} }
int String::lastIndexOf(const String &s2) const { int String::lastIndexOf(const String &s2) const {
@ -672,11 +615,11 @@ int String::lastIndexOf(const String &s2, unsigned int fromIndex) const {
if (fromIndex >= len()) if (fromIndex >= len())
fromIndex = len() - 1; fromIndex = len() - 1;
int found = -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()); p = strstr(p, s2.buffer());
if (!p) if (!p)
break; break;
if ((unsigned int) (p - wbuffer()) <= fromIndex) if ((unsigned int)(p - buffer()) <= fromIndex)
found = p - buffer(); found = p - buffer();
} }
return found; return found;
@ -693,10 +636,11 @@ String String::substring(unsigned int left, unsigned int right) const {
return out; return out;
if (right > len()) if (right > len())
right = len(); right = len();
char temp = buffer()[right]; // save the replaced character char *writeTo = wbuffer();
wbuffer()[right] = '\0'; char tempchar = writeTo[right]; // save the replaced character
out = wbuffer() + left; // pointer arithmetic writeTo[right] = '\0';
wbuffer()[right] = temp; //restore character out = writeTo + left; // pointer arithmetic
writeTo[right] = tempchar; // restore character
return out; return out;
} }
@ -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) { void String::remove(unsigned int index, unsigned int count) {
if (index >= len()) { if (index >= len()) {
return; return;
@ -828,11 +765,10 @@ long String::toInt(void) const {
float String::toFloat(void) const { float String::toFloat(void) const {
if (buffer()) if (buffer())
return atof(buffer()); return atof(buffer());
return 0; return 0.0F;
} }
double String::toDouble(void) const double String::toDouble(void) const {
{
if (buffer()) if (buffer())
return atof(buffer()); return atof(buffer());
return 0.0; return 0.0;

View File

@ -53,7 +53,7 @@ class String {
// if the initial value is null or invalid, or if memory allocation // 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 // fails, the string will be marked as invalid (i.e. "if (s)" will
// be false). // be false).
String() { String() __attribute__((always_inline)) { // See init()
init(); init();
} }
String(const char *cstr); String(const char *cstr);
@ -61,7 +61,12 @@ class String {
String(const __FlashStringHelper *str); String(const __FlashStringHelper *str);
String(String &&rval) noexcept; String(String &&rval) noexcept;
String(StringSumHelper &&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(unsigned char, unsigned char base = 10);
explicit String(int, unsigned char base = 10); explicit String(int, unsigned char base = 10);
explicit String(unsigned int, unsigned char base = 10); explicit String(unsigned int, unsigned char base = 10);
@ -69,24 +74,22 @@ class String {
explicit String(unsigned long, unsigned char base = 10); explicit String(unsigned long, unsigned char base = 10);
explicit String(float, unsigned char decimalPlaces = 2); explicit String(float, unsigned char decimalPlaces = 2);
explicit String(double, unsigned char decimalPlaces = 2); explicit String(double, unsigned char decimalPlaces = 2);
~String(void); ~String() {
invalidate();
}
// memory management // memory management
// return true on success, false on failure (in which case, the string // return true on success, false on failure (in which case, the string
// is left unchanged). reserve(0), if successful, will validate an // is left unchanged). reserve(0), if successful, will validate an
// invalid string (i.e., "if (s)" will be true afterwards) // invalid string (i.e., "if (s)" will be true afterwards)
unsigned char reserve(unsigned int size); unsigned char reserve(unsigned int size);
inline unsigned int length(void) const { unsigned int length(void) const {
if(buffer()) { return buffer() ? len() : 0;
return len();
} else {
return 0;
} }
} void clear(void) {
inline void clear(void) {
setLen(0); setLen(0);
} }
inline bool isEmpty(void) const { bool isEmpty(void) const {
return length() == 0; return length() == 0;
} }
@ -97,7 +100,9 @@ class String {
String &operator =(const char *cstr); String &operator =(const char *cstr);
String &operator =(const __FlashStringHelper *str); String &operator =(const __FlashStringHelper *str);
String &operator =(String &&rval) noexcept; String &operator =(String &&rval) noexcept;
String & operator =(StringSumHelper &&rval) noexcept; String &operator =(StringSumHelper &&rval) noexcept {
return operator =((String &&)rval);
}
// concatenate (works w/ built-in types) // concatenate (works w/ built-in types)
@ -121,47 +126,47 @@ class String {
// will be left unchanged (but this isn't signalled in any way) // will be left unchanged (but this isn't signalled in any way)
String &operator +=(const String &rhs) { String &operator +=(const String &rhs) {
concat(rhs); concat(rhs);
return (*this); return *this;
} }
String &operator +=(const char *cstr) { String &operator +=(const char *cstr) {
concat(cstr); concat(cstr);
return (*this); return *this;
} }
String &operator +=(char c) { String &operator +=(char c) {
concat(c); concat(c);
return (*this); return *this;
} }
String &operator +=(unsigned char num) { String &operator +=(unsigned char num) {
concat(num); concat(num);
return (*this); return *this;
} }
String &operator +=(int num) { String &operator +=(int num) {
concat(num); concat(num);
return (*this); return *this;
} }
String &operator +=(unsigned int num) { String &operator +=(unsigned int num) {
concat(num); concat(num);
return (*this); return *this;
} }
String &operator +=(long num) { String &operator +=(long num) {
concat(num); concat(num);
return (*this); return *this;
} }
String &operator +=(unsigned long num) { String &operator +=(unsigned long num) {
concat(num); concat(num);
return (*this); return *this;
} }
String &operator +=(float num) { String &operator +=(float num) {
concat(num); concat(num);
return (*this); return *this;
} }
String &operator +=(double num) { String &operator +=(double num) {
concat(num); concat(num);
return (*this); return *this;
} }
String &operator +=(const __FlashStringHelper *str) { String &operator +=(const __FlashStringHelper *str) {
concat(str); concat(str);
return (*this); return *this;
} }
friend StringSumHelper &operator +(const StringSumHelper &lhs, const String &rhs); friend StringSumHelper &operator +(const StringSumHelper &lhs, const String &rhs);
@ -218,7 +223,9 @@ class String {
} }
// character access // character access
char charAt(unsigned int index) const; char charAt(unsigned int index) const {
return operator [](index);
}
void setCharAt(unsigned int index, char c); void setCharAt(unsigned int index, char c);
char operator [](unsigned int index) const; char operator [](unsigned int index) const;
char &operator [](unsigned int index); char &operator [](unsigned int index);
@ -233,14 +240,12 @@ class String {
const char *end() const { return c_str() + length(); } const char *end() const { return c_str() + length(); }
// search // search
int indexOf(char ch) const; int indexOf(char ch, unsigned int fromIndex = 0) const;
int indexOf(char ch, unsigned int fromIndex) const; int indexOf(const char *str, unsigned int fromIndex = 0) const;
int indexOf(const char *str) const; int indexOf(const __FlashStringHelper *str, unsigned int fromIndex = 0) const {
int indexOf(const char *str, unsigned int fromIndex) const; return indexOf((const char*)str, fromIndex);
int indexOf(const __FlashStringHelper *str) const; }
int indexOf(const __FlashStringHelper *str, unsigned int fromIndex) const; int indexOf(const String &str, unsigned int fromIndex = 0) const;
int indexOf(const String &str) const;
int indexOf(const String &str, unsigned int fromIndex) const;
int lastIndexOf(char ch) const; int lastIndexOf(char ch) const;
int lastIndexOf(char ch, unsigned int fromIndex) const; int lastIndexOf(char ch, unsigned int fromIndex) const;
int lastIndexOf(const String &str) const; int lastIndexOf(const String &str) const;
@ -248,7 +253,6 @@ class String {
String substring(unsigned int beginIndex) const { String substring(unsigned int beginIndex) const {
return substring(beginIndex, len()); return substring(beginIndex, len());
} }
;
String substring(unsigned int beginIndex, unsigned int endIndex) const; String substring(unsigned int beginIndex, unsigned int endIndex) const;
// modification // modification
@ -269,8 +273,9 @@ class String {
void replace(const __FlashStringHelper *find, const __FlashStringHelper *replace) { void replace(const __FlashStringHelper *find, const __FlashStringHelper *replace) {
this->replace(String(find), String(replace)); this->replace(String(find), String(replace));
} }
void remove(unsigned int index); // Pass the biggest integer if the count is not specified.
void remove(unsigned int index, unsigned int count); // 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 toLowerCase(void);
void toUpperCase(void); void toUpperCase(void);
void trim(void); void trim(void);
@ -292,7 +297,7 @@ class String {
struct _sso { struct _sso {
char buff[SSOSIZE]; char buff[SSOSIZE];
unsigned char len : 7; // Ensure only one byte is allocated by GCC for the bitfields unsigned char len : 7; // Ensure only one byte is allocated by GCC for the bitfields
unsigned char isSSO : 1; unsigned char isHeap : 1;
} __attribute__((packed)); // Ensure that GCC doesn't expand the flag byte to a 32-bit word for alignment issues } __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 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 { union {
@ -300,19 +305,41 @@ class String {
struct _sso sso; struct _sso sso;
}; };
// Accessor functions // Accessor functions
inline bool isSSO() const { return sso.isSSO; } bool isSSO() const { return !sso.isHeap; }
inline unsigned int len() const { return isSSO() ? sso.len : ptr.len; } 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 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; } void setSSO(bool set) { sso.isHeap = !set; }
inline void setLen(int len) { if (isSSO()) sso.len = len; else ptr.len = len; } void setLen(int len) {
inline void setCapacity(int cap) { if (!isSSO()) ptr.cap = cap; } if (isSSO()) {
inline void setBuffer(char *buff) { if (!isSSO()) ptr.buff = buff; } 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 // Buffer accessor functions
inline const char *buffer() const { return (const char *)(isSSO() ? sso.buff : ptr.buff); } const char *buffer() const { return wbuffer(); }
inline char *wbuffer() const { return isSSO() ? const_cast<char *>(sso.buff) : ptr.buff; } // Writable version of buffer char *wbuffer() const { return isSSO() ? const_cast<char *>(sso.buff) : ptr.buff; } // Writable version of buffer
protected: 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); void invalidate(void);
unsigned char changeBuffer(unsigned int maxStrLen); unsigned char changeBuffer(unsigned int maxStrLen);
@ -354,6 +381,9 @@ class StringSumHelper: public String {
StringSumHelper(double num) : StringSumHelper(double num) :
String(num) { String(num) {
} }
StringSumHelper(const __FlashStringHelper *s) :
String(s) {
}
}; };
extern const String emptyString; 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 @@
/* // Wrapper to include both versions of the waveform generator
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
#ifdef WAVEFORM_LOCKED_PHASE
#include "core_esp8266_waveform_phase.h"
#else
#include "core_esp8266_waveform_pwm.h"
#endif #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) { 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(pin < 16){
if(val) GPOS = (1 << pin); if(val) GPOS = (1 << pin);
else GPOC = (1 << pin); else GPOC = (1 << pin);

View File

@ -26,27 +26,18 @@
extern "C" { extern "C" {
static uint32_t analogMap = 0;
static int32_t analogScale = 255; // Match upstream default, breaking change from 2.x.x 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; 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) { extern void __analogWriteFreq(uint32_t freq) {
if (freq < 100) { if (freq < 100) {
analogFreq = 100; analogFreq = 100;
} else if (freq > 40000) { } else if (freq > 60000) {
analogFreq = 40000; analogFreq = 60000;
} else { } else {
analogFreq = freq; analogFreq = freq;
} }
@ -63,23 +54,69 @@ extern void __analogWrite(uint8_t pin, int val) {
val = analogScale; val = analogScale;
} }
if (analogMap & 1UL << pin) {
// Per the Arduino docs at https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/ // 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). // 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) // 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 high = (analogPeriod * val) / analogScale;
uint32_t low = analogPeriod - high; uint32_t low = analogPeriod - high;
pinMode(pin, OUTPUT); // 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)
if (low == 0) { int phaseReference = __builtin_ffs(analogMap) - 1;
digitalWrite(pin, HIGH); if (startWaveformClockCycles(pin, high, low, 0, phaseReference, 0, true)) {
} else if (high == 0) {
digitalWrite(pin, LOW);
} else {
if (startWaveformClockCycles(pin, high, low, 0)) {
analogMap |= (1 << pin); 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 {
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;
}
} }
extern void analogWrite(uint8_t pin, int val) __attribute__((weak, alias("__analogWrite"))); extern void analogWrite(uint8_t pin, int val) __attribute__((weak, alias("__analogWrite")));

View File

@ -725,7 +725,7 @@ int HTTPClient::writeToStream(Stream * stream)
int ret = 0; int ret = 0;
if(_transferEncoding == HTTPC_TE_IDENTITY) { if(_transferEncoding == HTTPC_TE_IDENTITY) {
if(len > 0) { if(len > 0 || len == -1) {
ret = writeToStreamDataBlock(stream, len); ret = writeToStreamDataBlock(stream, len);
// have we an error? // have we an error?
@ -1185,6 +1185,12 @@ int HTTPClient::writeToStreamDataBlock(Stream * stream, int size)
readBytes = buff_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 // read data
int bytesRead = _client->readBytes(buff, readBytes); int bytesRead = _client->readBytes(buff, readBytes);
if (!bytesRead) if (!bytesRead)

View File

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

View File

@ -347,14 +347,14 @@ void ESP8266WebServerTemplate<ServerType>::_uploadWriteByte(uint8_t b){
} }
template <typename ServerType> template <typename ServerType>
uint8_t ESP8266WebServerTemplate<ServerType>::_uploadReadByte(ClientType& client){ int ESP8266WebServerTemplate<ServerType>::_uploadReadByte(ClientType& client){
int res = client.read(); int res = client.read();
if(res == -1){ if(res == -1){
while(!client.available() && client.connected()) while(!client.available() && client.connected())
yield(); yield();
res = client.read(); res = client.read();
} }
return (uint8_t)res; return res;
} }
@ -444,44 +444,33 @@ bool ESP8266WebServerTemplate<ServerType>::_parseForm(ClientType& client, const
_currentHandler->upload(*this, _currentUri, *_currentUpload); _currentHandler->upload(*this, _currentUri, *_currentUpload);
_currentUpload->status = UPLOAD_FILE_WRITE; _currentUpload->status = UPLOAD_FILE_WRITE;
int bLen = boundary.length(); int fastBoundaryLen = 4 /* \r\n-- */ + boundary.length() + 1 /* \0 */;
uint8_t boundBuf[2 + bLen + 1]; // "--" + boundary + null terminator char fastBoundary[ fastBoundaryLen ];
boundBuf[2 + bLen] = '\0'; snprintf(fastBoundary, fastBoundaryLen, "\r\n--%s", boundary.c_str());
uint8_t argByte; int boundaryPtr = 0;
bool first = true; while ( true ) {
while (1) { int ret = _uploadReadByte(client);
//attempt to fill up boundary buffer with length of boundary string if (ret < 0) {
int i; // Unexpected, we should have had data available per above
for (i = 0; i < 2 + bLen; i++) { return _parseFormUploadAborted();
if (!client.connected()) return _parseFormUploadAborted(); }
argByte = _uploadReadByte(client); char in = (char) ret;
if (argByte == '\r') 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; break;
boundBuf[i] = argByte;
} }
if ((strncmp((const char*)boundBuf, "--", 2) == 0) && (strcmp((const char*)(boundBuf + 2), boundary.c_str()) == 0)) } else {
break; //found the boundary, done parsing this file // The char doesn't match what we want, so dump whatever matches we had, the read in char, and reset ptr to start
if (first) first = false; //only add newline characters after the first line for (int i = 0; i < boundaryPtr; i++) {
else { _uploadWriteByte( fastBoundary[ i ] );
_uploadWriteByte('\r');
_uploadWriteByte('\n');
} }
// current line does not contain boundary, upload all bytes in boundary buffer _uploadWriteByte( in );
for (int j = 0; j < i; j++) boundaryPtr = 0;
_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);
} }
} }
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)) if (_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, *_currentUpload); _currentHandler->upload(*this, _currentUri, *_currentUpload);

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) uint8_t Servo::attach(int pin, uint16_t minUs, uint16_t maxUs, int value)
{ {
if (!_attached) { if (!_attached) {
#ifdef WAVEFORM_LOCKED_PHASE
pinMode(pin, OUTPUT);
digitalWrite(pin, LOW);
#else
digitalWrite(pin, LOW); digitalWrite(pin, LOW);
pinMode(pin, OUTPUT); pinMode(pin, OUTPUT);
#endif
_pin = pin; _pin = pin;
_attached = true; _attached = true;
} }
@ -90,7 +95,11 @@ void Servo::detach()
{ {
if (_attached) { if (_attached) {
_servoMap &= ~(1 << _pin); _servoMap &= ~(1 << _pin);
#ifdef WAVEFORM_LOCKED_PHASE
startWaveform(_pin, 0, REFRESH_INTERVAL, 1); 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. delay(REFRESH_INTERVAL / 1000); // long enough to complete active period under all circumstances.
stopWaveform(_pin); stopWaveform(_pin);
_attached = false; _attached = false;
@ -115,7 +124,14 @@ void Servo::writeMicroseconds(int value)
_valueUs = value; _valueUs = value;
if (_attached) { if (_attached) {
_servoMap &= ~(1 << _pin); _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); _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.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.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.cmd=xtensa-lx106-elf-gcc
compiler.S.flags=-c -g -x assembler-with-cpp -MMD -mlongcalls 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.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.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 compiler.as.cmd=xtensa-lx106-elf-as

View File

@ -66,6 +66,8 @@ function build_sketches()
local sketches=$(find $srcpath -name *.ino | sort) local sketches=$(find $srcpath -name *.ino | sort)
print_size_info >size.log print_size_info >size.log
export ARDUINO_IDE_PATH=$arduino export ARDUINO_IDE_PATH=$arduino
local pwm_phase=""
[ $(( $build_rem % 2 )) -eq 0 ] && pwm_phase="--waveform_phase"
local testcnt=0 local testcnt=0
for sketch in $sketches; do for sketch in $sketches; do
testcnt=$(( ($testcnt + 1) % $build_mod )) testcnt=$(( ($testcnt + 1) % $build_mod ))
@ -112,8 +114,8 @@ function build_sketches()
export MSYS2_ARG_CONV_EXC="*" export MSYS2_ARG_CONV_EXC="*"
export MSYS_NO_PATHCONV=1 export MSYS_NO_PATHCONV=1
fi fi
echo "$build_cmd $sketch" echo "$build_cmd $pwm_phase $sketch"
time ($build_cmd $sketch >build.log) time ($build_cmd $pwm_phase $sketch >build.log)
local result=$? local result=$?
if [ $result -ne 0 ]; then if [ $result -ne 0 ]; then
echo "Build failed ($1)" echo "Build failed ($1)"

View File

@ -1466,6 +1466,18 @@ def led (name, default, ledList):
])) ]))
return { name: led } 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 # sdk selection
@ -1517,6 +1529,7 @@ def all_boards ():
macros.update(led('led', led_default, range(0,led_max+1))) macros.update(led('led', led_default, range(0,led_max+1)))
macros.update(led('led216', 2, { 16 })) macros.update(led('led216', 2, { 16 }))
macros.update(sdk()) macros.update(sdk())
macros.update(waveform())
if boardfilteropt or excludeboards: if boardfilteropt or excludeboards:
print('#') print('#')
@ -1561,6 +1574,7 @@ def all_boards ():
print('menu.wipe=Erase Flash') print('menu.wipe=Erase Flash')
print('menu.sdk=Espressif FW') print('menu.sdk=Espressif FW')
print('menu.ssl=SSL Support') print('menu.ssl=SSL Support')
print('menu.waveform=Waveform Flavour')
print('') print('')
missingboards = [] missingboards = []
@ -1581,7 +1595,7 @@ def all_boards ():
print(id + optname + '=' + board['opts'][optname]) print(id + optname + '=' + board['opts'][optname])
# macros # 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: if 'macro' in board:
macrolist += board['macro'] macrolist += board['macro']
macrolist += [ 'lwip', 'debug_menu', 'flash_erase_menu' ] 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)) 'ResetMethod=nodemcu'.format(**vars(args))
if args.debug_port and args.debug_level: if args.debug_port and args.debug_level:
fqbn += 'dbg={debug_port},lvl={debug_level}'.format(**vars(args)) fqbn += 'dbg={debug_port},lvl={debug_level}'.format(**vars(args))
if args.waveform_phase:
fqbn += ',waveform=phase'
cmd += [fqbn] cmd += [fqbn]
cmd += ['-built-in-libraries', ide_path + '/libraries'] cmd += ['-built-in-libraries', ide_path + '/libraries']
cmd += ['-ide-version=10607'] cmd += ['-ide-version=10607']
@ -115,6 +117,8 @@ def parse_args():
type=int, choices=[40, 80]) type=int, choices=[40, 80])
parser.add_argument('--debug_port', help='Debug port', parser.add_argument('--debug_port', help='Debug port',
choices=['Serial', 'Serial1']) 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('--debug_level', help='Debug level')
parser.add_argument('--build_cache', help='Build directory to cache core.a', default='') parser.add_argument('--build_cache', help='Build directory to cache core.a', default='')
parser.add_argument('sketch_path', help='Sketch file path') 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: with open(out, "wb") as binfile:
binfile.write(raw) 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(): def main():
parser = argparse.ArgumentParser(description='Create a BIN file from eboot.elf and Arduino sketch.elf for upload by esptool.py') 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') 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('-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('-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('-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() 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 # Because the CRC includes both eboot and app, can only calculate it after the entire BIN generated
add_crc(args.out) add_crc(args.out)
if args.gzip:
gzip_bin(args.gzip, args.out)
return 0 return 0

View File

@ -45,10 +45,15 @@ Builder.match_splitext = scons_patched_match_splitext
env = DefaultEnvironment() env = DefaultEnvironment()
platform = env.PioPlatform() platform = env.PioPlatform()
board = env.BoardConfig()
gzip_fw = board.get("build.gzip_fw", False)
gzip_switch = []
FRAMEWORK_DIR = platform.get_package_dir("framework-arduinoespressif8266") FRAMEWORK_DIR = platform.get_package_dir("framework-arduinoespressif8266")
assert isdir(FRAMEWORK_DIR) assert isdir(FRAMEWORK_DIR)
if gzip_fw:
gzip_switch = ["--gzip", "PIO"]
env.Append( env.Append(
ASFLAGS=["-x", "assembler-with-cpp"], ASFLAGS=["-x", "assembler-with-cpp"],
@ -145,7 +150,7 @@ env.Append(
"--path", '"%s"' % join( "--path", '"%s"' % join(
platform.get_package_dir("toolchain-xtensa"), "bin"), platform.get_package_dir("toolchain-xtensa"), "bin"),
"--out", "$TARGET" "--out", "$TARGET"
]), "Building $TARGET"), ] + gzip_switch), "Building $TARGET"),
suffix=".bin" suffix=".bin"
) )
) )
@ -237,6 +242,13 @@ else:
LIBS=["lwip2-536-feat"] 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 # VTables
# #