diff --git a/boards.txt b/boards.txt index afd4f3004..d32a47e85 100644 --- a/boards.txt +++ b/boards.txt @@ -23,6 +23,7 @@ menu.stacksmash=Stack Protection menu.wipe=Erase Flash menu.sdk=Espressif FW menu.ssl=SSL Support +menu.waveform=Waveform Flavour ############################################################## generic.name=Generic ESP8266 Module @@ -63,6 +64,10 @@ generic.menu.ssl.all=All SSL ciphers (most compatible) generic.menu.ssl.all.build.sslflags= generic.menu.ssl.basic=Basic SSL ciphers (lower ROM use) generic.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +generic.menu.waveform.pwm=Locked PWM +generic.menu.waveform.pwm.build.waveform= +generic.menu.waveform.phase=Locked Phase +generic.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE generic.menu.ResetMethod.nodemcu=dtr (aka nodemcu) generic.menu.ResetMethod.nodemcu.upload.resetmethod=--before default_reset --after hard_reset generic.menu.ResetMethod.ck=no dtr (aka ck) @@ -532,6 +537,10 @@ esp8285.menu.ssl.all=All SSL ciphers (most compatible) esp8285.menu.ssl.all.build.sslflags= esp8285.menu.ssl.basic=Basic SSL ciphers (lower ROM use) esp8285.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +esp8285.menu.waveform.pwm=Locked PWM +esp8285.menu.waveform.pwm.build.waveform= +esp8285.menu.waveform.phase=Locked Phase +esp8285.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE esp8285.menu.ResetMethod.nodemcu=dtr (aka nodemcu) esp8285.menu.ResetMethod.nodemcu.upload.resetmethod=--before default_reset --after hard_reset esp8285.menu.ResetMethod.ck=no dtr (aka ck) @@ -871,6 +880,10 @@ gen4iod.menu.ssl.all=All SSL ciphers (most compatible) gen4iod.menu.ssl.all.build.sslflags= gen4iod.menu.ssl.basic=Basic SSL ciphers (lower ROM use) gen4iod.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +gen4iod.menu.waveform.pwm=Locked PWM +gen4iod.menu.waveform.pwm.build.waveform= +gen4iod.menu.waveform.phase=Locked Phase +gen4iod.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE gen4iod.upload.resetmethod=--before default_reset --after hard_reset gen4iod.menu.FlashMode.dout=DOUT (compatible) gen4iod.menu.FlashMode.dout.build.flash_mode=dout @@ -1125,6 +1138,10 @@ huzzah.menu.ssl.all=All SSL ciphers (most compatible) huzzah.menu.ssl.all.build.sslflags= huzzah.menu.ssl.basic=Basic SSL ciphers (lower ROM use) huzzah.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +huzzah.menu.waveform.pwm=Locked PWM +huzzah.menu.waveform.pwm.build.waveform= +huzzah.menu.waveform.phase=Locked Phase +huzzah.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE huzzah.upload.resetmethod=--before default_reset --after hard_reset huzzah.build.flash_mode=qio huzzah.build.flash_flags=-DFLASHMODE_QIO @@ -1312,6 +1329,10 @@ wifi_slot.menu.ssl.all=All SSL ciphers (most compatible) wifi_slot.menu.ssl.all.build.sslflags= wifi_slot.menu.ssl.basic=Basic SSL ciphers (lower ROM use) wifi_slot.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +wifi_slot.menu.waveform.pwm=Locked PWM +wifi_slot.menu.waveform.pwm.build.waveform= +wifi_slot.menu.waveform.phase=Locked Phase +wifi_slot.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE wifi_slot.upload.resetmethod=--before default_reset --after hard_reset wifi_slot.menu.FlashFreq.40=40MHz wifi_slot.menu.FlashFreq.40.build.flash_freq=40 @@ -1625,6 +1646,10 @@ arduino-esp8266.menu.ssl.all=All SSL ciphers (most compatible) arduino-esp8266.menu.ssl.all.build.sslflags= arduino-esp8266.menu.ssl.basic=Basic SSL ciphers (lower ROM use) arduino-esp8266.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +arduino-esp8266.menu.waveform.pwm=Locked PWM +arduino-esp8266.menu.waveform.pwm.build.waveform= +arduino-esp8266.menu.waveform.phase=Locked Phase +arduino-esp8266.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE arduino-esp8266.upload.resetmethod=--before no_reset --after soft_reset arduino-esp8266.build.flash_mode=qio arduino-esp8266.build.flash_flags=-DFLASHMODE_QIO @@ -1813,6 +1838,10 @@ espmxdevkit.menu.ssl.all=All SSL ciphers (most compatible) espmxdevkit.menu.ssl.all.build.sslflags= espmxdevkit.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espmxdevkit.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +espmxdevkit.menu.waveform.pwm=Locked PWM +espmxdevkit.menu.waveform.pwm.build.waveform= +espmxdevkit.menu.waveform.phase=Locked Phase +espmxdevkit.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE espmxdevkit.upload.resetmethod=--before default_reset --after hard_reset espmxdevkit.build.flash_mode=dout espmxdevkit.build.flash_flags=-DFLASHMODE_DOUT @@ -2041,6 +2070,10 @@ oak.menu.ssl.all=All SSL ciphers (most compatible) oak.menu.ssl.all.build.sslflags= oak.menu.ssl.basic=Basic SSL ciphers (lower ROM use) oak.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +oak.menu.waveform.pwm=Locked PWM +oak.menu.waveform.pwm.build.waveform= +oak.menu.waveform.phase=Locked Phase +oak.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE oak.upload.resetmethod=--before no_reset --after soft_reset oak.build.flash_mode=dio oak.build.flash_flags=-DFLASHMODE_DIO @@ -2237,6 +2270,10 @@ espduino.menu.ssl.all=All SSL ciphers (most compatible) espduino.menu.ssl.all.build.sslflags= espduino.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espduino.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +espduino.menu.waveform.pwm=Locked PWM +espduino.menu.waveform.pwm.build.waveform= +espduino.menu.waveform.phase=Locked Phase +espduino.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE espduino.build.flash_mode=dio espduino.build.flash_flags=-DFLASHMODE_DIO espduino.build.flash_freq=40 @@ -2423,6 +2460,10 @@ espectro.menu.ssl.all=All SSL ciphers (most compatible) espectro.menu.ssl.all.build.sslflags= espectro.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espectro.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +espectro.menu.waveform.pwm=Locked PWM +espectro.menu.waveform.pwm.build.waveform= +espectro.menu.waveform.phase=Locked Phase +espectro.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE espectro.upload.resetmethod=--before default_reset --after hard_reset espectro.build.flash_mode=dio espectro.build.flash_flags=-DFLASHMODE_DIO @@ -2610,6 +2651,10 @@ espino.menu.ssl.all=All SSL ciphers (most compatible) espino.menu.ssl.all.build.sslflags= espino.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espino.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +espino.menu.waveform.pwm=Locked PWM +espino.menu.waveform.pwm.build.waveform= +espino.menu.waveform.phase=Locked Phase +espino.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE espino.menu.ResetMethod.nodemcu=dtr (aka nodemcu) espino.menu.ResetMethod.nodemcu.upload.resetmethod=--before default_reset --after hard_reset espino.menu.ResetMethod.ck=no dtr (aka ck) @@ -2800,6 +2845,10 @@ espresso_lite_v1.menu.ssl.all=All SSL ciphers (most compatible) espresso_lite_v1.menu.ssl.all.build.sslflags= espresso_lite_v1.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espresso_lite_v1.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +espresso_lite_v1.menu.waveform.pwm=Locked PWM +espresso_lite_v1.menu.waveform.pwm.build.waveform= +espresso_lite_v1.menu.waveform.phase=Locked Phase +espresso_lite_v1.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE espresso_lite_v1.build.flash_mode=dio espresso_lite_v1.build.flash_flags=-DFLASHMODE_DIO espresso_lite_v1.build.flash_freq=40 @@ -2990,6 +3039,10 @@ espresso_lite_v2.menu.ssl.all=All SSL ciphers (most compatible) espresso_lite_v2.menu.ssl.all.build.sslflags= espresso_lite_v2.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espresso_lite_v2.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +espresso_lite_v2.menu.waveform.pwm=Locked PWM +espresso_lite_v2.menu.waveform.pwm.build.waveform= +espresso_lite_v2.menu.waveform.phase=Locked Phase +espresso_lite_v2.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE espresso_lite_v2.build.flash_mode=dio espresso_lite_v2.build.flash_flags=-DFLASHMODE_DIO espresso_lite_v2.build.flash_freq=40 @@ -3190,6 +3243,10 @@ sonoff.menu.ssl.all=All SSL ciphers (most compatible) sonoff.menu.ssl.all.build.sslflags= sonoff.menu.ssl.basic=Basic SSL ciphers (lower ROM use) sonoff.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +sonoff.menu.waveform.pwm=Locked PWM +sonoff.menu.waveform.pwm.build.waveform= +sonoff.menu.waveform.phase=Locked Phase +sonoff.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE sonoff.upload.resetmethod=--before no_reset --after soft_reset sonoff.build.flash_mode=dout sonoff.build.flash_flags=-DFLASHMODE_DOUT @@ -3417,6 +3474,10 @@ inventone.menu.ssl.all=All SSL ciphers (most compatible) inventone.menu.ssl.all.build.sslflags= inventone.menu.ssl.basic=Basic SSL ciphers (lower ROM use) inventone.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +inventone.menu.waveform.pwm=Locked PWM +inventone.menu.waveform.pwm.build.waveform= +inventone.menu.waveform.phase=Locked Phase +inventone.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE inventone.upload.resetmethod=--before default_reset --after hard_reset inventone.build.flash_mode=dio inventone.build.flash_flags=-DFLASHMODE_DIO @@ -3604,6 +3665,10 @@ d1_mini.menu.ssl.all=All SSL ciphers (most compatible) d1_mini.menu.ssl.all.build.sslflags= d1_mini.menu.ssl.basic=Basic SSL ciphers (lower ROM use) d1_mini.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +d1_mini.menu.waveform.pwm=Locked PWM +d1_mini.menu.waveform.pwm.build.waveform= +d1_mini.menu.waveform.phase=Locked Phase +d1_mini.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE d1_mini.upload.resetmethod=--before default_reset --after hard_reset d1_mini.build.flash_mode=dio d1_mini.build.flash_flags=-DFLASHMODE_DIO @@ -3791,6 +3856,10 @@ d1_mini_lite.menu.ssl.all=All SSL ciphers (most compatible) d1_mini_lite.menu.ssl.all.build.sslflags= d1_mini_lite.menu.ssl.basic=Basic SSL ciphers (lower ROM use) d1_mini_lite.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +d1_mini_lite.menu.waveform.pwm=Locked PWM +d1_mini_lite.menu.waveform.pwm.build.waveform= +d1_mini_lite.menu.waveform.phase=Locked Phase +d1_mini_lite.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE d1_mini_lite.upload.resetmethod=--before default_reset --after hard_reset d1_mini_lite.build.flash_mode=dout d1_mini_lite.build.flash_flags=-DFLASHMODE_DOUT @@ -4018,6 +4087,10 @@ d1_mini_pro.menu.ssl.all=All SSL ciphers (most compatible) d1_mini_pro.menu.ssl.all.build.sslflags= d1_mini_pro.menu.ssl.basic=Basic SSL ciphers (lower ROM use) d1_mini_pro.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +d1_mini_pro.menu.waveform.pwm=Locked PWM +d1_mini_pro.menu.waveform.pwm.build.waveform= +d1_mini_pro.menu.waveform.phase=Locked Phase +d1_mini_pro.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE d1_mini_pro.upload.resetmethod=--before default_reset --after hard_reset d1_mini_pro.build.flash_mode=dio d1_mini_pro.build.flash_flags=-DFLASHMODE_DIO @@ -4188,6 +4261,10 @@ d1.menu.ssl.all=All SSL ciphers (most compatible) d1.menu.ssl.all.build.sslflags= d1.menu.ssl.basic=Basic SSL ciphers (lower ROM use) d1.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +d1.menu.waveform.pwm=Locked PWM +d1.menu.waveform.pwm.build.waveform= +d1.menu.waveform.phase=Locked Phase +d1.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE d1.upload.resetmethod=--before default_reset --after hard_reset d1.build.flash_mode=dio d1.build.flash_flags=-DFLASHMODE_DIO @@ -4375,6 +4452,10 @@ nodemcu.menu.ssl.all=All SSL ciphers (most compatible) nodemcu.menu.ssl.all.build.sslflags= nodemcu.menu.ssl.basic=Basic SSL ciphers (lower ROM use) nodemcu.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +nodemcu.menu.waveform.pwm=Locked PWM +nodemcu.menu.waveform.pwm.build.waveform= +nodemcu.menu.waveform.phase=Locked Phase +nodemcu.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE nodemcu.upload.resetmethod=--before default_reset --after hard_reset nodemcu.build.flash_mode=qio nodemcu.build.flash_flags=-DFLASHMODE_QIO @@ -4562,6 +4643,10 @@ nodemcuv2.menu.ssl.all=All SSL ciphers (most compatible) nodemcuv2.menu.ssl.all.build.sslflags= nodemcuv2.menu.ssl.basic=Basic SSL ciphers (lower ROM use) nodemcuv2.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +nodemcuv2.menu.waveform.pwm=Locked PWM +nodemcuv2.menu.waveform.pwm.build.waveform= +nodemcuv2.menu.waveform.phase=Locked Phase +nodemcuv2.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE nodemcuv2.upload.resetmethod=--before default_reset --after hard_reset nodemcuv2.build.flash_mode=dio nodemcuv2.build.flash_flags=-DFLASHMODE_DIO @@ -4753,6 +4838,10 @@ modwifi.menu.ssl.all=All SSL ciphers (most compatible) modwifi.menu.ssl.all.build.sslflags= modwifi.menu.ssl.basic=Basic SSL ciphers (lower ROM use) modwifi.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +modwifi.menu.waveform.pwm=Locked PWM +modwifi.menu.waveform.pwm.build.waveform= +modwifi.menu.waveform.phase=Locked Phase +modwifi.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE modwifi.upload.resetmethod=--before no_reset --after soft_reset modwifi.build.flash_mode=qio modwifi.build.flash_flags=-DFLASHMODE_QIO @@ -4960,6 +5049,10 @@ phoenix_v1.menu.ssl.all=All SSL ciphers (most compatible) phoenix_v1.menu.ssl.all.build.sslflags= phoenix_v1.menu.ssl.basic=Basic SSL ciphers (lower ROM use) phoenix_v1.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +phoenix_v1.menu.waveform.pwm=Locked PWM +phoenix_v1.menu.waveform.pwm.build.waveform= +phoenix_v1.menu.waveform.phase=Locked Phase +phoenix_v1.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE phoenix_v1.build.flash_mode=dio phoenix_v1.build.flash_flags=-DFLASHMODE_DIO phoenix_v1.build.flash_freq=40 @@ -5150,6 +5243,10 @@ phoenix_v2.menu.ssl.all=All SSL ciphers (most compatible) phoenix_v2.menu.ssl.all.build.sslflags= phoenix_v2.menu.ssl.basic=Basic SSL ciphers (lower ROM use) phoenix_v2.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +phoenix_v2.menu.waveform.pwm=Locked PWM +phoenix_v2.menu.waveform.pwm.build.waveform= +phoenix_v2.menu.waveform.phase=Locked Phase +phoenix_v2.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE phoenix_v2.build.flash_mode=dio phoenix_v2.build.flash_flags=-DFLASHMODE_DIO phoenix_v2.build.flash_freq=40 @@ -5340,6 +5437,10 @@ eduinowifi.menu.ssl.all=All SSL ciphers (most compatible) eduinowifi.menu.ssl.all.build.sslflags= eduinowifi.menu.ssl.basic=Basic SSL ciphers (lower ROM use) eduinowifi.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +eduinowifi.menu.waveform.pwm=Locked PWM +eduinowifi.menu.waveform.pwm.build.waveform= +eduinowifi.menu.waveform.phase=Locked Phase +eduinowifi.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE eduinowifi.upload.resetmethod=--before default_reset --after hard_reset eduinowifi.build.flash_mode=dio eduinowifi.build.flash_flags=-DFLASHMODE_DIO @@ -5527,6 +5628,10 @@ wiolink.menu.ssl.all=All SSL ciphers (most compatible) wiolink.menu.ssl.all.build.sslflags= wiolink.menu.ssl.basic=Basic SSL ciphers (lower ROM use) wiolink.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +wiolink.menu.waveform.pwm=Locked PWM +wiolink.menu.waveform.pwm.build.waveform= +wiolink.menu.waveform.phase=Locked Phase +wiolink.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE wiolink.upload.resetmethod=--before default_reset --after hard_reset wiolink.build.flash_mode=qio wiolink.build.flash_flags=-DFLASHMODE_QIO @@ -5714,6 +5819,10 @@ blynk.menu.ssl.all=All SSL ciphers (most compatible) blynk.menu.ssl.all.build.sslflags= blynk.menu.ssl.basic=Basic SSL ciphers (lower ROM use) blynk.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +blynk.menu.waveform.pwm=Locked PWM +blynk.menu.waveform.pwm.build.waveform= +blynk.menu.waveform.phase=Locked Phase +blynk.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE blynk.upload.resetmethod=--before default_reset --after hard_reset blynk.build.flash_mode=qio blynk.build.flash_flags=-DFLASHMODE_QIO @@ -5901,6 +6010,10 @@ thing.menu.ssl.all=All SSL ciphers (most compatible) thing.menu.ssl.all.build.sslflags= thing.menu.ssl.basic=Basic SSL ciphers (lower ROM use) thing.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +thing.menu.waveform.pwm=Locked PWM +thing.menu.waveform.pwm.build.waveform= +thing.menu.waveform.phase=Locked Phase +thing.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE thing.upload.resetmethod=--before no_reset --after soft_reset thing.build.flash_mode=qio thing.build.flash_flags=-DFLASHMODE_QIO @@ -6088,6 +6201,10 @@ thingdev.menu.ssl.all=All SSL ciphers (most compatible) thingdev.menu.ssl.all.build.sslflags= thingdev.menu.ssl.basic=Basic SSL ciphers (lower ROM use) thingdev.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +thingdev.menu.waveform.pwm=Locked PWM +thingdev.menu.waveform.pwm.build.waveform= +thingdev.menu.waveform.phase=Locked Phase +thingdev.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE thingdev.upload.resetmethod=--before default_reset --after hard_reset thingdev.build.flash_mode=dio thingdev.build.flash_flags=-DFLASHMODE_DIO @@ -6275,6 +6392,10 @@ esp210.menu.ssl.all=All SSL ciphers (most compatible) esp210.menu.ssl.all.build.sslflags= esp210.menu.ssl.basic=Basic SSL ciphers (lower ROM use) esp210.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +esp210.menu.waveform.pwm=Locked PWM +esp210.menu.waveform.pwm.build.waveform= +esp210.menu.waveform.phase=Locked Phase +esp210.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE esp210.upload.resetmethod=--before no_reset --after soft_reset esp210.build.flash_mode=qio esp210.build.flash_flags=-DFLASHMODE_QIO @@ -6462,6 +6583,10 @@ espinotee.menu.ssl.all=All SSL ciphers (most compatible) espinotee.menu.ssl.all.build.sslflags= espinotee.menu.ssl.basic=Basic SSL ciphers (lower ROM use) espinotee.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +espinotee.menu.waveform.pwm=Locked PWM +espinotee.menu.waveform.pwm.build.waveform= +espinotee.menu.waveform.phase=Locked Phase +espinotee.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE espinotee.upload.resetmethod=--before default_reset --after hard_reset espinotee.build.flash_mode=qio espinotee.build.flash_flags=-DFLASHMODE_QIO @@ -6649,6 +6774,10 @@ wifiduino.menu.ssl.all=All SSL ciphers (most compatible) wifiduino.menu.ssl.all.build.sslflags= wifiduino.menu.ssl.basic=Basic SSL ciphers (lower ROM use) wifiduino.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +wifiduino.menu.waveform.pwm=Locked PWM +wifiduino.menu.waveform.pwm.build.waveform= +wifiduino.menu.waveform.phase=Locked Phase +wifiduino.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE wifiduino.upload.resetmethod=--before default_reset --after hard_reset wifiduino.build.flash_mode=dio wifiduino.build.flash_flags=-DFLASHMODE_DIO @@ -6853,6 +6982,10 @@ wifinfo.menu.ssl.all=All SSL ciphers (most compatible) wifinfo.menu.ssl.all.build.sslflags= wifinfo.menu.ssl.basic=Basic SSL ciphers (lower ROM use) wifinfo.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +wifinfo.menu.waveform.pwm=Locked PWM +wifinfo.menu.waveform.pwm.build.waveform= +wifinfo.menu.waveform.phase=Locked Phase +wifinfo.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE wifinfo.upload.resetmethod=--before default_reset --after hard_reset wifinfo.build.flash_mode=qio wifinfo.build.flash_flags=-DFLASHMODE_QIO @@ -7087,6 +7220,10 @@ cw01.menu.ssl.all=All SSL ciphers (most compatible) cw01.menu.ssl.all.build.sslflags= cw01.menu.ssl.basic=Basic SSL ciphers (lower ROM use) cw01.menu.ssl.basic.build.sslflags=-DBEARSSL_SSL_BASIC +cw01.menu.waveform.pwm=Locked PWM +cw01.menu.waveform.pwm.build.waveform= +cw01.menu.waveform.phase=Locked Phase +cw01.menu.waveform.phase.build.waveform=-DWAVEFORM_LOCKED_PHASE cw01.upload.resetmethod=--before default_reset --after hard_reset cw01.menu.CrystalFreq.26=26 MHz cw01.menu.CrystalFreq.40=40 MHz diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp index 5ac887ce1..b1230ed67 100644 --- a/cores/esp8266/Schedule.cpp +++ b/cores/esp8266/Schedule.cpp @@ -102,6 +102,7 @@ bool schedule_function(const std::function& fn) return true; } +IRAM_ATTR // (not only) called from ISR bool schedule_recurrent_function_us(const std::function& fn, uint32_t repeat_us, const std::function& alarm) { diff --git a/cores/esp8266/Tone.cpp b/cores/esp8266/Tone.cpp index 064fdad5d..8deeb63d0 100644 --- a/cores/esp8266/Tone.cpp +++ b/cores/esp8266/Tone.cpp @@ -25,15 +25,18 @@ #include "core_esp8266_waveform.h" #include "user_interface.h" -// Which pins have a tone running on them? -static uint32_t _toneMap = 0; - - static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t duration) { if (_pin > 16) { return; } +#ifndef WAVEFORM_LOCKED_PHASE + // Stop any analogWrites (PWM) because they are a different generator + _stopPWM(_pin); +#endif + // If there's another Tone or startWaveform on this pin + // it will be changed on-the-fly (no need to stop it) + pinMode(_pin, OUTPUT); high = std::max(high, (uint32_t)microsecondsToClockCycles(25)); // new 20KHz maximum tone frequency, @@ -42,9 +45,7 @@ static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t durat duration = microsecondsToClockCycles(duration * 1000UL); duration += high + low - 1; duration -= duration % (high + low); - if (startWaveformClockCycles(_pin, high, low, duration)) { - _toneMap |= 1 << _pin; - } + startWaveformClockCycles(_pin, high, low, duration); } @@ -86,6 +87,5 @@ void noTone(uint8_t _pin) { return; } stopWaveform(_pin); - _toneMap &= ~(1 << _pin); digitalWrite(_pin, 0); } diff --git a/cores/esp8266/WString.cpp b/cores/esp8266/WString.cpp index 3dda69bda..07ab99b2c 100644 --- a/cores/esp8266/WString.cpp +++ b/cores/esp8266/WString.cpp @@ -32,7 +32,7 @@ String::String(const char *cstr) { init(); if (cstr) - copy(cstr, strlen(cstr)); + copy(cstr, strlen_P(cstr)); } String::String(const String &value) { @@ -55,14 +55,6 @@ String::String(StringSumHelper &&rval) noexcept { move(rval); } -String::String(char c) { - init(); - char buf[2]; - buf[0] = c; - buf[1] = 0; - *this = buf; -} - String::String(unsigned char value, unsigned char base) { init(); char buf[1 + 8 * sizeof(unsigned char)]; @@ -91,7 +83,7 @@ String::String(unsigned int value, unsigned char base) { String::String(long value, unsigned char base) { init(); char buf[2 + 8 * sizeof(long)]; - if (base==10) { + if (base == 10) { sprintf(buf, "%ld", value); } else { ltoa(value, buf, base); @@ -118,31 +110,21 @@ String::String(double value, unsigned char decimalPlaces) { *this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf); } -String::~String() { - invalidate(); -} - /*********************************************/ /* Memory Management */ /*********************************************/ -inline void String::init(void) { - setSSO(true); - setLen(0); - wbuffer()[0] = 0; -} - void String::invalidate(void) { - if(!isSSO() && wbuffer()) + if (!isSSO() && wbuffer()) free(wbuffer()); init(); } unsigned char String::reserve(unsigned int size) { - if(buffer() && capacity() >= size) + if (buffer() && capacity() >= size) return 1; - if(changeBuffer(size)) { - if(len() == 0) + if (changeBuffer(size)) { + if (len() == 0) wbuffer()[0] = 0; return 1; } @@ -157,35 +139,32 @@ unsigned char String::changeBuffer(unsigned int maxStrLen) { uint16_t oldLen = len(); setSSO(true); setLen(oldLen); - return 1; } else { // if bufptr && !isSSO() // Using bufptr, need to shrink into sso.buff - char temp[sizeof(sso.buff)]; - memcpy(temp, buffer(), maxStrLen); - free(wbuffer()); + const char *temp = buffer(); uint16_t oldLen = len(); setSSO(true); setLen(oldLen); memcpy(wbuffer(), temp, maxStrLen); - return 1; + free((void *)temp); } + return 1; } // Fallthrough to normal allocator size_t newSize = (maxStrLen + 16) & (~0xf); // Make sure we can fit newsize in the buffer if (newSize > CAPACITY_MAX) { - return false; + return 0; } uint16_t oldLen = len(); - char *newbuffer = (char *) realloc(isSSO() ? nullptr : wbuffer(), newSize); + char *newbuffer = (char *)realloc(isSSO() ? nullptr : wbuffer(), newSize); if (newbuffer) { size_t oldSize = capacity() + 1; // include NULL. if (isSSO()) { // Copy the SSO buffer into allocated space memmove_P(newbuffer, sso.buff, sizeof(sso.buff)); } - if (newSize > oldSize) - { + if (newSize > oldSize) { memset(newbuffer + oldSize, 0, newSize - oldSize); } setSSO(false); @@ -201,7 +180,7 @@ unsigned char String::changeBuffer(unsigned int maxStrLen) { /* Copy and Move */ /*********************************************/ -String & String::copy(const char *cstr, unsigned int length) { +String &String::copy(const char *cstr, unsigned int length) { if (!reserve(length)) { invalidate(); return *this; @@ -211,7 +190,7 @@ String & String::copy(const char *cstr, unsigned int length) { return *this; } -String & String::copy(const __FlashStringHelper *pstr, unsigned int length) { +String &String::copy(const __FlashStringHelper *pstr, unsigned int length) { if (!reserve(length)) { invalidate(); return *this; @@ -227,44 +206,35 @@ void String::move(String &rhs) noexcept { rhs.init(); } -String & String::operator =(const String &rhs) { +String &String::operator =(const String &rhs) { if (this == &rhs) return *this; - if (rhs.buffer()) copy(rhs.buffer(), rhs.len()); else invalidate(); - return *this; } -String & String::operator =(String &&rval) noexcept { +String &String::operator =(String &&rval) noexcept { if (this != &rval) move(rval); return *this; } -String & String::operator =(StringSumHelper &&rval) noexcept { - if (this != &rval) - move(rval); - return *this; -} - -String & String::operator =(const char *cstr) { +String &String::operator =(const char *cstr) { if (cstr) copy(cstr, strlen(cstr)); else invalidate(); - return *this; } -String & String::operator = (const __FlashStringHelper *pstr) -{ - if (pstr) copy(pstr, strlen_P((PGM_P)pstr)); - else invalidate(); - +String &String::operator =(const __FlashStringHelper *pstr) { + if (pstr) + copy(pstr, strlen_P((PGM_P)pstr)); + else + invalidate(); return *this; } @@ -285,7 +255,7 @@ unsigned char String::concat(const String &s) { return 0; memmove_P(wbuffer() + len(), buffer(), len()); setLen(newlen); - wbuffer()[len()] = 0; + wbuffer()[newlen] = 0; return 1; } else { return concat(s.buffer(), s.len()); @@ -313,22 +283,17 @@ unsigned char String::concat(const char *cstr) { } unsigned char String::concat(char c) { - char buf[2]; - buf[0] = c; - buf[1] = 0; - return concat(buf, 1); + return concat(&c, 1); } unsigned char String::concat(unsigned char num) { char buf[1 + 3 * sizeof(unsigned char)]; - sprintf(buf, "%d", num); - return concat(buf, strlen(buf)); + return concat(buf, sprintf(buf, "%d", num)); } unsigned char String::concat(int num) { char buf[2 + 3 * sizeof(int)]; - sprintf(buf, "%d", num); - return concat(buf, strlen(buf)); + return concat(buf, sprintf(buf, "%d", num)); } unsigned char String::concat(unsigned int num) { @@ -339,8 +304,7 @@ unsigned char String::concat(unsigned int num) { unsigned char String::concat(long num) { char buf[2 + 3 * sizeof(long)]; - sprintf(buf, "%ld", num); - return concat(buf, strlen(buf)); + return concat(buf, sprintf(buf, "%ld", num)); } unsigned char String::concat(unsigned long num) { @@ -351,22 +315,25 @@ unsigned char String::concat(unsigned long num) { unsigned char String::concat(float num) { char buf[20]; - char* string = dtostrf(num, 4, 2, buf); + char *string = dtostrf(num, 4, 2, buf); return concat(string, strlen(string)); } unsigned char String::concat(double num) { char buf[20]; - char* string = dtostrf(num, 4, 2, buf); + char *string = dtostrf(num, 4, 2, buf); return concat(string, strlen(string)); } -unsigned char String::concat(const __FlashStringHelper * str) { - if (!str) return 0; +unsigned char String::concat(const __FlashStringHelper *str) { + if (!str) + return 0; int length = strlen_P((PGM_P)str); - if (length == 0) return 1; + if (length == 0) + return 1; unsigned int newlen = len() + length; - if (!reserve(newlen)) return 0; + if (!reserve(newlen)) + return 0; memcpy_P(wbuffer() + len(), (PGM_P)str, length + 1); setLen(newlen); return 1; @@ -376,79 +343,78 @@ unsigned char String::concat(const __FlashStringHelper * str) { /* Concatenate */ /*********************************************/ -StringSumHelper & operator +(const StringSumHelper &lhs, const String &rhs) { - StringSumHelper &a = const_cast(lhs); +StringSumHelper &operator +(const StringSumHelper &lhs, const String &rhs) { + StringSumHelper &a = const_cast(lhs); if (!a.concat(rhs.buffer(), rhs.len())) a.invalidate(); return a; } -StringSumHelper & operator +(const StringSumHelper &lhs, const char *cstr) { - StringSumHelper &a = const_cast(lhs); +StringSumHelper &operator +(const StringSumHelper &lhs, const char *cstr) { + StringSumHelper &a = const_cast(lhs); if (!cstr || !a.concat(cstr, strlen(cstr))) a.invalidate(); return a; } -StringSumHelper & operator +(const StringSumHelper &lhs, char c) { - StringSumHelper &a = const_cast(lhs); +StringSumHelper &operator +(const StringSumHelper &lhs, char c) { + StringSumHelper &a = const_cast(lhs); if (!a.concat(c)) a.invalidate(); return a; } -StringSumHelper & operator +(const StringSumHelper &lhs, unsigned char num) { - StringSumHelper &a = const_cast(lhs); +StringSumHelper &operator +(const StringSumHelper &lhs, unsigned char num) { + StringSumHelper &a = const_cast(lhs); if (!a.concat(num)) a.invalidate(); return a; } -StringSumHelper & operator +(const StringSumHelper &lhs, int num) { - StringSumHelper &a = const_cast(lhs); +StringSumHelper &operator +(const StringSumHelper &lhs, int num) { + StringSumHelper &a = const_cast(lhs); if (!a.concat(num)) a.invalidate(); return a; } -StringSumHelper & operator +(const StringSumHelper &lhs, unsigned int num) { - StringSumHelper &a = const_cast(lhs); +StringSumHelper &operator +(const StringSumHelper &lhs, unsigned int num) { + StringSumHelper &a = const_cast(lhs); if (!a.concat(num)) a.invalidate(); return a; } -StringSumHelper & operator +(const StringSumHelper &lhs, long num) { - StringSumHelper &a = const_cast(lhs); +StringSumHelper &operator +(const StringSumHelper &lhs, long num) { + StringSumHelper &a = const_cast(lhs); if (!a.concat(num)) a.invalidate(); return a; } -StringSumHelper & operator +(const StringSumHelper &lhs, unsigned long num) { - StringSumHelper &a = const_cast(lhs); +StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long num) { + StringSumHelper &a = const_cast(lhs); if (!a.concat(num)) a.invalidate(); return a; } -StringSumHelper & operator +(const StringSumHelper &lhs, float num) { - StringSumHelper &a = const_cast(lhs); +StringSumHelper &operator +(const StringSumHelper &lhs, float num) { + StringSumHelper &a = const_cast(lhs); if (!a.concat(num)) a.invalidate(); return a; } -StringSumHelper & operator +(const StringSumHelper &lhs, double num) { - StringSumHelper &a = const_cast(lhs); +StringSumHelper &operator +(const StringSumHelper &lhs, double num) { + StringSumHelper &a = const_cast(lhs); if (!a.concat(num)) a.invalidate(); return a; } -StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHelper *rhs) -{ - StringSumHelper &a = const_cast(lhs); +StringSumHelper &operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs) { + StringSumHelper &a = const_cast(lhs); if (!a.concat(rhs)) a.invalidate(); return a; @@ -459,11 +425,11 @@ StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHel /*********************************************/ int String::compareTo(const String &s) const { - if(!buffer() || !s.buffer()) { - if(s.buffer() && s.len() > 0) - return 0 - *(unsigned char *) s.buffer(); - if(buffer() && len() > 0) - return *(unsigned char *) buffer(); + if (!buffer() || !s.buffer()) { + if (s.buffer() && s.len() > 0) + return 0 - *(unsigned char *)s.buffer(); + if (buffer() && len() > 0) + return *(unsigned char *)buffer(); return 0; } return strcmp(buffer(), s.buffer()); @@ -521,7 +487,7 @@ unsigned char String::equalsConstantTime(const String &s2) const { //at this point lengths are the same if (len() == 0) return 1; - //at this point lenghts are the same and non-zero + //at this point lengths are the same and non-zero const char *p1 = buffer(); const char *p2 = s2.buffer(); unsigned int equalchars = 0; @@ -541,19 +507,19 @@ unsigned char String::equalsConstantTime(const String &s2) const { } unsigned char String::startsWith(const String &s2) const { - if(len() < s2.len()) + if (len() < s2.len()) return 0; return startsWith(s2, 0); } unsigned char String::startsWith(const String &s2, unsigned int offset) const { - if(offset > (unsigned)(len() - s2.len()) || !buffer() || !s2.buffer()) + if (offset > (unsigned)(len() - s2.len()) || !buffer() || !s2.buffer()) return 0; return strncmp(&buffer()[offset], s2.buffer(), s2.len()) == 0; } unsigned char String::endsWith(const String &s2) const { - if(len() < s2.len() || !buffer() || !s2.buffer()) + if (len() < s2.len() || !buffer() || !s2.buffer()) return 0; return strcmp(&buffer()[len() - s2.len()], s2.buffer()) == 0; } @@ -562,16 +528,12 @@ unsigned char String::endsWith(const String &s2) const { /* Character Access */ /*********************************************/ -char String::charAt(unsigned int loc) const { - return operator[](loc); -} - void String::setCharAt(unsigned int loc, char c) { if (loc < len()) wbuffer()[loc] = c; } -char & String::operator[](unsigned int index) { +char &String::operator[](unsigned int index) { static char dummy_writable_char; if (index >= len() || !buffer()) { dummy_writable_char = 0; @@ -596,7 +558,7 @@ void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int ind unsigned int n = bufsize - 1; if (n > len() - index) n = len() - index; - strncpy((char *) buf, buffer() + index, n); + strncpy((char *)buf, buffer() + index, n); buf[n] = 0; } @@ -604,31 +566,15 @@ void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int ind /* Search */ /*********************************************/ -int String::indexOf(char c) const { - return indexOf(c, 0); -} - int String::indexOf(char ch, unsigned int fromIndex) const { if (fromIndex >= len()) return -1; - const char* temp = strchr(buffer() + fromIndex, ch); + const char *temp = strchr(buffer() + fromIndex, ch); if (temp == NULL) return -1; return temp - buffer(); } -int String::indexOf(const __FlashStringHelper *s2) const { - return indexOf(s2, 0); -} - -int String::indexOf(const __FlashStringHelper *s2, unsigned int fromIndex) const { - return indexOf((const char*) s2, fromIndex); -} - -int String::indexOf(const char *s2) const { - return indexOf(s2, 0); -} - int String::indexOf(const char *s2, unsigned int fromIndex) const { if (fromIndex >= len()) return -1; @@ -638,28 +584,25 @@ int String::indexOf(const char *s2, unsigned int fromIndex) const { return found - buffer(); } -int String::indexOf(const String &s2) const { - return indexOf(s2, 0); -} - int String::indexOf(const String &s2, unsigned int fromIndex) const { return indexOf(s2.c_str(), fromIndex); } -int String::lastIndexOf(char theChar) const { - return lastIndexOf(theChar, len() - 1); +int String::lastIndexOf(char ch) const { + return lastIndexOf(ch, len() - 1); } int String::lastIndexOf(char ch, unsigned int fromIndex) const { if (fromIndex >= len()) return -1; - char tempchar = buffer()[fromIndex + 1]; - wbuffer()[fromIndex + 1] = '\0'; - char* temp = strrchr(wbuffer(), ch); - wbuffer()[fromIndex + 1] = tempchar; + char *writeTo = wbuffer(); + char tempchar = writeTo[fromIndex + 1]; // save the replaced character + writeTo[fromIndex + 1] = '\0'; + char *temp = strrchr(writeTo, ch); + writeTo[fromIndex + 1] = tempchar; // restore character if (temp == NULL) return -1; - return temp - buffer(); + return temp - writeTo; } int String::lastIndexOf(const String &s2) const { @@ -672,11 +615,11 @@ int String::lastIndexOf(const String &s2, unsigned int fromIndex) const { if (fromIndex >= len()) fromIndex = len() - 1; int found = -1; - for (char *p = wbuffer(); p <= wbuffer() + fromIndex; p++) { + for (const char *p = buffer(); p <= buffer() + fromIndex; p++) { p = strstr(p, s2.buffer()); if (!p) break; - if ((unsigned int) (p - wbuffer()) <= fromIndex) + if ((unsigned int)(p - buffer()) <= fromIndex) found = p - buffer(); } return found; @@ -693,10 +636,11 @@ String String::substring(unsigned int left, unsigned int right) const { return out; if (right > len()) right = len(); - char temp = buffer()[right]; // save the replaced character - wbuffer()[right] = '\0'; - out = wbuffer() + left; // pointer arithmetic - wbuffer()[right] = temp; //restore character + char *writeTo = wbuffer(); + char tempchar = writeTo[right]; // save the replaced character + writeTo[right] = '\0'; + out = writeTo + left; // pointer arithmetic + writeTo[right] = tempchar; // restore character return out; } @@ -713,7 +657,7 @@ void String::replace(char find, char replace) { } } -void String::replace(const String& find, const String& replace) { +void String::replace(const String &find, const String &replace) { if (len() == 0 || find.len() == 0) return; int diff = replace.len() - find.len(); @@ -735,7 +679,7 @@ void String::replace(const String& find, const String& replace) { readFrom = foundAt + find.len(); setLen(len() + diff); } - memmove_P(writeTo, readFrom, strlen(readFrom)+1); + memmove_P(writeTo, readFrom, strlen(readFrom) + 1); } else { unsigned int size = len(); // compute size needed for result while ((foundAt = strstr(readFrom, find.buffer())) != NULL) { @@ -759,13 +703,6 @@ void String::replace(const String& find, const String& replace) { } } -void String::remove(unsigned int index) { - // Pass the biggest integer as the count. The remove method - // below will take care of truncating it at the end of the - // string. - remove(index, (unsigned int) -1); -} - void String::remove(unsigned int index, unsigned int count) { if (index >= len()) { return; @@ -828,11 +765,10 @@ long String::toInt(void) const { float String::toFloat(void) const { if (buffer()) return atof(buffer()); - return 0; + return 0.0F; } -double String::toDouble(void) const -{ +double String::toDouble(void) const { if (buffer()) return atof(buffer()); return 0.0; diff --git a/cores/esp8266/WString.h b/cores/esp8266/WString.h index 262cfda0a..dcb098238 100644 --- a/cores/esp8266/WString.h +++ b/cores/esp8266/WString.h @@ -53,7 +53,7 @@ class String { // if the initial value is null or invalid, or if memory allocation // fails, the string will be marked as invalid (i.e. "if (s)" will // be false). - String() { + String() __attribute__((always_inline)) { // See init() init(); } String(const char *cstr); @@ -61,7 +61,12 @@ class String { String(const __FlashStringHelper *str); String(String &&rval) noexcept; String(StringSumHelper &&rval) noexcept; - explicit String(char c); + explicit String(char c) { + sso.buff[0] = c; + sso.buff[1] = 0; + sso.len = 1; + sso.isHeap = 0; + } explicit String(unsigned char, unsigned char base = 10); explicit String(int, unsigned char base = 10); explicit String(unsigned int, unsigned char base = 10); @@ -69,35 +74,35 @@ class String { explicit String(unsigned long, unsigned char base = 10); explicit String(float, unsigned char decimalPlaces = 2); explicit String(double, unsigned char decimalPlaces = 2); - ~String(void); + ~String() { + invalidate(); + } // memory management // return true on success, false on failure (in which case, the string // is left unchanged). reserve(0), if successful, will validate an // invalid string (i.e., "if (s)" will be true afterwards) unsigned char reserve(unsigned int size); - inline unsigned int length(void) const { - if(buffer()) { - return len(); - } else { - return 0; - } + unsigned int length(void) const { + return buffer() ? len() : 0; } - inline void clear(void) { + void clear(void) { setLen(0); } - inline bool isEmpty(void) const { + bool isEmpty(void) const { return length() == 0; } // creates a copy of the assigned value. if the value is null or // invalid, or if the memory allocation fails, the string will be // marked as invalid ("if (s)" will be false). - String & operator =(const String &rhs); - String & operator =(const char *cstr); - String & operator = (const __FlashStringHelper *str); - String & operator =(String &&rval) noexcept; - String & operator =(StringSumHelper &&rval) noexcept; + String &operator =(const String &rhs); + String &operator =(const char *cstr); + String &operator =(const __FlashStringHelper *str); + String &operator =(String &&rval) noexcept; + String &operator =(StringSumHelper &&rval) noexcept { + return operator =((String &&)rval); + } // concatenate (works w/ built-in types) @@ -114,67 +119,67 @@ class String { unsigned char concat(unsigned long num); unsigned char concat(float num); unsigned char concat(double num); - unsigned char concat(const __FlashStringHelper * str); + unsigned char concat(const __FlashStringHelper *str); unsigned char concat(const char *cstr, unsigned int length); // if there's not enough memory for the concatenated value, the string // will be left unchanged (but this isn't signalled in any way) - String & operator +=(const String &rhs) { + String &operator +=(const String &rhs) { concat(rhs); - return (*this); + return *this; } - String & operator +=(const char *cstr) { + String &operator +=(const char *cstr) { concat(cstr); - return (*this); + return *this; } - String & operator +=(char c) { + String &operator +=(char c) { concat(c); - return (*this); + return *this; } - String & operator +=(unsigned char num) { + String &operator +=(unsigned char num) { concat(num); - return (*this); + return *this; } - String & operator +=(int num) { + String &operator +=(int num) { concat(num); - return (*this); + return *this; } - String & operator +=(unsigned int num) { + String &operator +=(unsigned int num) { concat(num); - return (*this); + return *this; } - String & operator +=(long num) { + String &operator +=(long num) { concat(num); - return (*this); + return *this; } - String & operator +=(unsigned long num) { + String &operator +=(unsigned long num) { concat(num); - return (*this); + return *this; } - String & operator +=(float num) { + String &operator +=(float num) { concat(num); - return (*this); + return *this; } - String & operator +=(double num) { + String &operator +=(double num) { concat(num); - return (*this); + return *this; } - String & operator += (const __FlashStringHelper *str){ + String &operator +=(const __FlashStringHelper *str) { concat(str); - return (*this); + return *this; } - friend StringSumHelper & operator +(const StringSumHelper &lhs, const String &rhs); - friend StringSumHelper & operator +(const StringSumHelper &lhs, const char *cstr); - friend StringSumHelper & operator +(const StringSumHelper &lhs, char c); - friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned char num); - friend StringSumHelper & operator +(const StringSumHelper &lhs, int num); - friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned int num); - friend StringSumHelper & operator +(const StringSumHelper &lhs, long num); - friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned long num); - friend StringSumHelper & operator +(const StringSumHelper &lhs, float num); - friend StringSumHelper & operator +(const StringSumHelper &lhs, double num); - friend StringSumHelper & operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs); + friend StringSumHelper &operator +(const StringSumHelper &lhs, const String &rhs); + friend StringSumHelper &operator +(const StringSumHelper &lhs, const char *cstr); + friend StringSumHelper &operator +(const StringSumHelper &lhs, char c); + friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned char num); + friend StringSumHelper &operator +(const StringSumHelper &lhs, int num); + friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned int num); + friend StringSumHelper &operator +(const StringSumHelper &lhs, long num); + friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long num); + friend StringSumHelper &operator +(const StringSumHelper &lhs, float num); + friend StringSumHelper &operator +(const StringSumHelper &lhs, double num); + friend StringSumHelper &operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs); // comparison (only works w/ Strings and "strings") operator StringIfHelperType() const { @@ -202,45 +207,45 @@ class String { unsigned char equalsIgnoreCase(const String &s) const; unsigned char equalsConstantTime(const String &s) const; unsigned char startsWith(const String &prefix) const; - unsigned char startsWith(const char * prefix) const { + unsigned char startsWith(const char *prefix) const { return this->startsWith(String(prefix)); } - unsigned char startsWith(const __FlashStringHelper * prefix) const { + unsigned char startsWith(const __FlashStringHelper *prefix) const { return this->startsWith(String(prefix)); } unsigned char startsWith(const String &prefix, unsigned int offset) const; unsigned char endsWith(const String &suffix) const; - unsigned char endsWith(const char * suffix) const { + unsigned char endsWith(const char *suffix) const { return this->endsWith(String(suffix)); } - unsigned char endsWith(const __FlashStringHelper * suffix) const { + unsigned char endsWith(const __FlashStringHelper *suffix) const { return this->endsWith(String(suffix)); } // character access - char charAt(unsigned int index) const; + char charAt(unsigned int index) const { + return operator [](index); + } void setCharAt(unsigned int index, char c); char operator [](unsigned int index) const; - char& operator [](unsigned int index); + char &operator [](unsigned int index); void getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index = 0) const; void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const { getBytes((unsigned char *) buf, bufsize, index); } - const char* c_str() const { return buffer(); } - char* begin() { return wbuffer(); } - char* end() { return wbuffer() + length(); } - const char* begin() const { return c_str(); } - const char* end() const { return c_str() + length(); } + const char *c_str() const { return buffer(); } + char *begin() { return wbuffer(); } + char *end() { return wbuffer() + length(); } + const char *begin() const { return c_str(); } + const char *end() const { return c_str() + length(); } // search - int indexOf(char ch) const; - int indexOf(char ch, unsigned int fromIndex) const; - int indexOf(const char *str) const; - int indexOf(const char *str, unsigned int fromIndex) const; - int indexOf(const __FlashStringHelper *str) const; - int indexOf(const __FlashStringHelper *str, unsigned int fromIndex) const; - int indexOf(const String &str) const; - int indexOf(const String &str, unsigned int fromIndex) const; + int indexOf(char ch, unsigned int fromIndex = 0) const; + int indexOf(const char *str, unsigned int fromIndex = 0) const; + int indexOf(const __FlashStringHelper *str, unsigned int fromIndex = 0) const { + return indexOf((const char*)str, fromIndex); + } + int indexOf(const String &str, unsigned int fromIndex = 0) const; int lastIndexOf(char ch) const; int lastIndexOf(char ch, unsigned int fromIndex) const; int lastIndexOf(const String &str) const; @@ -248,29 +253,29 @@ class String { String substring(unsigned int beginIndex) const { return substring(beginIndex, len()); } - ; String substring(unsigned int beginIndex, unsigned int endIndex) const; // modification void replace(char find, char replace); - void replace(const String& find, const String& replace); - void replace(const char * find, const String& replace) { + void replace(const String &find, const String &replace); + void replace(const char *find, const String &replace) { this->replace(String(find), replace); } - void replace(const __FlashStringHelper * find, const String& replace) { + void replace(const __FlashStringHelper *find, const String &replace) { this->replace(String(find), replace); } - void replace(const char * find, const char * replace) { + void replace(const char *find, const char *replace) { this->replace(String(find), String(replace)); } - void replace(const __FlashStringHelper * find, const char * replace) { + void replace(const __FlashStringHelper *find, const char *replace) { this->replace(String(find), String(replace)); } - void replace(const __FlashStringHelper * find, const __FlashStringHelper * replace) { + void replace(const __FlashStringHelper *find, const __FlashStringHelper *replace) { this->replace(String(find), String(replace)); } - void remove(unsigned int index); - void remove(unsigned int index, unsigned int count); + // Pass the biggest integer if the count is not specified. + // The remove method below will take care of truncating it at the end of the string. + void remove(unsigned int index, unsigned int count = (unsigned int)-1); void toLowerCase(void); void toUpperCase(void); void trim(void); @@ -278,11 +283,11 @@ class String { // parsing/conversion long toInt(void) const; float toFloat(void) const; - double toDouble(void) const; + double toDouble(void) const; protected: // Contains the string info when we're not in SSO mode - struct _ptr { + struct _ptr { char * buff; uint16_t cap; uint16_t len; @@ -291,8 +296,8 @@ class String { enum { SSOSIZE = sizeof(struct _ptr) + 4 - 1 }; // Characters to allocate space for SSO, must be 12 or more struct _sso { char buff[SSOSIZE]; - unsigned char len : 7; // Ensure only one byte is allocated by GCC for the bitfields - unsigned char isSSO : 1; + unsigned char len : 7; // Ensure only one byte is allocated by GCC for the bitfields + unsigned char isHeap : 1; } __attribute__((packed)); // Ensure that GCC doesn't expand the flag byte to a 32-bit word for alignment issues enum { CAPACITY_MAX = 65535 }; // If typeof(cap) changed from uint16_t, be sure to update this enum to the max value storable in the type union { @@ -300,25 +305,47 @@ class String { struct _sso sso; }; // Accessor functions - inline bool isSSO() const { return sso.isSSO; } - inline unsigned int len() const { return isSSO() ? sso.len : ptr.len; } - inline unsigned int capacity() const { return isSSO() ? (unsigned int)SSOSIZE - 1 : ptr.cap; } // Size of max string not including terminal NUL - inline void setSSO(bool set) { sso.isSSO = set; } - inline void setLen(int len) { if (isSSO()) sso.len = len; else ptr.len = len; } - inline void setCapacity(int cap) { if (!isSSO()) ptr.cap = cap; } - inline void setBuffer(char *buff) { if (!isSSO()) ptr.buff = buff; } + bool isSSO() const { return !sso.isHeap; } + unsigned int len() const { return isSSO() ? sso.len : ptr.len; } + unsigned int capacity() const { return isSSO() ? (unsigned int)SSOSIZE - 1 : ptr.cap; } // Size of max string not including terminal NUL + void setSSO(bool set) { sso.isHeap = !set; } + void setLen(int len) { + if (isSSO()) { + setSSO(true); // Avoid emitting of bitwise EXTRACT-AND-OR ops (store-merging optimization) + sso.len = len; + } else + ptr.len = len; + } + void setCapacity(int cap) { if (!isSSO()) ptr.cap = cap; } + void setBuffer(char *buff) { if (!isSSO()) ptr.buff = buff; } // Buffer accessor functions - inline const char *buffer() const { return (const char *)(isSSO() ? sso.buff : ptr.buff); } - inline char *wbuffer() const { return isSSO() ? const_cast(sso.buff) : ptr.buff; } // Writable version of buffer + const char *buffer() const { return wbuffer(); } + char *wbuffer() const { return isSSO() ? const_cast(sso.buff) : ptr.buff; } // Writable version of buffer protected: - void init(void); + void init(void) __attribute__((always_inline)) { + sso.buff[0] = 0; + sso.len = 0; + sso.isHeap = 0; + // Without the 6 statements shown below, GCC simply emits such as: "MOVI.N aX,0", "S8I aX,a2,0" and "S8I aX,a2,11" (8 bytes in total) + sso.buff[1] = 0; + sso.buff[2] = 0; + sso.buff[3] = 0; + sso.buff[8] = 0; + sso.buff[9] = 0; + sso.buff[10] = 0; + // With the above, thanks to store-merging, GCC can use the narrow form of 32-bit store insn ("S32I.N") and emits: + // "MOVI.N aX,0", "S32I.N aX,a2,0" and "S32I.N aX,a2,8" (6 bytes in total) + // (Literature: Xtensa(R) Instruction Set Reference Manual, "S8I - Store 8-bit" [p.504] and "S32I.N - Narrow Store 32-bit" [p.512]) + // Unfortunately, GCC seems not to re-evaluate the cost of inlining after the store-merging optimizer stage, + // `always_inline` attribute is necessary in order to keep inlining. + } void invalidate(void); unsigned char changeBuffer(unsigned int maxStrLen); // copy and move - String & copy(const char *cstr, unsigned int length); - String & copy(const __FlashStringHelper *pstr, unsigned int length); + String ©(const char *cstr, unsigned int length); + String ©(const __FlashStringHelper *pstr, unsigned int length); void move(String &rhs) noexcept; }; @@ -354,6 +381,9 @@ class StringSumHelper: public String { StringSumHelper(double num) : String(num) { } + StringSumHelper(const __FlashStringHelper *s) : + String(s) { + } }; extern const String emptyString; diff --git a/cores/esp8266/core_esp8266_waveform.cpp b/cores/esp8266/core_esp8266_waveform.cpp deleted file mode 100644 index 597a8e88a..000000000 --- a/cores/esp8266/core_esp8266_waveform.cpp +++ /dev/null @@ -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 -#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<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<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 -} - -}; diff --git a/cores/esp8266/core_esp8266_waveform.h b/cores/esp8266/core_esp8266_waveform.h index e42a17f89..4c5ce4fed 100644 --- a/cores/esp8266/core_esp8266_waveform.h +++ b/cores/esp8266/core_esp8266_waveform.h @@ -1,76 +1,7 @@ -/* - esp8266_waveform - General purpose waveform generation and control, - supporting outputs on all pins in parallel. - - Copyright (c) 2018 Earle F. Philhower, III. All rights reserved. - - The core idea is to have a programmable waveform generator with a unique - high and low period (defined in microseconds or CPU clock cycles). TIMER1 is - set to 1-shot mode and is always loaded with the time until the next edge - of any live waveforms. - - Up to one waveform generator per pin supported. - - Each waveform generator is synchronized to the ESP clock cycle counter, not the - timer. This allows for removing interrupt jitter and delay as the counter - always increments once per 80MHz clock. Changes to a waveform are - contiguous and only take effect on the next waveform transition, - allowing for smooth transitions. - - This replaces older tone(), analogWrite(), and the Servo classes. - - Everywhere in the code where "cycles" is used, it means ESP.getCycleCount() - clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1 - cycles (which may be 2 CPU clock cycles @ 160MHz). - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include - -#ifndef __ESP8266_WAVEFORM_H -#define __ESP8266_WAVEFORM_H - -#ifdef __cplusplus -extern "C" { -#endif - -// Start or change a waveform of the specified high and low times on specific pin. -// If runtimeUS > 0 then automatically stop it after that many usecs. -// Returns true or false on success or failure. -int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS); -// Start or change a waveform of the specified high and low CPU clock cycles on specific pin. -// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles. -// Returns true or false on success or failure. -int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles); -// Stop a waveform, if any, on the specified pin. -// Returns true or false on success or failure. -int stopWaveform(uint8_t pin); - -// Add a callback function to be called on *EVERY* timer1 trigger. The -// callback returns the number of microseconds until the next desired call. -// However, since it is called every timer1 interrupt, it may be called -// again before this period. It should therefore use the ESP Cycle Counter -// to determine whether or not to perform an operation. -// Pass in NULL to disable the callback and, if no other waveforms being -// generated, stop the timer as well. -// Make sure the CB function has the ICACHE_RAM_ATTR decorator. -void setTimer1Callback(uint32_t (*fn)()); - -#ifdef __cplusplus -} -#endif +// Wrapper to include both versions of the waveform generator +#ifdef WAVEFORM_LOCKED_PHASE + #include "core_esp8266_waveform_phase.h" +#else + #include "core_esp8266_waveform_pwm.h" #endif diff --git a/cores/esp8266/core_esp8266_waveform_phase.cpp b/cores/esp8266/core_esp8266_waveform_phase.cpp new file mode 100644 index 000000000..3d8eb1a89 --- /dev/null +++ b/cores/esp8266/core_esp8266_waveform_phase.cpp @@ -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 +#include "ets_sys.h" +#include + +// 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(periodCcys) <= 0 || + static_cast(highCcys) < 0 || static_cast(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(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(waveNextEventCcy - wave.expiryCcy) >= 0 && + static_cast(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(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(waveNextEventCcy - wave.expiryCcy) > 0) { + waveNextEventCcy = wave.expiryCcy; + } + } + + if (static_cast(waveNextEventCcy - isrTimeoutCcy) >= 0) { + busyPins ^= pinBit; + if (static_cast(waveform.nextEventCcy - waveNextEventCcy) > 0) { + waveform.nextEventCcy = waveNextEventCcy; + } + } + else if (static_cast(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 diff --git a/cores/esp8266/core_esp8266_waveform_phase.h b/cores/esp8266/core_esp8266_waveform_phase.h new file mode 100644 index 000000000..dff8fe502 --- /dev/null +++ b/cores/esp8266/core_esp8266_waveform_phase.h @@ -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 + +#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 diff --git a/cores/esp8266/core_esp8266_waveform_pwm.cpp b/cores/esp8266/core_esp8266_waveform_pwm.cpp new file mode 100644 index 000000000..aea88e15b --- /dev/null +++ b/cores/esp8266/core_esp8266_waveform_pwm.cpp @@ -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 +#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<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<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<= _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<= 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<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<> 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<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 diff --git a/cores/esp8266/core_esp8266_waveform_pwm.h b/cores/esp8266/core_esp8266_waveform_pwm.h new file mode 100644 index 000000000..3d66bc141 --- /dev/null +++ b/cores/esp8266/core_esp8266_waveform_pwm.h @@ -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 + +#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 diff --git a/cores/esp8266/core_esp8266_wiring_digital.cpp b/cores/esp8266/core_esp8266_wiring_digital.cpp index 9c15703e0..539f5448b 100644 --- a/cores/esp8266/core_esp8266_wiring_digital.cpp +++ b/cores/esp8266/core_esp8266_wiring_digital.cpp @@ -82,7 +82,10 @@ extern void __pinMode(uint8_t pin, uint8_t mode) { } extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) { - stopWaveform(pin); + stopWaveform(pin); // Disable any tone +#ifndef WAVEFORM_LOCKED_PHASE + _stopPWM(pin); // ...and any analogWrite +#endif if(pin < 16){ if(val) GPOS = (1 << pin); else GPOC = (1 << pin); diff --git a/cores/esp8266/core_esp8266_wiring_pwm.cpp b/cores/esp8266/core_esp8266_wiring_pwm.cpp index b4991310d..a456c965c 100644 --- a/cores/esp8266/core_esp8266_wiring_pwm.cpp +++ b/cores/esp8266/core_esp8266_wiring_pwm.cpp @@ -26,27 +26,18 @@ extern "C" { -static uint32_t analogMap = 0; static int32_t analogScale = 255; // Match upstream default, breaking change from 2.x.x + +#ifdef WAVEFORM_LOCKED_PHASE + +static uint32_t analogMap = 0; static uint16_t analogFreq = 1000; -extern void __analogWriteRange(uint32_t range) { - if ((range >= 15) && (range <= 65535)) { - analogScale = range; - } -} - -extern void __analogWriteResolution(int res) { - if ((res >= 4) && (res <= 16)) { - analogScale = (1 << res) - 1; - } -} - extern void __analogWriteFreq(uint32_t freq) { if (freq < 100) { analogFreq = 100; - } else if (freq > 40000) { - analogFreq = 40000; + } else if (freq > 60000) { + analogFreq = 60000; } else { analogFreq = freq; } @@ -63,22 +54,68 @@ extern void __analogWrite(uint8_t pin, int val) { val = analogScale; } + if (analogMap & 1UL << pin) { // Per the Arduino docs at https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/ // val: the duty cycle: between 0 (always off) and 255 (always on). // So if val = 0 we have digitalWrite(LOW), if we have val==range we have digitalWrite(HIGH) - analogMap &= ~(1 << pin); + analogMap &= ~(1 << pin); + } + else { + pinMode(pin, OUTPUT); + } uint32_t high = (analogPeriod * val) / analogScale; uint32_t low = analogPeriod - high; - pinMode(pin, OUTPUT); - if (low == 0) { - digitalWrite(pin, HIGH); - } else if (high == 0) { - digitalWrite(pin, LOW); + // Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t) + int phaseReference = __builtin_ffs(analogMap) - 1; + if (startWaveformClockCycles(pin, high, low, 0, phaseReference, 0, true)) { + analogMap |= (1 << pin); + } +} + +#else // !WAVEFORM_LOCKED_PHASE + +extern void __analogWriteFreq(uint32_t freq) { + if (freq < 100) { + freq = 100; + } else if (freq > 60000) { + freq = 60000; } else { - if (startWaveformClockCycles(pin, high, low, 0)) { - analogMap |= (1 << pin); - } + freq = freq; + } + _setPWMFreq(freq); +} + +extern void __analogWrite(uint8_t pin, int val) { + if (pin > 16) { + return; + } + + if (val < 0) { + val = 0; + } else if (val > analogScale) { + val = analogScale; + } + + // Per the Arduino docs at https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/ + // val: the duty cycle: between 0 (always off) and 255 (always on). + // So if val = 0 we have digitalWrite(LOW), if we have val==range we have digitalWrite(HIGH) + pinMode(pin, OUTPUT); + _setPWM(pin, val, analogScale); +} + +#endif // WAVEFORM_LOCKED_PHASE + + +extern void __analogWriteRange(uint32_t range) { + if ((range >= 15) && (range <= 65535)) { + analogScale = range; + } +} + +extern void __analogWriteResolution(int res) { + if ((res >= 4) && (res <= 16)) { + analogScale = (1 << res) - 1; } } diff --git a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp index 7ec3ab86f..37cdae0fc 100644 --- a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp +++ b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp @@ -725,7 +725,7 @@ int HTTPClient::writeToStream(Stream * stream) int ret = 0; if(_transferEncoding == HTTPC_TE_IDENTITY) { - if(len > 0) { + if(len > 0 || len == -1) { ret = writeToStreamDataBlock(stream, len); // have we an error? @@ -1184,6 +1184,12 @@ int HTTPClient::writeToStreamDataBlock(Stream * stream, int size) if(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 int bytesRead = _client->readBytes(buff, readBytes); diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer.h b/libraries/ESP8266WebServer/src/ESP8266WebServer.h index 23b5b328a..d40313a59 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer.h +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer.h @@ -246,7 +246,7 @@ protected: bool _parseForm(ClientType& client, const String& boundary, uint32_t len); bool _parseFormUploadAborted(); void _uploadWriteByte(uint8_t b); - uint8_t _uploadReadByte(ClientType& client); + int _uploadReadByte(ClientType& client); void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength); bool _collectHeader(const char* headerName, const char* headerValue); diff --git a/libraries/ESP8266WebServer/src/Parsing-impl.h b/libraries/ESP8266WebServer/src/Parsing-impl.h index 33d3efd66..cab0c04f7 100644 --- a/libraries/ESP8266WebServer/src/Parsing-impl.h +++ b/libraries/ESP8266WebServer/src/Parsing-impl.h @@ -347,14 +347,14 @@ void ESP8266WebServerTemplate::_uploadWriteByte(uint8_t b){ } template -uint8_t ESP8266WebServerTemplate::_uploadReadByte(ClientType& client){ +int ESP8266WebServerTemplate::_uploadReadByte(ClientType& client){ int res = client.read(); if(res == -1){ while(!client.available() && client.connected()) yield(); res = client.read(); } - return (uint8_t)res; + return res; } @@ -444,45 +444,34 @@ bool ESP8266WebServerTemplate::_parseForm(ClientType& client, const _currentHandler->upload(*this, _currentUri, *_currentUpload); _currentUpload->status = UPLOAD_FILE_WRITE; - int bLen = boundary.length(); - uint8_t boundBuf[2 + bLen + 1]; // "--" + boundary + null terminator - boundBuf[2 + bLen] = '\0'; - uint8_t argByte; - bool first = true; - while (1) { - //attempt to fill up boundary buffer with length of boundary string - int i; - for (i = 0; i < 2 + bLen; i++) { - if (!client.connected()) return _parseFormUploadAborted(); - argByte = _uploadReadByte(client); - if (argByte == '\r') + int fastBoundaryLen = 4 /* \r\n-- */ + boundary.length() + 1 /* \0 */; + char fastBoundary[ fastBoundaryLen ]; + snprintf(fastBoundary, fastBoundaryLen, "\r\n--%s", boundary.c_str()); + int boundaryPtr = 0; + while ( true ) { + int ret = _uploadReadByte(client); + if (ret < 0) { + // Unexpected, we should have had data available per above + return _parseFormUploadAborted(); + } + char in = (char) ret; + if (in == fastBoundary[ boundaryPtr ]) { + // The input matched the current expected character, advance and possibly exit this file + boundaryPtr++; + if (boundaryPtr == fastBoundaryLen - 1) { + // We read the whole boundary line, we're done here! break; - boundBuf[i] = argByte; - } - if ((strncmp((const char*)boundBuf, "--", 2) == 0) && (strcmp((const char*)(boundBuf + 2), boundary.c_str()) == 0)) - break; //found the boundary, done parsing this file - if (first) first = false; //only add newline characters after the first line - else { - _uploadWriteByte('\r'); - _uploadWriteByte('\n'); - } - // current line does not contain boundary, upload all bytes in boundary buffer - for (int j = 0; j < i; j++) - _uploadWriteByte(boundBuf[j]); - // the initial pass (filling up the boundary buffer) did not reach the end of the line. Upload the rest of the line now - if (i >= 2 + bLen) { - if (!client.connected()) return _parseFormUploadAborted(); - argByte = _uploadReadByte(client); - while (argByte != '\r') { - if (!client.connected()) return _parseFormUploadAborted(); - _uploadWriteByte(argByte); - argByte = _uploadReadByte(client); } + } else { + // The char doesn't match what we want, so dump whatever matches we had, the read in char, and reset ptr to start + for (int i = 0; i < boundaryPtr; i++) { + _uploadWriteByte( fastBoundary[ i ] ); + } + _uploadWriteByte( in ); + boundaryPtr = 0; } - if (!client.connected()) return _parseFormUploadAborted(); - _uploadReadByte(client); // '\n' } - //Found the boundary string, finish processing this file upload + // Found the boundary string, finish processing this file upload if (_currentHandler && _currentHandler->canUpload(_currentUri)) _currentHandler->upload(*this, _currentUri, *_currentUpload); _currentUpload->totalSize += _currentUpload->currentSize; diff --git a/libraries/Servo/src/Servo.cpp b/libraries/Servo/src/Servo.cpp index aff9afebb..d24ea3379 100644 --- a/libraries/Servo/src/Servo.cpp +++ b/libraries/Servo/src/Servo.cpp @@ -69,8 +69,13 @@ uint8_t Servo::attach(int pin, uint16_t minUs, uint16_t maxUs) uint8_t Servo::attach(int pin, uint16_t minUs, uint16_t maxUs, int value) { if (!_attached) { +#ifdef WAVEFORM_LOCKED_PHASE + pinMode(pin, OUTPUT); + digitalWrite(pin, LOW); +#else digitalWrite(pin, LOW); pinMode(pin, OUTPUT); +#endif _pin = pin; _attached = true; } @@ -90,7 +95,11 @@ void Servo::detach() { if (_attached) { _servoMap &= ~(1 << _pin); +#ifdef WAVEFORM_LOCKED_PHASE startWaveform(_pin, 0, REFRESH_INTERVAL, 1); +#else + // TODO - timeHigh == 0 is illegal in _PWM code branch. Do nothing for now. +#endif delay(REFRESH_INTERVAL / 1000); // long enough to complete active period under all circumstances. stopWaveform(_pin); _attached = false; @@ -115,7 +124,14 @@ void Servo::writeMicroseconds(int value) _valueUs = value; if (_attached) { _servoMap &= ~(1 << _pin); - if (startWaveform(_pin, _valueUs, REFRESH_INTERVAL - _valueUs, 0)) { +#ifdef WAVEFORM_LOCKED_PHASE + // Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t) + int phaseReference = __builtin_ffs(_servoMap) - 1; + if (startWaveform(_pin, _valueUs, REFRESH_INTERVAL - _valueUs, 0, phaseReference)) +#else + if (startWaveform(_pin, _valueUs, REFRESH_INTERVAL - _valueUs, 0)) +#endif + { _servoMap |= (1 << _pin); } } diff --git a/libraries/SoftwareSerial b/libraries/SoftwareSerial index 4c08ee8d2..6d520c259 160000 --- a/libraries/SoftwareSerial +++ b/libraries/SoftwareSerial @@ -1 +1 @@ -Subproject commit 4c08ee8d2cb7b5b27eb4f86797694cbac94aa5c9 +Subproject commit 6d520c259cad4457ccdbee362c16f7fa3b504b06 diff --git a/platform.txt b/platform.txt index 5e2c9be54..b3401cd0d 100644 --- a/platform.txt +++ b/platform.txt @@ -55,7 +55,7 @@ compiler.libc.path={runtime.platform.path}/tools/sdk/libc/xtensa-lx106-elf compiler.cpreprocessor.flags=-D__ets__ -DICACHE_FLASH -U__STRICT_ANSI__ "-I{compiler.sdk.path}/include" "-I{compiler.sdk.path}/{build.lwip_include}" "-I{compiler.libc.path}/include" "-I{build.path}/core" compiler.c.cmd=xtensa-lx106-elf-gcc -compiler.c.flags=-c {compiler.warning_flags} -std=gnu17 {build.stacksmash_flags} -Os -g -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -mlongcalls -mtext-section-literals -falign-functions=4 -MMD -ffunction-sections -fdata-sections {build.exception_flags} {build.sslflags} +compiler.c.flags=-c {compiler.warning_flags} -std=gnu17 {build.stacksmash_flags} -Os -g -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -mlongcalls -mtext-section-literals -falign-functions=4 -MMD -ffunction-sections -fdata-sections {build.exception_flags} {build.sslflags} {build.waveform} compiler.S.cmd=xtensa-lx106-elf-gcc compiler.S.flags=-c -g -x assembler-with-cpp -MMD -mlongcalls @@ -66,7 +66,7 @@ compiler.c.elf.cmd=xtensa-lx106-elf-gcc compiler.c.elf.libs=-lhal -lphy -lpp -lnet80211 {build.lwip_lib} -lwpa -lcrypto -lmain -lwps -lbearssl -lespnow -lsmartconfig -lairkiss -lwpa2 {build.stdcpp_lib} -lm -lc -lgcc compiler.cpp.cmd=xtensa-lx106-elf-g++ -compiler.cpp.flags=-c {compiler.warning_flags} {build.stacksmash_flags} -Os -g -mlongcalls -mtext-section-literals -fno-rtti -falign-functions=4 {build.stdcpp_level} -MMD -ffunction-sections -fdata-sections {build.exception_flags} {build.sslflags} +compiler.cpp.flags=-c {compiler.warning_flags} {build.stacksmash_flags} -Os -g -mlongcalls -mtext-section-literals -fno-rtti -falign-functions=4 {build.stdcpp_level} -MMD -ffunction-sections -fdata-sections {build.exception_flags} {build.sslflags} {build.waveform} compiler.as.cmd=xtensa-lx106-elf-as diff --git a/tests/common.sh b/tests/common.sh index 930071784..0c6ac9540 100755 --- a/tests/common.sh +++ b/tests/common.sh @@ -66,6 +66,8 @@ function build_sketches() local sketches=$(find $srcpath -name *.ino | sort) print_size_info >size.log export ARDUINO_IDE_PATH=$arduino + local pwm_phase="" + [ $(( $build_rem % 2 )) -eq 0 ] && pwm_phase="--waveform_phase" local testcnt=0 for sketch in $sketches; do testcnt=$(( ($testcnt + 1) % $build_mod )) @@ -104,7 +106,7 @@ function build_sketches() fi echo -e "\n ------------ Building $sketch ------------ \n"; # $arduino --verify $sketch; - if [ "$WINDOWS" == "1" ]; then + if [ "$WINDOWS" == "1" ]; then sketch=$(echo $sketch | sed 's/^\/c//') # MINGW will try to be helpful and silently convert args that look like paths to point to a spot inside the MinGW dir. This breaks everything. # http://www.mingw.org/wiki/Posix_path_conversion @@ -112,8 +114,8 @@ function build_sketches() export MSYS2_ARG_CONV_EXC="*" export MSYS_NO_PATHCONV=1 fi - echo "$build_cmd $sketch" - time ($build_cmd $sketch >build.log) + echo "$build_cmd $pwm_phase $sketch" + time ($build_cmd $pwm_phase $sketch >build.log) local result=$? if [ $result -ne 0 ]; then echo "Build failed ($1)" diff --git a/tools/boards.txt.py b/tools/boards.txt.py index 616275f67..d0fcc4585 100755 --- a/tools/boards.txt.py +++ b/tools/boards.txt.py @@ -1466,6 +1466,18 @@ def led (name, default, ledList): ])) return { name: led } +################################################################ +# Waveform flavour + +def waveform (): + return { 'waveform': collections.OrderedDict([ + ('.menu.waveform.pwm', 'Locked PWM'), + ('.menu.waveform.pwm.build.waveform', ''), + ('.menu.waveform.phase', 'Locked Phase'), + ('.menu.waveform.phase.build.waveform', '-DWAVEFORM_LOCKED_PHASE'), + ]) + } + ################################################################ # sdk selection @@ -1517,6 +1529,7 @@ def all_boards (): macros.update(led('led', led_default, range(0,led_max+1))) macros.update(led('led216', 2, { 16 })) macros.update(sdk()) + macros.update(waveform()) if boardfilteropt or excludeboards: print('#') @@ -1561,6 +1574,7 @@ def all_boards (): print('menu.wipe=Erase Flash') print('menu.sdk=Espressif FW') print('menu.ssl=SSL Support') + print('menu.waveform=Waveform Flavour') print('') missingboards = [] @@ -1581,7 +1595,7 @@ def all_boards (): print(id + optname + '=' + board['opts'][optname]) # macros - macrolist = [ 'defaults', 'cpufreq_menu', 'vtable_menu', 'exception_menu', 'stacksmash_menu', 'ssl_cipher_menu' ] + macrolist = [ 'defaults', 'cpufreq_menu', 'vtable_menu', 'exception_menu', 'stacksmash_menu', 'ssl_cipher_menu', 'waveform' ] if 'macro' in board: macrolist += board['macro'] macrolist += [ 'lwip', 'debug_menu', 'flash_erase_menu' ] diff --git a/tools/build.py b/tools/build.py index efb1409ba..791ac30e2 100755 --- a/tools/build.py +++ b/tools/build.py @@ -69,6 +69,8 @@ def compile(tmp_dir, sketch, cache, tools_dir, hardware_dir, ide_path, f, args): 'ResetMethod=nodemcu'.format(**vars(args)) if args.debug_port and args.debug_level: fqbn += 'dbg={debug_port},lvl={debug_level}'.format(**vars(args)) + if args.waveform_phase: + fqbn += ',waveform=phase' cmd += [fqbn] cmd += ['-built-in-libraries', ide_path + '/libraries'] cmd += ['-ide-version=10607'] @@ -115,6 +117,8 @@ def parse_args(): type=int, choices=[40, 80]) parser.add_argument('--debug_port', help='Debug port', choices=['Serial', 'Serial1']) + parser.add_argument('--waveform_phase', action='store_true', + help='Select waveform locked on phase') parser.add_argument('--debug_level', help='Debug level') parser.add_argument('--build_cache', help='Build directory to cache core.a', default='') parser.add_argument('sketch_path', help='Sketch file path') diff --git a/tools/elf2bin.py b/tools/elf2bin.py index ffc3a6208..231bd5e5d 100755 --- a/tools/elf2bin.py +++ b/tools/elf2bin.py @@ -141,6 +141,31 @@ def add_crc(out): with open(out, "wb") as binfile: binfile.write(raw) +def gzip_bin(mode, out): + import gzip + + firmware_path = out + gzip_path = firmware_path + '.gz' + orig_path = firmware_path + '.orig' + if os.path.exists(gzip_path): + os.remove(gzip_path) + print('GZipping firmware ' + firmware_path) + with open(firmware_path, 'rb') as firmware_file, \ + gzip.open(gzip_path, 'wb') as dest: + data = firmware_file.read() + dest.write(data) + orig_size = os.stat(firmware_path).st_size + gzip_size = os.stat(gzip_path).st_size + print("New FW size {:d} bytes vs old {:d} bytes".format( + gzip_size, orig_size)) + + if mode == "PIO": + if os.path.exists(orig_path): + os.remove(orig_path) + print('Moving original firmware to ' + orig_path) + os.rename(firmware_path, orig_path) + os.rename(gzip_path, firmware_path) + def main(): parser = argparse.ArgumentParser(description='Create a BIN file from eboot.elf and Arduino sketch.elf for upload by esptool.py') parser.add_argument('-e', '--eboot', action='store', required=True, help='Path to the Arduino eboot.elf bootloader') @@ -150,6 +175,7 @@ def main(): parser.add_argument('-s', '--flash_size', action='store', required=True, choices=['256K', '512K', '1M', '2M', '4M', '8M', '16M'], help='SPI flash size') parser.add_argument('-o', '--out', action='store', required=True, help='Output BIN filename') parser.add_argument('-p', '--path', action='store', required=True, help='Path to Xtensa toolchain binaries') + parser.add_argument('-g', '--gzip', choices=['PIO', 'Arduino'], help='PIO - generate gzipped BIN file, Arduino - generate BIN and BIN.gz') args = parser.parse_args() @@ -175,6 +201,9 @@ def main(): # Because the CRC includes both eboot and app, can only calculate it after the entire BIN generated add_crc(args.out) + if args.gzip: + gzip_bin(args.gzip, args.out) + return 0 diff --git a/tools/platformio-build.py b/tools/platformio-build.py index 9dfc9277d..0c893c2cc 100644 --- a/tools/platformio-build.py +++ b/tools/platformio-build.py @@ -45,10 +45,15 @@ Builder.match_splitext = scons_patched_match_splitext env = DefaultEnvironment() platform = env.PioPlatform() +board = env.BoardConfig() +gzip_fw = board.get("build.gzip_fw", False) +gzip_switch = [] FRAMEWORK_DIR = platform.get_package_dir("framework-arduinoespressif8266") assert isdir(FRAMEWORK_DIR) +if gzip_fw: + gzip_switch = ["--gzip", "PIO"] env.Append( ASFLAGS=["-x", "assembler-with-cpp"], @@ -145,7 +150,7 @@ env.Append( "--path", '"%s"' % join( platform.get_package_dir("toolchain-xtensa"), "bin"), "--out", "$TARGET" - ]), "Building $TARGET"), + ] + gzip_switch), "Building $TARGET"), suffix=".bin" ) ) @@ -237,6 +242,13 @@ else: LIBS=["lwip2-536-feat"] ) +# +# Waveform +# +if "PIO_FRAMEWORK_ARDUINO_WAVEFORM_LOCKED_PHASE" in flatten_cppdefines: + env.Append(CPPDEFINES=[("WAVEFORM_LOCKED_PHASE", 1)]) +# PIO_FRAMEWORK_ARDUINO_WAVEFORM_LOCKED_PWM will be used by default + # # VTables #