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:
commit
84fdf15fdc
137
boards.txt
137
boards.txt
@ -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
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
@ -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
|
||||||
|
440
cores/esp8266/core_esp8266_waveform_phase.cpp
Normal file
440
cores/esp8266/core_esp8266_waveform_phase.cpp
Normal 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
|
93
cores/esp8266/core_esp8266_waveform_phase.h
Normal file
93
cores/esp8266/core_esp8266_waveform_phase.h
Normal 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
|
626
cores/esp8266/core_esp8266_waveform_pwm.cpp
Normal file
626
cores/esp8266/core_esp8266_waveform_pwm.cpp
Normal 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
|
87
cores/esp8266/core_esp8266_waveform_pwm.h
Normal file
87
cores/esp8266/core_esp8266_waveform_pwm.h
Normal 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
|
@ -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);
|
||||||
|
@ -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")));
|
||||||
|
@ -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)
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
@ -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
|
||||||
|
|
||||||
|
@ -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)"
|
||||||
|
@ -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' ]
|
||||||
|
@ -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')
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
#
|
#
|
||||||
|
Loading…
x
Reference in New Issue
Block a user