diff --git a/CodeCoverage.cov b/CodeCoverage.cov new file mode 100644 index 000000000..8c6613e6f --- /dev/null +++ b/CodeCoverage.cov @@ -0,0 +1,672 @@ +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\WString.cpp +RES: _______cccccc_uuuuuu_uuuuuu_uu_uuu_uuuuu_uuuuu_uuuuu_uuuuu_ccccc_uuuuu_uuuuu_uuuuu_uuuuu_uuuuu___ccccccccccccccc__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Configuration\LegacySettingRegistry.h +RES: ____________uuuu______________u_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\FS.h +RES: ________________________________________u________u_____________________c___________________________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\10vSpindle.cpp +RES: ______________________uu__uuu__u_uuuu_uu_____u_u_u_uu__uu__u___uuuu_uuu__u__uuuu_u_uuuuu_uuuuuuuu___c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Motors\StandardStepper.cpp +RES: _______________________u_uuuu_uu_uuuu_u_uu_uu_u_uuuuuuuuuuu_uuuu_uuu__uuuuuuuuuuu__uu_uuu_uuuuuu_u_uuu_u_u_u___c__uuuuuuu__uuuu___u__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\WString.h +RES: _____________________c______ccuucuuuuu_ccccuuuu_____u_____uuuuuuuuuuuu_uuuu_cccc_uuuu_uuuu_uuuu_uuuu_uuuu____uuuuuuuu___________________________________________ucucuc______cccc___________________u______cccuu__u___________cccccccc__uuuuu_uuuuuuuuu_____uu__uuuuu_________cuu_______________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\WebUI\Authentication.cpp +RES: ___________________________________________u__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\Wire.cpp +RES: ____cc_c__ccccccccccccccc_ccccccc____uuu_ccc_uuuuuu_uuuu_uuuuuu_uuuuccccuuuu_uuuuuccccc_cc_ccc_ccuuuuuuuuuuuuuuuuuuuuuccc_c_cccc_cc_cc_uuuuuuuuuuccccccuu_uuuuuuuu_uu_c___ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Extenders\PinExtenderDriver.cpp +RES: _______uuuu_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\SPI.h +RES: ____uu___ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\WebUI\InputBuffer.h +RES: ____________u__________u_______________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\SDFS.cpp +RES: ____cuuuuuuuuu_c__c +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\Wire.h +RES: ___________c_______________c___cccc_________________________________________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\Stream.cpp +RES: ______________________________u_u_uuu_uuu__u_u_uuu_uuu___u_uuuu_uu_uu_uuu_____uuuuuu__uuu___uuuuuuuu_uuu_________________________________________________________________________________________uuu___uuu__u_uu___u_uuuu_uuu_uu_uu__uuu___uuuu_u_u_uu___u_uuuuuuuu__uuu_uu_uuuu_u______uuuuuu_uuuuu_____uuu_uuuuu_uuuuu_uuuuuuuuu_uuuuuuuuu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\SPIFFS.cpp +RES: ____cc___uuuuuuu__c +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Motors\NullMotor.cpp +RES: ____________c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Pins\ErrorPinDetail.cpp +RES: ________c_c____________cccuucc___u_u_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\Print.cpp +RES: ______________________________________ccccccc_u_u__uuuuuuu_uuuuu_u_uuuu_uu_uuu_ccc_uuu_uuu_ccc_ccc_cccuu_cc_ccuuc_c_uuuuu_uu_uuuuu_u_uuu_uuu_uuuu__uuu_uu_uuu_uuuuu_uuuuu_uuuuu_uuuuu_uuuuu_uuuuu_uuuuu_uuuuu_uuuuu_uuuuu_uuuuu_uuuuu_uuuuu___c_c_c__cu___cc_cc_cc_u_u_u__uu___uuu_uu_uu_uu_uu_uu_uu_uu___uuu___uuuu_u__uuu__uu___uuuuuu_uu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Pin.h +RES: ___________________________________________________________________________________c______c______c________u__cccc__cc_cc___cccc____c_c_u_cc_c____c_c__c_c___u___ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\driver\rmt.cpp +RES: ____uuu_u_uuu_uu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\Print.h +RES: __________________________________________cc____ccu_cc_u______u_______________________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Machine\SPIBus.cpp +RES: __________uuuuu_u_uuu_uuu____uuuuu_u_uuuuu__u_________u_u_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\nvs.cpp +RES: __uuuuuuuuu_uuuuuu_uuuuuu_uuuuuu_uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\WebUI\BTConfig.h +RES: __________uuu_u__________________________________________________________________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\nvs.h +RES: ____________uuuuuuu_u_uuuuuuu_u_uuuuuuu_uuuu_uuu_u_uuuuuuu_uuuu_uuu_u_u_uuuu_u_u_uuuu__________uuuuuuu______________________________________________________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Report.cpp +RES: ________________________________________________uuu_u_u__uuuuuuuu__uuuuu_u________uuu_uu____uuuuuuu__uuu_uu________uu____u_uu_u_u_uu_uuu__u_u_u_c______________c______uuuu_u___uu__________u__uu____u___uuuuu__u_uuuuu_uuu_uuuu_uuu_u__uuu_u_uu_uu_uu_uu_uu_uu_uu_uu_u__u_u_u_uu_uu_u__u_u_uu_u__u_u_uu_u__u_________u_uu_u__u__u_uu_uu_uu_uu_u__u_u_uu_uu_uu_u_u__uuuu_uu_uu___uu__uuuuuu__uuuuu_uuu______uuu_uu_uu___u_u_uuu_uuu_u___uuu______uuuuu_u_uu_uuu_uuu_uuuuuuu_uu_u_u_uu__u_u__u_u_uu_uu__u_u_uu_uuuuuu_u__uuuuuuuu_u_u__uuuuu_u_u______uu__uuuuuu_u___uu__u_uuuuu_____uuu_u_u_uuuu_____u_u__uu_uu__uuuu_____u_u___uuuuuu_u_uu_u_____u_uu_uu___uu_______uu_u__uuuuu_uu_u_______u_uu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\I2SO.cpp +RES: ____uuu_u_u_uuu_uuu_u_uuu_uuu_uuu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\FS.cpp +RES: _________________________uuu__uu_uuu__uu_uuu__uu_uuu__uu_uuu___uu__uu_uuu__uu_uuu__uuuuu_uuu__uu_uuu__uu_uuu__uu_uuu__uu_uuuu_u_u_uuu__uu_uuu__uu__uuu_uu_uuu_uu_uuu_uu_u_uuu__uu_uuu_uu_u_uuu_uu_u_uuu_uu_u_uuu_uu_u_uuu_uu_u_u_u_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Configuration\LegacySettingRegistry.cpp +RES: ________u_u_uuu_uu_uuuu_uu_uuu_uuu_u_uuuuuu__u_uu_u_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\freertos\Task.cpp +RES: _____________c_______ccccc_cccc_uuu_uuuuu_uuu_uuu_uuu_uuu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\Capture.h +RES: _______________________c__cccc___uuuuuuuu__________________________ccuuu_u_c_____uuuu_____________uuuuuu_u_uu_uuuuu_u_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\Spindle.h +RES: ______________________________uu____________uu__u___u__uu___uu_u_______u_u___uuuu_uu_u__u____ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\freertos\Queue.cpp +RES: ______uuuuuuu_uu_uu_uuu_u_uuu_u____uu_uuu_uu_uuuu_u_uu_uuu_uuu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\ExceptionHelper.cpp +RES: _______________________________________cccc__cc__ccu__cccc_______c_cc____ccc_ccccccccccccccc__ccccccc_c__cu__cccccccc__ccc_c_________ccccccc_cccc_c__cccc_ccuc_uc_c__ccccuc_cc__cu__cu_c_u_uc___uuuuuu_u__u___u______________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\esp32-hal-timer.cpp +RES: _________uuuu_uuu_uuu_uu___uuu_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Machine\LimitPin.h +RES: _____________u_____uu___u_________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Pins\PinOptionsParser.cpp +RES: __________c_ccc_cccc_c_ccc___cc_c__ccc_ccccc_c_ccc_c_c_c_c_c_cc_c_cc_cccc_c_cccccccc_ccc_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\test\Pins\GPIO.cpp +RES: _____________________________cc__cc__c_c__c_________cc_cc_cc_cccc_c_cccc_c_ccccc_cc_cc_cc_cccc_c_cccc_c_ccccc_cc_cc_cc_ccccccc______________cccccc_ccc__ccccc_ccc_c__c_ccccc_cc_c_c_c_cc_cc_cc_cc_cccc_c_cccc_c_ccccc_cc_cc_cc_cc_ccccc_c_cccc_c_ccccc_cc_ccc_cc_ccc_cc_cc_cc_cuuu_u_uuuu_u_uuuuu______cc_cc_cc_cc_____________cccccc_cuc__ccccc_cuu_u__u_uuuuu_uu__c_c_c_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Pins\LedcPin.cpp +RES: ______________________u________uuuu_uu_u_u_u____uuuuuu_uuu____uuuu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Motors\StandardStepper.h +RES: _________________u__________________uuuuu__u_____________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\10vSpindle.h +RES: ___________________________________u_uuuuu__u_u_________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.1.8.1.3\build\native\include\gtest\internal\gtest-internal.h +RES: _____________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________u______c__________c____________________cc____________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Motors\StepStick.cpp +RES: __________u_uuu_uu__u_uu_uuuuu_uuu__uu_u__u___c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Regex.cpp +RES: _____________________u_uu_uuu__uuu_uu_uu_uu_uu___uuu__uu_uuu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\IPAddress.h +RES: ____________________uccccccu_u_uu___u_________________u__c +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Configuration\GenericFactory.h +RES: ____________cccc__________c_____u______c_____c_u__uuuuuu_u_uuu_uuuuuuuu_u_uuuuu_u__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\xmodem.cpp +RES: ________________________________________u_uuuuuu______________________u_uuuuu_____________uuuuuuu_uuuuuu__uu_uuuu______________uu_u_uuu_uuu_uuuuuu_uuuuuuuu_u___uuuuu_u__uuuuu_uu_uu_uuuuu_uuuuu______uuuu_uuuuuu__uuuuuuuuuu_uuuuu_uuuuuuu_uu__uuuuu_uu__uuuu___uuu_uu_uu_uuuu______uuuuuu____uu____uu_uuuuuuuuuuuuuuuu_uuuuuu_uuu_uuuu_______uuuuuuuuuuuuuu_uuu_______________________________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Channel.h +RES: ______________________________c__c__u__u_u +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Uart.h +RES: ____________________________cccc________________uuu______u__uuuuu______uuu_u_u_uuuuu_uuu_______ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\Spindle.cpp +RES: ________________u_uuuu_uuuuu_u_uuuuu_u__uu_uuu_uuuu_________uuu_uuuu__uuu__uuuuu_uuuu_u_uuuuu_uuuuuuu_uu_uuu_uuuu_uu_u_uuu_uu_______uu___uuuuu___u_u_uu_u__u___u_u__uu_uuuu_u_uu___u_u__uu_uuu_uuuu____uu_uu_uuu_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Pins\GPIOPinDetail.cpp +RES: ________________c_c_c_u____u__u___________c___________c_____u_________u______uu__u_c__c______ccc__ccuuuuu_cuuuuuccu_uu_cc__cc_u_c_ccu_cccccccc_c_____c__c___c__c_cccc___cucu___cc__cc_cccc_cccc_cccu_cu_cu__cc_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Configuration\AfterParse.cpp +RES: ____________uu_uu__u__uu_u_uu_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Motors\RcServo.h +RES: ________________u_____uu_uu_u_u______________uuuuuu__u__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Pins\DebugPinDetail.cpp +RES: ___________u___uuuuuuuuu__uuuuu__uu_uuuu_uuu_uuu_uu_uu_uu_uu_uu_uu_u_uu_uu_u_uuuu_uu__uuuu_uu_uuu_u_u_uuuuuuuuuuuuuuuu_u_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\VFDSpindle.cpp +RES: _________________________________________u____uu________________u__u___uu__u_uuu__uuu_u__u_uu_u___u_uu_uuu_u__u_____uuuu_uuuu___uuuu____uu_________uu_______u__uuuu_________uu_uuu__uuu___uu__u_uuu__u___u___uu__uu_uu_u_u__uuu__uu___u______u_uuuuu_uuuu__uu___uuu___uu__u_uuu_____u_u__uuu_________u_uu____u_u_uuuu__u_uu_u_u_uu_u_u__uuu____u_uu_uuu_u__________u__uuuu_____uuuu__u_uu__u_u_u_uuu___uuu_uuu_uuuuu__u_uuu__u_u_uuuuu__u_uu_uuuuu__u_uuuu_u_____u___u_uu_____uuuu_uuuuuu_uu_uu_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Motors\MotorDriver.cpp +RES: ____________________________uuu_u_u_uuuuuuuuuuuu_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Pins\PinOptionsParser.h +RES: ______________________________________u____c__cc__cc____________cc__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Configuration\Completer.cpp +RES: _____________u_uuu_uu_uuuuu____uu__u_uu_uuuu_u_u_____________uu_u_uu__uuuu_uuuuu_uuu_u_uu_uu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Configuration\Completer.h +RES: ____________________u_______uuuuuuuuu_u____ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\RelaySpindle.cpp +RES: _______________c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\SDCard.h +RES: ________________________________________________________________________uuuu___ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\NullSpindle.cpp +RES: _________________uuuuuuuuuuuu___c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\driver\uartdriver.cpp +RES: ________uuu_uu_uuuuuu_uuu_uuuuuu_uuuuuuuuuuu_uuuuuuuuuuuuuuuuuu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\RelaySpindle.h +RES: ______________________u____u____ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\SDCard.cpp +RES: _____________u_______uu_u_u__u_u___uuu___uu__uu___u_u_u__uuu__uu_uuu_uuu_uu_uuu_uuuu_u__uuuuuuu_u___uuu_u___u_u +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\WebUI\WifiServices.cpp +RES: _________c_cu_uuu____________________________________________________________________________________________________________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\YL620Spindle.cpp +RES: ____________________________________________________________________________u_uuu__uuu_uu_uu_uu_u__u_u____uu_uuuuuu_uuuu_uuuuu___uuu_____uuuuu_uuuuu___uuu____u_uu_u__uu________uuuu_u_uuu__uuuuu___uu_u_uuuu_uuu__uuuuu_____uu___c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\YL620Spindle.h +RES: __________uu_______u_uu__u_____ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\CoolantControl.cpp +RES: ______u__uuuu__uu_uu__uu_uu_uu___uu_uu___uu_uuuu__uuu_u___uuuu______uuu_uuu_uuuu_uuuuu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\freertos\FreeRTOS.h +RES: ____________________________uuu__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Serial.cpp +RES: ________________________________________________________________u_uuuuuu_u_____uu__uu_uuu_uu_uu_uu_uu_uu_u____u_uu_uu_uuu_u_uuu_u_uuu_u_uuu_u_uu_uu_uu_uu_uu_uuu_u_uuu_u_uuu_u_uuu_u_uu_u__________u__uuu_uu____u_uuuuu_uuuuuu_uuuuuuuu_uuuuuuuuuuuuu____u_uuu_u_uu_uuu_c_u_____uu_uuu_u_u_uu_uu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Serial.h +RES: ________________________________________________________________________c_________uuuu___u_______ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\ControlPin.h +RES: _____________u___uuu____u_____ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Control.cpp +RES: ________uuu_uuuuuuuuuu_uuuuuuuuuu_uu_u_uu_u__u___uu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\driver\dac.cpp +RES: ______uuu_uu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Pins\ErrorPinDetail.h +RES: _________________________u___ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Machine\LimitPin.cpp +RES: _____________uu_u_uuuu_uuuu_uuuu_uuu_____uuuu_uuuu___________uu__u_uuuuu_uu_uuu_uu__u_uuu_uuuuu_uu_uu____uuuu_u_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Pins\GPIOPinDetail.h +RES: ____________________________________c___ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Pins\PinCapabilities.h +RES: __________________c_____________________________cccc___c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Kinematics\Cartesian.cpp +RES: _____u_u_uu_u_u_u_uu_u_uu___c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Probe.cpp +RES: _________u__uu_uuu__u_uuu__uuu____uuu_u_uuuu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\Laser.cpp +RES: _______________uuu_uu_u__u_u_u_uu_u_u__uu___c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\Laser.h +RES: ____________________________uu_u_u___uuu_u__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Configuration\Generator.cpp +RES: ____________uuu_uuuuu_uuu_u_uuuu__uu_uuuuu__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\MyIOStream.h +RES: _________uuuu_cccc_cccc_cccc_cccc______uuuu_uuuu_uuuu_____u_u_________u__uuu_uuuu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Configuration\Generator.h +RES: ____________________u_uuuuuu_______uu____uuuu_uuuu_uuuuuuuuuuu_uu_uuuu_uu_uu_u__u_uu_uu_u__uu_uuuu_uuuuu_uuuuuuuuuuuuuuu_uuu__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\driver\ledc.cpp +RES: _________cuuuuu______u__c_uuuuuuuuuuuuuuuuuu_uu__uu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Logging.cpp +RES: ___________ccc_______ccc_cccc_ccc +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Jog.cpp +RES: _____________u__uuuu_u__uu__uuuuu__uu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\StackTrace\AssertionFailed.cpp +RES: __________________________________________c_cccc___ccccc_cc__cc__c___u__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Planner.cpp +RES: ________________________uuu_uu____________uuuu_uu__uuu_uuu__________________________________________________________________u_u_uu______u_uuu_uu_uuuuu_uu__uuuuuu__u___uuuuu___uu_uuu______uu_uuu_uuuu_uuuuuu__uuu_uu_u_u__uuu__uuu_uu_uuuu_uu__uuu___uuuuuuu_uu__uu_uu___u_uuuu__uu_u__uu__uuuuuuuuuu____u_uuuuuuu_____uuuu_uu___u____uuuu_uu_u____uu______uuu_uuuuuu___u__uuu______________________uuuuu_u_uuu_uuuuuu______uuuu_uu_uu_u_uu__u__uuuuuu___uuuuu_u___u_uuuu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\StartupLog.h +RES: _______________c__uuuu_u_u_u____ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\WebUI\WifiConfig.h +RES: __________________uuuuuuu_uu_______________________________________________________________________________________________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\VFDSpindle.h +RES: _________________________uuu_____________________________________uuu_u__uu______u______________uuuu_uuu_uu_u__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\BESCSpindle.cpp +RES: ____________________________uuuu__u__uuu_u_u____u__uu_uu_____u_uuu_uuu___uu__u_____u_uu__uu_u___p__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\WebUI\WebSettings.cpp +RES: ______________________________uu_uuuuu_______uuuuuu__uu_uu__u_uuuu_uu_u_uuuu__uuuuuuuu_u_u_uuu__uuu_uuu_uu__uu_uuu___u_u__uuu_uuuuu_uuuu_uuuuu_______________uuuuu_uuuu_uuuuuu_uuuuuuu__u_u_uuu_uuu_u__uu_uuuuu_uuu_uuuu__uuuu__uuu_uuu_uu_uuuu_uuu__uuuuu_uuu__uu___uuuuu_uuuu_uuuuuu_u_uuu_uuu_uu_uuuu_uuu_uuu_uuuuu_uuu_uuuu_uuuu_uuuuu_u_uuu_uuuuu_uuuu_uuu_uuuuu_uuu_uuuuu_uu_uuu_uuuuuuuuu_uu_u_uu_uu__uuuu_uuuu_uuuuuuuuuuuuuuuu_uuuu_uuuuuuuuuu_uuu_uuu_uu_u_uuu_uuu_uuu_uuuuu_uuu___uu__uu_uuu_uuuuuuuuu_uuuuuuuuuuu_u_uuu_u_____________________u_uu____uuuu_uuuuuuu_uuuuu_u_uuuuu_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Settings.h +RES: _______________________________________________________________________uuuuu_________u______u________u_________u___u_uu__u_________uu_uuuu_u________uu___u____u__________________________________________u________u_u_u_u_uu_u_u____________________________________u__c______________________________________u____________________________________u_______________uu______________uu___________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\WebUI\JSONEncoder.h +RES: _______________________________________u___________________________________________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Kinematics\Kinematics.h +RES: ___________________________________u______________u___________________uuu_____u____ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Machine\Macros.h +RES: ______________________uuu_uuu____uu_uu_uuu_uuu___uuu_____uuuuuuuu_u__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Motors\Dynamixel2.cpp +RES: ______________________________________uuu_uuuuu_uu___uu_u_u_uuu__uu__uuu_uu_u_uu_u_uu_uuuuuu_uuu__uu_u__uu_uu__u_uu_uuuu_uuu__u___uu_u_uuuuu_uuu__u_u___uuu__uu_uuuu_uu_u_____u_uu_u_u_uu__u_uu_u_u_u_uuu_u_uu_uuuuu_uu_uu_uuuuu__uuuu_uuuu_u____u_uuuuu_u_uu_uuu_uu_uu_uu_uu_uu_uu_u____u_u_u_u___u_uu__u__u_uuuuuu_u__________uu_uuu__uu_uu___u_uu_uuu__u__uuuuuuuuuuuuuuuu__uuuu_uu___p__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Pins\ExtPinDetail.cpp +RES: _______uu_uuuu_uu_uu_u__uuuu_uuuuu_uuuu_u___uuuu_u_u_uuuuuu__u__uuu_u_uuuuuuuu_uuuu_uu_uuu_u_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Machine\SPIBus.h +RES: ___________________________u__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\WebUI\InputBuffer.cpp +RES: _______c_c_uuuu_uuuu_u_u_u_uuuuu_uuuuuuu_uuu_uuuuu_uuuuuu_uuuuuu_uuuu_u_u__u_uuuu_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Machine\MachineConfig.cpp +RES: _________________________________uuuu_uuuuuuuuuuuuu_u_uuuuuuu_uu_uuuu__uu__uu__uu__uu__uu__uu__uu_____uu__uu__uu____u_uuuu_u_uu_u___u___uuuuuu__uuu__uu_uuu_uuuu__uuuuuu_uuuu_uuuu_uuuuu____uuu_u_u_u_u_uuuuu_u_uuuuu___u_uu__uuu_u_uuu_uuu_uuu_u_uu_cccccccccc_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Stepping.h +RES: _______________________u________________________u_uuuu_u__________________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\NullSpindle.h +RES: ______________________________u__u_u__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Configuration\HandlerBase.h +RES: _______________________________________uuuuu_uuuuu_______________pp_ppp_puu__p__uuu__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\StringRange.h +RES: ______________u_c_c____u__________uuuu_uuu_uu__uuuuuuuuuu__uu_uuuu_uu_uu______cccccc_c__cu_c_ccuucccccc_c_u_uuu_u_uuu_u_uuu_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\CoolantControl.h +RES: _____________u______uu___________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Probe.h +RES: ___________________u______u______u___________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Machine\MachineConfig.h +RES: __________________________________uu_____u__u_uuuuu__u_____ccccccccccccc_c_cccc_c_____c__c_ccc_u_uu______________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\MotionControl.cpp +RES: ______________________________uuu______________uu_u__uuu_________________uu___uuuuu_u__uuu_uuu_uuu_u____uuuu________________uuuuuuu_u_uuuu__uuuu_uuu_______u_u___uuu_u_uuuu__________________________uuu____uuuu_uuuuu__uuuuu__uuuuuuuuuuu_uu_u__uu__uuu_uuu____uu__u_____uu___u_uuu____uu_uu_______uuuu__uu__uuu__u__uuuu__uuuuu__uu_u_u_uuuuu_u_u___uuuuu_uu_uu_uuuu_u_uuuu_u___uuu_uuuuuu_uuu_uuuuu_u_u_uuu_uu_______u_uu_____u_uuu_uu_u_u_u +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\WebUI\JSONEncoder.cpp +RES: ________u________u______uuuu_uu____uuu_uu__uuuuu_____uuu_uu__u__uuuuuu_u__u___uuuu_u__uuuuu__uuuuuu__uuuuu___uuuuu__uuuuu__uuuu__uuuu__u___uuu_____uuuuu___uuu___uuuuu_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Motors\Dynamixel2.h +RES: _________________________________________________u_____________________________________uu_____u_________uuuuu_uu_uu_u_u_uuuu__u_u__u__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Pins\ExtPinDetail.h +RES: _______________u___________________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Main.cpp +RES: ______________________________uuu___u_u_u__u_uu_uu__u_u_uuu__uu_uu_uu__uu___uu__u_u_uuuu_u_u___uu_uuu__u________u_u_uuuu_uu___uuu_uu_uuu_u___________uuu_____u_uuuu_u___uuuuu_u_uu____u____________uuuu_uuu_u_u____________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Pins\VoidPinDetail.cpp +RES: _______cu_c_cc_ccuu_c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Pins\VoidPinDetail.h +RES: ________________________c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Limits.cpp +RES: __________________u________________u_____uuu_uuuuu_uu____uuu_uuuuuu_uu__uu____uu_uu_uuuuuuu_uu___uu_uuuu_u_uuuu_u_______________________uuuuu__uu_uuuuu__uu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Pins\PinCapabilities.cpp +RES: ______c__________ccccc_cccc_cccc_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Protocol.cpp +RES: __________________________________c_____________c_________________________________________uuu_uuuu_u_uuuuuuuuuuuuuuuu___u________u___uuu_uuuu___uuuu_uuuu__u_________uu_uuu__uuuuuu_u_uuuuu_u_uu___uu_uu____u_uu__uuuu_____________uuu_u_u___u__uuuuu_uu_______uuu_u____________uuuu_u_uuuu____uu_u___uuuu______uuu_uu__uu_uuuuu_u_uuu_u_uuuu_u__uu____u___u__uu__uu__uu_u_______uu_u_uuu_____u___u__uu__uu__uuu_uu_u____uuu_u___uu__u_uu_u_uuuuuu__uuuu_u_uu_uu_uu__u_uu___uu_uuu__uuu__uu___u__________uu_u_uu_u_uuuuuuuuu_u___uu____u_uuuu_____u__u_uu__uuuuu____________u_uu_uu_u_uu_u_uu___uu__uu__u_uu_u_u________u__uuu_uuu___________uuuuuu_uuuuuuu___u_u_uuuuuu___uuuu___uuu___uu_uuuuu______uuuuuu__uu_uuuu__u___u_uuu__u__u_u__u_uu_u_u_uuu__uu_uu_____________________________uuu_u_uu__uuu__uu__uu__uu__uu__uu__uu__uuu_uuu_uuu_uuu__u________u_____u_____u__u______u__u_uuuuuu_u___uuuuuuuu_uu__uuu____uu__u__u_uu_u__uuuu____u__uuuuuuu__uuuuuuuu_uuuu_u__uuu__uuuuu_uuuuuuuu__uuu___u__u_uuuu___u_uu_uuuu___u_uuu____u_u___uuuuu__uuu___u__u_uuuuuuuuu_uuuu_uuuu__uu_u_u__uuu____uuuu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Machine\Axes.h +RES: _______________u___u_____________u____u_______uuu_uuuu_uuuu_______________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\lineedit.cpp +RES: _____ccc___uuu_uuuuuuuu_u_uuuuuuuuu_uuuuuu_u_u_uuuuuuuuuuuu_u_uuuuuuu_u___uu_uuuuu__uuuuu_u____uu_uuuuu__uuuuuuuu_uuuuu_u_uuu_uuuu_uuu_u___uuuu__uu_uu_uu_uuu_u___u____u_uu_uu_uuuuuu_uuuu_uu_uu_uuuu_u_uuuu_u_uuu_u_uuuu_uuuu_uuuuu_uuuuuuuuuuuuuu_uuu___uuuu_uuuu_______u__uuu__uuu_uuuuu_uuuuuuu___uuuuuuuuuuuuuu_uuu_uuu_uuu__uuu_uu_uuuuuuu__uuuu_uuu_uuuuuuu_uuu__uuuu_uuuuuuuuuuuu_uuuuuuuuuu__ccccccc_uuu_uuuuuuuu_u___uuuuuu________uuu_u__u_u_uu_uu____uu_u__uu__uuuuu_uu_uuu___uuu________uuu_u_uu_uu_u__uu___uuu__uu_uu_uu___uu_uu_uu_uu_uu_uu_uu_u______uuuuuuu____uuuuu_u_uuu___u_uu__uu_u__uuu__uuu__uu__uuu_u__uuu__uu_uu_uu_uu__uuu__uuu_uuuuu__uu_uu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\lineedit.h +RES: ______________________________c__________c___c_c__c_______________________________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Configuration\Parser.cpp +RES: ______________c_u_uuuu_u_ccc_ccc_ccc_cc_c_uuuu_uu_uu__uu_uuu_uu_uu_uu_uu_uuu_uuuuu_uuuuu_uuuuuu_uuuu_uuuuu_uu_uuuuu_uuu_uuu_uuuu_u_u__uu__uu__uu_u___uuuuuuuuu__uu_u_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Kinematics\WallPlotter.cpp +RES: _____________________uuuu_uuu_uu_uu______uuuuu_u_uu_u_u___________u_____uuu___uuuuu__u_u__u_uu___uu_uuu____u_______________uuu__uuu_u__uu_______u___u__uuu__u_______________________uuu__uu_uu__uuu__uuu_u__uuu_uuuu___c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Motors\MotorDriver.h +RES: ______________________________________u____________u_____________________________u__________u_____u__u______u___________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Kinematics\WallPlotter.h +RES: ________________________________u_u__u_u_____________uuu_uuuu__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Kinematics\Midtbot.cpp +RES: _______u_uuuu___c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\H2ASpindle.h +RES: ________________u_uu__u_____ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Kinematics\Midtbot.h +RES: _______________________________u_u__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\PWMSpindle.cpp +RES: _____________________uuu_uuu__uuu__uu_u_u_uu_u_u_uuu__u__u_uuu_uuuu__uuu____uuuu_u__u_______uuuuu__uuu__uu___u_uuu___uu__u_uu______uuu____uuuuuu_uuu_uuuuuuu___c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Machine\Homing.cpp +RES: ________________________________________uuuuu_uuu_u_uuu___u__u_uu_u_u___u_uuuu_uuuuuuu_______u_uuuu_u___uuuuu_u__uuuuuuuuu_uu_uuuu_uu_u__uu__u_uu__u__u_uu__u_____u_u_u_u_uuu_u__u_u__u_uu_uuu___uuu_u__uuuuu__uu_u__uu___uuu_u__uuuuu___uu_u_uu___________u________uu__uuu_uuuu_uuu_uu_uuuuu_uuuu_u_uuuuuu__u___uu_uuuuuu__uuuu_uuu_uu__u__uuu_u_u__u___________uuu_uuuu__u_uuuuuuuu_uuu_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\PinMapper.h +RES: __________________________________c____ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\WebUI\Commands.cpp +RES: __________________________uuuu_uuu_uuuu___uu___uuuu_uuu____u____uu_uuu_u_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Motors\RcServo.cpp +RES: _________________________________uuuuu__u_uu_uu_u_uu_uuu_u_uu__uuu__u_uu_uuu_u__u_uu_uuu_u_uuu_uu_u_uuu_____u_uu__u____uu_uu_uu___c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\PinUsers\PwmPin.cpp +RES: _____________u_____uu_____________uu___uuu_uu_uu__uu_uu__u__uuu_u__uuuuu_u__u_____uu__uu_uu_u_uu____uuu_uu_uuuuu__uuu_uu_uu__uuuu_uu__uu__uu_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\PinUsers\PwmPin.h +RES: _________________u_____________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\Arduino.cpp +RES: ________uuu_uu_uuuu__u_ccc_ccc_cccc_cccc_cccc_ccc_uuu_uuu_uuuuuuuuuuuuuuu_uuu__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Kinematics\Cartesian.h +RES: ________________________________uuu__u__u__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\SoftwareGPIO.h +RES: ________c_________c_cccccccc_uuuuuu__uuuu_u_uuuuu_u_cccc_cc_c_cc_c_cc___c_c_____c___cc__cccc_ccccc_ccc_ccccc_cccuu__c_cc_cccuuuu_cc_c_c_ccc_cccc_ccccc_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\test\Pins\PinOptionParsing.cpp +RES: _______ccc__ccc___cuu_cuuc_cc_c_c__cccc_cc___cccccu_ccc_cc_c_c__cccc_cc___cccccu_ccc_cc_c_c__cccc_cc___cccccu_ccc_cc_c_c__cccc_ccc_cc___ccccuuuu_ccc_cc_c_c__cccc_ccc_cc___ccccuuuu_ccc_cc_c_c__ccccccc_cccccc_cc___ccccuuuu_ccc_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\test\TestFactory.cpp +RES: ______________________________________uu__uu____uu__uu_uuuu_u_uuu__u_u_uuu_u_uu_uu_uuuu_uu_uu_uu_uu__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\test\Pins\Undefined.cpp +RES: ______c__cc__ccc___ccc__pc_cccc_c_cc_cc__cc_cc__cc_cc__cc_ccc_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\test\Pins\ErrorPinTest.cpp +RES: ______c__c_cc_c_cc_pc_cc_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\test\Extender\I2CExtenderTests.cpp +RES: _________________u_____u_c_c__________cc___ccc__cc_cc__ccc_ccc__cc_cc_u__c_ccccccc__cccc_cuccccccu_cu_c__cccccccc_cc_c_ccc_ccc_c_cc_ccc__cc___c_ccc_c_cu_c_c_cccccc______c_cccccccc__c___c_ccccc_cc_c_cc_ccc__cc_cccc_ccccc______uu__c_uuuccc_cuccc_c_u_u_uccc_c__cc_c__cccccc__ccc_cc__cccccc_ccc__cccccc__ccc_cc__ccccc_cccccccccccccc_ccc__cccccc__ccc_cc__ccccc____cc__c__cc____cccc_____ccccc___ccccc___cc_cc___cccc_ccc_____cc__c_ccc____cc__c_cccc____cc_cc____cc_cc__cc___cc_cc____cc_cc_c_cccc__cccccc__ccc_cc__ccccc__ccc_c____ccc_____cccc___cccc___cc__c___cccc______cc__cc____cc__cc____cccc____cccc____cccc____ccc____ccc_c_c_cccc__cccccc__ccc_cc__ccccc__cc__c_c__c__c__c_c__c___ccc____cc__cc__ccc___c__c_c____cc__cc_c_cccc__cccccc__ccc_cc__ccccc__cc__c_c__c___c____cc____c_c__c__cc___c_c__c__cc___c__c_c____cc__c_c_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Machine\I2CBus.h +RES: ______________c____c__c_____________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.1.8.1.3\build\native\include\gtest\internal\gtest-port.h +RES: ________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________c___________cccc_c_c__________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\test\Configuration\YamlTreeBuilder.cpp +RES: ________________uccccc_______ucccc__________cc_______uccccc________uccccc____ccc_c____c__cc____cc_cuuu_cc___cc_cuuu_cc__cc_cuu_cc__cc_cuu_cc_________cc__ccuu___uuu_uu_cc_________cc__ccuu___uuu_uu_cc_________cc__ccuu___uuu_uu__c_ccccu__uuuuu_uuuuu_uuuuuu_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\NutsBolts.h +RES: ____________________________________________________________________________________________________________uuu____uuu_uuu_____uu___uu____uuu__uuu_uu_uu__uuuu___uuuuu_uu__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Configuration\ParserHandler.h +RES: _______________________cc___c______cc_______c__c____cc_c___cc__u__uu_cc_______c_________c____cc_c__c_ccuu_c_ccu_c_uuu_u_uuuu_u_uuu_u_uuu_u_ccc_c_uuuuuu_uuu_u_c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Machine\Axes.cpp +RES: _________________uuuuu_uu_uuu__uuuu__u__uuuuu_u_uu_uuuuu_uu_uuuu_uu___uuu_uuuuuuuu_u__u_uu_uuu_uu_____uu_uu_uuuu_uuu___uuu_uuuu__uuuu___uuu__uuuuuuuu_uu_uu_uuuuu___uuuuu_uu_uuu_uuuuuuu_u_u_uuu___uuu____u____uuuu_uuu_u_uuuu_u_uu__uuu_uu_uuuu_uu_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\StackTrace\AssertionFailed.h +RES: _____________________________c__u___ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\test\Configuration\YamlParser.cpp +RES: ______cc____cc_cc__c_cc__c_cc_ccc_cc_____cc_cc__c_c_ccc___c_cc_ccc_cc_______cc_cc__c_cc_cc___c_cc_cc___c_cc_ccc_cc__________cc_cc__c_cc__cc_cc_c_cc___c_cc_cc_c_cc____c_cc_ccc_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\H2ASpindle.cpp +RES: ____________________u_uuu_uuuuuu_u__uu_uuuuuu_uuuu__uuuuu___uu_uu__uu_u_uuuu_u_uuu__uuuuu___uuuuu_uuu__uuuuu_____uu___c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.1.8.1.3\build\native\include\gtest\gtest.h +RES: __________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________c_______c____________________________________________________________________________u__________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\test\Configuration\YamlComplete.cpp +RES: ______cc_____________________________________cc_ccc_ccc_ccc_ccc_ccc_ccc_ccc_ccc_ccc_cc_c_cccccuuuuuuuuu_uu_u_u_uuuuu_uu_u_uuu_uuu_uuu_uuuuuu__uu_u_uuu_uu_u_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Configuration\Tokenizer.h +RES: ________________ccc_cc_cccc_c_ccu_ccc_c_c_cccc_c_cccuuu_cccc__________c_____________c_____c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\WebUI\NotificationsService.h +RES: ____________u__________________________________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Pin.cpp +RES: _______________cc_cc__c____cu_c_uu__cccc_c_cc__cccccc___cuu_ccc_u_c_c________c__cc______cu__c_c__cuuuu_u___cuuu____c_u_uuu_cccccuu__uuuc____u_u______uc_u__uuu__uu_uuu_u_ccc_ccc_ccc_c +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\BESCSpindle.h +RES: ________________________________u_______uu_______________u_uu_uuu_u__u_u__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\GCode.cpp +RES: ________________________uuuu_____________u_u__uuuu___uuu_u_uuuuuuuuu_u___u_u__u_uuu_u_u_uuu__u__uu___uu_______u__u_uu__u_u_u_uu_uuu_uu_______u_u__________uuu_u_uu_uuuu____u_uuuuu_________u___uuuuuu_u_uuu_uuu________uu___u____u__uuuu_u_uu__uu_uu_u___u_uu_uu_uu_uu___uu_uuu_uuu___uuuu_uuuu_uuuu_uuuu__uuu___uu_uu_uu_uu_uu_uu_u__uuu__uuu_uuu_uuu_uuu_u_uuu_u__u_u__u_u_uuu_u__uu_u__u_uuu_uuu_uuu_uuu____uu_____uu__uuuuuuu_uuu_uuu_uuu_uuu_uuu_uuu_uuu__uu__uu_u_uu___uuu_uu__uu_u__uuu__uu__uuu__uuu___u_uu_uuuu_u_u__uu_u_uu___u_uu_u_uu_u_uu___uu_uuuuu_u_uuu_uuu_uuu_uuu_uuu_uuu_u___uuu_uu______u_uuuuuu_u_uuuuuu_u_uuuuuu_u__uu_u_uuu__uuuu_uuuu_uuuu_uuu_uuu_uuu_uu_u_uuu_uuu_uuu_uuu_uuuu_uu_u_uuuuuu_u_uuuuuu_u_u__uuu___u_uu__u_u____________________________uuu___u_uu_____________uuu_uu_uu_uuuu______________u_uuuu_uu______uu_________uuuuu_u____uuu_u__uuu_u_uuu_uu__u_uuuu_uuuu_uuu____uuuu_u___________uuuu_________uu__uu_uu__________u____uu_uu_uuuu_uu___uu_uu_u_uu_uu__u_uu__uuu_u_u__uu__uu___uu_uuu_uu_uu_____uuuuuu__u_uuuu_uu___u___u____uuuu_u_uuu_uuu_u____u__u___uu_______u__uuu__uu__uu___u_uu_u_u_u___uu_u_u_______uu_uu___uuuuuu__uu__________________________________________________uuu__u_uu____________________uuu__uuuuu_u_uuuu_u__uuu_u_uuuu_uu___u__u__uu_____uu_uu________u_uuu___uu___uu________uu____u__uu_uu__uuuuuuu__u__uu_uu____uuu__uu_uu_u_uu_______uu___uuu__uu_uuuuuuuuu_u___u__uu____uu__u___uuuu_u_u______uu_u_uu_uu_u__uuuu___u__uuuu_uuu_uu__uuuuuu_uu_uu_uu____uuuu____uu__u_u______uuuu__uuu___uuuu____u__u_u_uuu_u____uuu_uuu_uu_uu_uuu_uu_______uuuuuuuuuuu________u__uu_u____uuuu______uu_u___u_uuuu_u__u_____uuuu_uuuuuuuu____uuuu___uuuuuu_uu__u__uu__________________________u_uuuu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Machine\I2SOBus.cpp +RES: ________uuuuu_u_uuuuu_uuuu_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Motors\UnipolarMotor.h +RES: ______________u_____uuuuuu_uuuuuuu__u______uuuu_____ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Motors\UnipolarMotor.cpp +RES: _______uuuuuuuu_uu_u_uuuuuu_uu_u_uu__uu__u_uuuu_______________uu_uu_uuu_uu_uuu_uu_uuu_uu_uu__uu_uuu_uuu_uuu_uu____uuuuuu___c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Channel.cpp +RES: _______uuuuuu_uuu_uu_______uuuu________uuuu_uu_uuuu_u_uu_u_uu______uuuuu_uu____uuuuu_u__u +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Pins\PinAttributes.cpp +RES: _______cc______ccccc_ccc_ccc__c_c_c_cu___cu___cu___cc_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Configuration\Tokenizer.cpp +RES: ___________ccuuc_c_cuuuuu_c_u_c_cc___c___c____c_ccccc_ccccc_c_uu__uuuuu__uuu_u__cc__cu__cccccc__cuu_cu_c__ccc__c_____cccccc_cccccccu_c____cccccccu______c___c_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Pins\PinDetail.h +RES: ________________________c______________________c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Configuration\ParseException.h +RES: ___________________uuuuuuu_uuuu_uu_u____ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Machine\UserOutputs.cpp +RES: ___________uuuuuu_uuuuuuu_u_u_u_uu_uuuuu_uuuuu_uu_uuuuuu_uuuu_uuu_uu__uu__u_uuuu__uu__u_u_uu_uuuuuuuuuuuuuu_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Stepper.cpp +RES: ___________________________________________________________________uuu_uuu_uu____________________________________________________________________________________________________________________u_uuuu________uu_u__u_u_u_________uu__uuu____u_____uu__________u_uuu_uu_uu_uu__uu____uu_uu___u_u_uuuuuuuu__u_uu_uu__uu__u__u_u__uu_uuuu__u_u_u__uuuuuuuuu_u__uuuuu_u__u_uuuuu__uuuu__u_uuuuuuuuuuu__uu__uuuu______________u_uu__u_u_uuuu__uu___uuuuu_u_u___uu_u____uu____u________u__uuuuu_uuuuu___u_uu_uu_________uuu__u_uu_uuuu_u_uu__uuuuu__uuuuuuu___uu___u_uuuuuu_uuuu_uu_u_uuuu_uu___uu_u___u___u______u_______________uuu__uu_uu___u_uuuu_uuuuuu_u__uuu_uuuuuu_uuu_u____uu_uuuuu_u__uu_uuuuu___uuu__uuuuu__uuuu__u____uuu_uu______uuuu_u_uu___________uuuu__uu__uuu_u____________u_u____u___uuu_uuuu__u__uuu__uuu_u_u___uuu_uu_uuu_uu__uu_____uu_____u_u_u +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Configuration\Validator.cpp +RES: _____________u_uu_uu_uu__u__uu_u_uu_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Configuration\Validator.h +RES: _____________________uu____uuuuuuuuu__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Error.cpp +RES: _______c____________________________________________________________________c +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Extenders\I2CExtender.h +RES: ________________________________________________________________________________________c____c___cc___c___c__ccccccc__________________c________________________________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Uart.cpp +RES: _____________________c___cuuuc_cc__uuuuu_uuu__u_u__uuu_u__uuuuuuuuu_uu_uuuuu_uuuu_uuuuu__uuuuuu_uuu_u__uu_u_uuuuu____uu_uuu_uuuuuuu_u__u_uuu_uuuuuu_u__uuuuuuuu_u_uuuuuu__uuuuu_uuuu_uuuu_uu_____uuuuuuuuu_c_uuuuu_uuu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\PinMapper.cpp +RES: _____________________________________ccccc_ccccc_cuc_c_cccc_____u_cc__cc__uuu_u_uuuu_u_uu__ccc_c__ccu_ccc_c_ccuu__ccu__ccc_cc_cu_cu__cc_ccu_ccc +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Configuration\RuntimeSetting.cpp +RES: ___________u_u__u_uu_uuu__uu__uuu__uuuuuuuuu____u_u_uuuuuuuu__u_uuuuuuu__u_uuuuuu_u__u_uuuuuuu__u_uuuuuuuu_u_uuuuuuuuu_u_uuuu_u_uuuuu___u_uuuuuuuuuu_uu____uuu_uuuuuuuu_uuuuuuuuu_u_uuuuuuuuu_uu_u_uuuuuuu____u_uuu_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Configuration\RuntimeSetting.h +RES: __________________uuuuuuu_u___u________u_____u_u____ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\NutsBolts.cpp +RES: _______________________uu__u_uuuuuu___uuuuuuuuuuu_uuuu_uuuuu_uu_uu____u__uuuuuuuu_uu___uuuu_uuu_uuu_uuu__uuuuuu_uuu__uu_uuuu__uuu_uuuuuu_uuuuuuuu___uuuuuuu_u____uu_uuuuuuu_uuu_uuu_u__uuuuu__uuuu_uuu_uuu_uuuu_uuu_uuu_uuu_uu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\InputFile.h +RES: _____________________________________________________u___u______ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\HuanyangSpindle.cpp +RES: _______________________________________________________________________________________________________________________________________________u__u_u_uu__uu_u_uu_uu_u__u_u____uu______________________uu__uuuuu__u_uu__uu_uu_u_u_uu__uuuuu_u_uu__uu_u____uuu_u_uu__uu_u_______u_uuu_uu_uuu_u__u_u_u_uuuu_uu_u_uu_uuuuu_u_uu_uuuu______uu_u_______uu_u_uuu_uuu_u_uu__uuuuu_uuuu_uu_u_uu__uuuuu_uu__uuuu___c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\HuanyangSpindle.h +RES: ______________uuuu__________u__u_____ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Pins\PinDetail.cpp +RES: _________ccucc_uc__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\DacSpindle.cpp +RES: _________________uuu__u_uuuu__u_u_uu_u_uu_uuu_uuuu_u_u___c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\DacSpindle.h +RES: _____________________________________u_u________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Kinematics\CoreXY.cpp +RES: _____________________________u_u__uuuuu_uu__u_uuuuuuuuuu___uu__u__uuuuuu_u___u_u__uuuuuuuuuu_u_uuuu______u_uu__u_____uu_uu_u_uuu_u_u__u_uu_uuu_u_uuu__uuu__uuu_u_uu____uuu_uuuu_u__uu__uu_uu__uuuu_uuuuuu_uuuuuuu__u_u_u__uu_uuu_u__u__uuu_u_uu_uuu__u_uu_uu_u_u___________u_____uuuu_u__u_u_uu__u__uu_______u__uu_uuuu____uuu_uuuuu___uu_u___p__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Kinematics\CoreXY.h +RES: ____________________________________u_u__u_u___________u__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Stepping.cpp +RES: __________ccccc_uu_____uu__uu_____uu_uuu_uuuuu_uuuu__uu_u_u_uuu_u__uu__u_uu_uuu_u__u__u_u_uuuuuu_u__uuu_u___uu___uuu_u_uuuuuu_u_uuuuu_u_____________u___u_u______u_uu_uuuuuuuu_uuuuuu__u_uu__u_u__u_u_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\xtensa\core-macros.h +RES: ____uuu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\OnOffSpindle.cpp +RES: _______uuuu__uuu_u_u___u_uuu__uuu_uuu____uuuu_u__u_uuuu_u_u_uuu__uu_u_uuuuuu___c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Kinematics\Kinematics.cpp +RES: __________uuuu_uuuu_uuuu_uuuu_uuuu_uuu_u_uuuu_u_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\InputFile.cpp +RES: _________u______uuu_uuu_uu_uu_uuuuu__uuu_u__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Motors\Servo.cpp +RES: __________________________uuuu_uuu__uu________u_u_uu_uuuu_uuu_u_____uu_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Motors\Servo.h +RES: ________________u__________u_______________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\ProcessSettings.cpp +RES: _________________________________uuu_u_u_uu_u_u_u___uu_uu_uu_uu_uu_uu_u__uuu____uu_u_uuuuu__uuu_uuuu_u_uuu_uu_uuu_uuuu_uu_uuu__uuuuuuuuu__uu_uuuuuu_uu__uuuuu________uuuuu_uuuu_uuuu_uuu__u_uuuuuuuuuuu_uuuuuuuu_uuuuuuuuu__uuuuuuuuuuuu_uuu_uuuuuuu_____uuuuuuu_uu_uuu_uuu_uuuu_uuuuu_uuuu_uu__uuuuuuuu_uuuuuuu_u__uu_uu__uu__u_u_u_u_uuuuu__uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu_uuuuuuuu_uuuuuuuuuuuuuuu_uuuuuuuu_c____cuuu_uuu_uuu_u_uuu_uuu_uuu______uu__uuuu_uuuu_uuuuu_uuuuuu_uuuuuuu___uuuuu_uuuu_uuuuuuu_uuuuuuu___uuuuu_uuu__uuuuuuu__u_uuu__uu_uuu_uuu_uuu__uu_uuuuuuuuuu_uuu_uuu__uu_uuuuuuuuuu_uu_u_uu_uuu_uuuuuuu_uu_uuu_uu_uuuu_uuuu______uuuuuuuu_uuuuuuuuuuuuuuuu_uuuuuu_uuuu_u_uu______u_____uuu__uu_____u___u_uu___u________uuu_uu_uuuuuuuu_uuuuu_uuu_uuu___uuuu_uuuuu__u___uuuu_uuuuu__u___uuuu_u_u_____uuuuuuuu_uuuu_uuu_uuu_uu__uuu_u__u_uu_u_uu_u___u______uu_u_uuuu___uuuu_uu_uu_uu__uu__uu_uu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Configuration\AfterParse.h +RES: _____________________uu____uuuuuuuuu__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Machine\Axis.cpp +RES: _______uuuuuuu__uu_uuuuuu_uuuuu_uuuuuu_uuuu____uuu_u_uuuuuuu__uuuuu_uuu__u__uuuuuu_uuu___u_uuuu_uu_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\FileStream.h +RES: ___________________u_______uuuuu_______u_________u___ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Machine\Motor.cpp +RES: ____________uuuuuuuuuuu_uuu_u_uuu_u_uuuu_uuu_u__u__uuuuu_u_u_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Motors\NullMotor.h +RES: _________u_u__u_u__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Configuration\JsonGenerator.cpp +RES: ____________uu_uuuu_uuuuuuuuuuu_uuu_u_uuuuu_uuuuu_uuuuu_uuuu_uuuu_uu_uuuuu_uu_uuuu_uuuu_uu_u_uuuuuu_u_________u_uuuuuu_uuu_uu_uu_u_uuuuuuuuuuu_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Configuration\JsonGenerator.h +RES: _______________________________uu_______________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Settings.cpp +RES: _____________uuuuuuuuuuuu__u_____uuuuu_____uuuu___uuuu_uuuuuuu_u_uuu_uu_uu___uuuu__u___________uuuu_uuuuuuu_u_uuuuuuu__u_uuuuu__uuu_uu___uu__uuuuuu_u__uuu_u_uuu_u___uuuuu_uu__uuu_uuuu_u__________uuuuuu_uuuuuuu___uuuuuuuu_uuu_uuuu_u_uuuu_uuu_uuuuuuuu_u__uuu_uuu_uu_u_uuuuu_uuu_uuu___________uu_uuuuuuu_u_uuuu_u_____uuuuu_uu___uuu__u_uuu_uuu_uuu__uuuuuuu_u__uuu_uuuuuuuuuuuuuuu_uuuuuuu_uuu_uuuuuuuuuu_uuu_uu__u_u_u_______u___u_u_uuuu_uu________u_u________uuuuuuu_u_uuuuuuu_u_uuuu_u_uuuuu_uuu_uuuuuuu_u__uuu_uuuuuuuuuu_uuuu_u +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Machine\Homing.h +RES: _________________________________uuuuuuuuu__u_uuuuuuuuuuu_u___ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Machine\Motor.h +RES: ___________________________u_uu____u____________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\ControlPin.cpp +RES: _______uuuuu_u_uuu_uuuu_uuuu_uuu_u_uuu_uuu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\FileStream.cpp +RES: _________uuuu_uuuuuuu_uuu_uuu_uuu_u_uu_u_uu_u_uuuu_uu_u__uuu_uuuuuuu__u__uu___u__uuuu_u_uu_uuuu_u_u_uuuu_u +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Machine\Axis.h +RES: ____________________uuuuu____u_uuuuu_________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\EnumItem.h +RES: _____________c__c____ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\SettingsDefinitions.cpp +RES: ___________c________c_c_uuuuuu_u_uu___uuuuuuuu_u_u__u_uu +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\PWMSpindle.h +RES: __________________________________u_u____________u_uu__u_u________u________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Spindles\OnOffSpindle.h +RES: ____________________uuuuu_uu____________________u_uuuu_u__u______u_u_____ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\System.cpp +RES: ____________________u_uuuuuuuuuu_uuuuuu_u_uuuuuuu_u_uuu_c__________c +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\X86TestSupport\TestSupport\Stream.h +RES: ___________________________________________________cc___________________________________u_____________________ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\StartupLog.cpp +RES: __uuuuuuuu_c +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Machine\I2CBus.cpp +RES: _________ccccc_c_uuuuuu_ccc_c_cccu_c_cc_uu_u_u_u_u_u_u_u_u_u_u_uc___cccccc_c___ccc_cccc__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Extenders\I2CExtender.cpp +RES: _______________c_c_cc_____cu_uuuccu__uuc_c_ccc__cc_c_cuuuc_c_ccuuu____ccc_cc_c_ccccc__c__c__c_cc_cccc___c_c____cccc_c_ccc_ccc_ccc_c___cc_ccc_ccc_c___ccc_cc__c___ccc_c_ccccc__ccc_c___ccc__c_ccccu__c_cccccccccc__c__ccc_c____c_ccc_c_cc_cc_cc_cccc_ccc__cc_c___cccc_cccc_ccc_c__ccccccc__u____c_c________ccc_c_cc_c_c_cc_cccuccc_cu___cc_____ccc_cc_cccccc___ccc___c_cc______cc_ccc__cc_cc_cc_cccc_ccc_ccc__ccc___ccc_cc_ccc__cccccu_c_c__ccc_u_c_c__cc___ccc__cc___c_u_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Pins\PinAttributes.h +RES: ______________________c___________________cc___c___c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Extenders\PinExtenderDriver.h +RES: ______________________________c__ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Extenders\Extenders.cpp +RES: ______p_uuu_uuuuu_u_u_uuuuu_u_uu_uu_uuuuuuu_uuuu_uu_uuuuu_ +PROF: +FILE: C:\Users\atlas\Desktop\Opensource\atlaste\FluidNC\FluidNC\src\Configuration\Configurable.h +RES: _____________________u_u__c__ +PROF: diff --git a/FluidNC/src/ControlPin.cpp b/FluidNC/src/ControlPin.cpp index bdb47127b..5acfe7c7d 100644 --- a/FluidNC/src/ControlPin.cpp +++ b/FluidNC/src/ControlPin.cpp @@ -33,7 +33,16 @@ void ControlPin::init() { _pin.setAttr(attr); _pin.attachInterrupt(ISRHandler, CHANGE, this); _rtVariable = false; - _value = _pin.read(); + _value = _pin.read(); + // Control pins must start in inactive state + if (_value) { + log_error(_legend << " pin is active at startup"); + rtAlarm = ExecAlarm::ControlPin; + } +} + +String ControlPin::report() { + return get() ? String(_letter) : String(""); } ControlPin::~ControlPin() { diff --git a/FluidNC/src/ControlPin.h b/FluidNC/src/ControlPin.h index e5693d651..4a98e5ece 100644 --- a/FluidNC/src/ControlPin.h +++ b/FluidNC/src/ControlPin.h @@ -9,11 +9,10 @@ class ControlPin { volatile bool& _rtVariable; // The variable that is set when the pin is asserted int32_t _debounceEnd = 0; - void IRAM_ATTR handleISR(); - CreateISRHandlerFor(ControlPin, handleISR); - // Interval during which we ignore repeated control pin activations const int debounceUs = 10000; // 10000 us = 10 ms + void IRAM_ATTR handleISR(); + CreateISRHandlerFor(ControlPin, handleISR); public: const char* _legend; // The name that appears in init() messages and the name of the configuration item @@ -29,5 +28,7 @@ class ControlPin { void init(); bool get() { return _value; } + String report(); + ~ControlPin(); }; diff --git a/FluidNC/src/Extenders/Extenders.cpp b/FluidNC/src/Extenders/Extenders.cpp new file mode 100644 index 000000000..d2b46821f --- /dev/null +++ b/FluidNC/src/Extenders/Extenders.cpp @@ -0,0 +1,58 @@ +// Copyright (c) 2021 - Stefan de Bruijn +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +#include "Extenders.h" + +namespace Extenders { + PinExtender::PinExtender() : _driver(nullptr) {} + + void PinExtender::validate() const { + if (_driver) { + _driver->validate(); + } + } + void PinExtender::group(Configuration::HandlerBase& handler) { PinExtenderFactory::factory(handler, _driver); } + void PinExtender::init() { + if (_driver) { + _driver->init(); + } + } + + PinExtender::~PinExtender() { delete _driver; } + + Extenders::Extenders() { + for (int i = 0; i < 16; ++i) { + _pinDrivers[i] = nullptr; + } + } + + void Extenders::validate() const {} + + void Extenders::group(Configuration::HandlerBase& handler) { + for (int i = 0; i < 10; ++i) { + char tmp[11 + 3]; + tmp[0] = 0; + strcat(tmp, "pinextender"); + + for (size_t i = 0; i < 10; ++i) { + tmp[11] = char(i + '0'); + tmp[12] = '\0'; + handler.section(tmp, _pinDrivers[i]); + } + } + } + + void Extenders::init() { + for (int i = 0; i < 16; ++i) { + if (_pinDrivers[i] != nullptr) { + _pinDrivers[i]->init(); + } + } + } + + Extenders::~Extenders() { + for (int i = 0; i < 16; ++i) { + delete _pinDrivers[i]; + } + } +} diff --git a/FluidNC/src/Extenders/Extenders.h b/FluidNC/src/Extenders/Extenders.h new file mode 100644 index 000000000..3d83af476 --- /dev/null +++ b/FluidNC/src/Extenders/Extenders.h @@ -0,0 +1,25 @@ +// Copyright (c) 2021 - Stefan de Bruijn +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +#pragma once + +#include "../Configuration/Configurable.h" +#include "../Configuration/GenericFactory.h" +#include "PinExtender.h" + +namespace Extenders { + class Extenders : public Configuration::Configurable { + public: + Extenders(); + + PinExtender* _pinDrivers[16]; + + void validate() const override; + void group(Configuration::HandlerBase& handler) override; + void init(); + + ~Extenders(); + }; + + using PinExtenderFactory = Configuration::GenericFactory; +} diff --git a/FluidNC/src/Extenders/I2CExtender.cpp b/FluidNC/src/Extenders/I2CExtender.cpp new file mode 100644 index 000000000..cc136c0e0 --- /dev/null +++ b/FluidNC/src/Extenders/I2CExtender.cpp @@ -0,0 +1,484 @@ +// Copyright (c) 2021 - Stefan de Bruijn +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +#include "I2CExtender.h" + +#include "Extenders.h" +#include "../Logging.h" +#include "../Assert.h" +#include "../Machine/I2CBus.h" + +#include +#include +#include + +namespace Extenders { + EnumItem i2cDevice[] = { { int(I2CExtenderDevice::PCA9539), "pca9539" }, + { int(I2CExtenderDevice::PCA9555), "pca9555" }, + EnumItem(int(I2CExtenderDevice::Unknown)) }; + + I2CExtender::I2CExtender() : _i2cBus(nullptr), _usedIORegisters(0), _dirtyWriteBuffer(0), _dirtyWrite(0), _status(0) {} + + uint8_t I2CExtender::I2CGetValue(uint8_t address, uint8_t reg) { + auto bus = _i2cBus; + + int err; + if ((err = bus->write(address, ®, 1)) != 0) { + log_warn("Cannot read from I2C bus: " << Machine::I2CBus::ErrorDescription(err)); + + IOError(); + return 0; + } else { + uint8_t result = 0; + if (bus->read(address, &result, 1) != 1) { + log_warn("Cannot read from I2C bus: " + << "no response"); + + IOError(); + } else { + // This log line will probably generate a stack overflow and way too much data. Use with care: + // log_info("Request address: " << int(address) << ", reg: " << int(reg) << " gives: " << int(result)); + errorCount = 0; + } + return result; + } + } + + void I2CExtender::I2CSetValue(uint8_t address, uint8_t reg, uint8_t value) { + auto bus = _i2cBus; + + uint8_t data[2]; + data[0] = reg; + data[1] = uint8_t(value); + + int err = bus->write(address, data, 2); + + if (err) { + log_warn("Cannot write to I2C bus: " << Machine::I2CBus::ErrorDescription(err)); + IOError(); + } else { + // This log line will probably generate a stack overflow and way too much data. Use with care: + // log_info("Set address: " << int(address) << ", reg: " << int(reg) << " to: " << int(value)); + errorCount = 0; + } + } + + void I2CExtender::IOError() { + if (errorCount != 0) { + delay(errorCount * 10); + if (errorCount < 50) { + ++errorCount; + } + } + + // If an I/O error occurred, the best we can do is just reset the whole thing, and get it over with: + _status |= 1; + if (_outputReg != 0xFF) { + _status |= 4; // writes + } + if (_inputReg != 0xFF) { + _status |= 8; // reads + } + } + + void I2CExtender::isrTaskLoopDetail() { + std::atomic_thread_fence(std::memory_order::memory_order_seq_cst); + int registersPerDevice = _ports / 8; + int claimedValues = 0; + uint8_t commonStatus = _operation; + + // Update everything the first operation + IOError(); + + // Main loop for I2C handling: + while (true) { + // If we set it to 0, we don't know if we can use the read data. 0x10 locks the status until we're done + // reading + uint8_t newStatus = 0x10; + + newStatus = _status.exchange(newStatus); + newStatus |= commonStatus; + + if (newStatus != 0) { + if ((newStatus & 2) != 0) { + _status = 0; + return; // Stop running + } + + // Update config: + if ((newStatus & 1) != 0) { + // First fence! + std::atomic_thread_fence(std::memory_order::memory_order_seq_cst); + + // Configuration dirty. Update _configuration and _invert. + // + // First check how many u8's are claimed: + claimedValues = 0; + for (int i = 0; i < 8; ++i) { + if (_claimed.bytes[i] != 0) { + claimedValues = i + 1; + } + } + // Invert: + if (_invertReg != 0xFF) { + uint8_t currentRegister = _invertReg; + uint8_t address = _address; + + for (int i = 0; i < claimedValues; ++i) { + uint8_t by = _invert.bytes[i]; + I2CSetValue(address, currentRegister, by); + + currentRegister++; + if (currentRegister == registersPerDevice + _invertReg) { + ++address; + } + } + } + // Configuration: + { + uint8_t currentRegister = _operationReg; + uint8_t address = _address; + + for (int i = 0; i < claimedValues; ++i) { + uint8_t by = _configuration.bytes[i]; + I2CSetValue(address, currentRegister, by); + + currentRegister++; + if (currentRegister == registersPerDevice + _operationReg) { + ++address; + } + } + } + + // Configuration changed. Writes and reads must be updated. + if (_outputReg != 0xFF) { + newStatus |= 4; // writes + _dirtyWrite = 0xFF; // everything is dirty. + } + if (_inputReg != 0xFF) { + newStatus |= 8; // reads + } + + commonStatus = _operation; + } + + // Handle writes: + if ((newStatus & 4) != 0) { + uint8_t currentRegister = _outputReg; + uint8_t address = _address; + + bool handleInvertSoftware = (_invertReg == 0xFF); + + auto toWrite = _dirtyWrite.exchange(0); + for (int i = 0; i < claimedValues; ++i) { + if ((toWrite & (1 << i)) != 0) { + uint8_t by = handleInvertSoftware ? (_output.bytes[i] ^ _invert.bytes[i]) : _output.bytes[i]; + I2CSetValue(address, currentRegister, by); + } + + currentRegister++; + if (currentRegister == registersPerDevice + _outputReg) { + ++address; + } + } + } + + // Handle reads: + if ((newStatus & 8) != 0) { + uint8_t currentRegister = _inputReg; + uint8_t address = _address; + + // If we don't have an ISR, we must update everything. Otherwise, we can cherry pick: + bool handleInvertSoftware = (_invertReg == 0xFF); + + uint8_t newBytes[8]; + for (int i = 0; i < claimedValues; ++i) { + auto newByte = I2CGetValue(address, currentRegister); + if (handleInvertSoftware) { + newByte ^= _invert.bytes[i]; + } + newBytes[i] = newByte; + + currentRegister++; + if (currentRegister == registersPerDevice + _inputReg) { + ++address; + } + } + + // Remove the busy flag, keep the rest. If we don't do that here, we + // end up with a race condition if we use _status in the ISR. + _status &= ~0x10; + + for (int i = 0; i < claimedValues; ++i) { + auto oldByte = _input.bytes[i]; + auto newByte = newBytes[i]; + + if (oldByte != newByte) { + // Handle ISR's: + _input.bytes[i] = newByte; + int offset = i * 8; + for (int j = 0; j < 8; ++j) { + auto isr = _isrData[offset + j]; + if (isr.defined()) { + auto mask = uint8_t(1 << j); + auto o = (oldByte & mask); + auto n = (newByte & mask); + if (o != n) { + isr.callback(isr.data); // bug; race condition + } + } + } + } + } + } + } + + // Remove the busy flag, keep the rest. + _status &= ~0x10; + + vTaskDelay(TaskDelayBetweenIterations); + } + } + + void I2CExtender::isrTaskLoop(void* arg) { static_cast(arg)->isrTaskLoopDetail(); } + + void I2CExtender::claim(pinnum_t index) { + Assert(index >= 0 && index < 64, "I2CExtender IO index should be [0-63]; %d is out of range", index); + + uint64_t mask = uint64_t(1) << index; + Assert((_claimed.value & mask) == 0, "I2CExtender IO port %d is already used.", index); + + _claimed.value |= mask; + } + + void I2CExtender::free(pinnum_t index) { + uint64_t mask = uint64_t(1) << index; + _claimed.value &= ~mask; + } + + void I2CExtender::validate() const { + auto i2c = config->_i2c; + Assert(i2c != nullptr, "I2CExtender works through I2C, but I2C is not configured."); + + // We cannot validate _i2cBus, because that's initialized during `init`. + Assert(_device != int(I2CExtenderDevice::Unknown), "I2C device type is unknown. Cannot continue initializing extender."); + } + + void I2CExtender::group(Configuration::HandlerBase& handler) { + // device: pca9539 + // device_id: 0 + // interrupt: gpio.36 + handler.item("device", _device, i2cDevice); + handler.item("device_id", _deviceId); + handler.item("interrupt", _interruptPin); + } + + void I2CExtender::interruptHandler(void* arg) { + auto ext = static_cast(arg); + ext->_status |= 8; + } + + void I2CExtender::init() { + Assert(_isrHandler == nullptr, "Init has already been called on I2C extender."); + this->_i2cBus = config->_i2c; + + switch (I2CExtenderDevice(_device)) { + case I2CExtenderDevice::PCA9539: + // See data sheet page 7+: + _address = 0x74 + _deviceId; + _ports = 16; + _inputReg = 0; + _outputReg = 2; + _invertReg = 4; + _operationReg = 6; + break; + + case I2CExtenderDevice::PCA9555: + // See data sheet page 7+: + _address = 0x20 + _deviceId; + _ports = 16; + _inputReg = 0; + _outputReg = 2; + _invertReg = 4; + _operationReg = 6; + break; + + default: + Assert(false, "Pin extender device is not supported!"); + break; + } + + // Ensure data is available: + std::atomic_thread_fence(std::memory_order::memory_order_seq_cst); + + xTaskCreatePinnedToCore(isrTaskLoop, // task + "i2cHandler", // name for task + configMINIMAL_STACK_SIZE + 512 + 2048, // size of task stack + this, // parameters + 1, // priority + &_isrHandler, + SUPPORT_TASK_CORE // core + ); + + if (_interruptPin.defined()) { + _interruptPin.setAttr(Pin::Attr::ISR | Pin::Attr::Input); + _interruptPin.attachInterrupt(interruptHandler, FALLING, this); + } + } + + void IRAM_ATTR I2CExtender::setupPin(pinnum_t index, Pins::PinAttributes attr) { + Assert(index < 64 && index >= 0, "Pin index out of range"); + + _usedIORegisters |= uint8_t(1 << (index / 8)); + + uint64_t mask = 1ull << index; + + if (attr.has(Pins::PinAttributes::Input)) { + _configuration.value |= mask; + + if (attr.has(Pins::PinAttributes::PullUp)) { + _output.value |= mask; + } else if (attr.has(Pins::PinAttributes::PullDown)) { + _output.value &= ~mask; + } + } else if (attr.has(Pins::PinAttributes::Output)) { + _configuration.value &= ~mask; + + if (attr.has(Pins::PinAttributes::InitialOn)) { + _output.value |= mask; + } + } + + if (attr.has(Pins::PinAttributes::ActiveLow)) { + _invert.value |= mask; + } + + // Ignore the ISR flag. ISR is fine. + + // Trigger an update: + std::atomic_thread_fence(std::memory_order::memory_order_seq_cst); + _status |= 1; + } + + void IRAM_ATTR I2CExtender::writePin(pinnum_t index, bool high) { + Assert(index < 64 && index >= 0, "Pin index out of range"); + + uint64_t mask = 1ull << index; + auto oldValue = _output.value; + if (high) { + _output.value |= mask; + } else { + _output.value &= ~mask; + } + + // Did something change? + if (oldValue != _output.value) { + uint8_t dirtyMask = uint8_t(1 << (index / 8)); + _dirtyWriteBuffer |= dirtyMask; + } + + // Note that _status is *not* updated! flushWrites takes care of this! + } + + bool IRAM_ATTR I2CExtender::readPin(pinnum_t index) { + Assert(index < 64 && index >= 0, "Pin index out of range"); + + // There are two possibilities here: + // 1. We use an ISR, and that we can just use the information as-is as long as it's in sync. + // The ISR itself triggers the update. + // 2. We don't use an ISR and need to update from I2C before we can reliably use the value. + + if (!_interruptPin.defined()) { + _status |= 8; + } + while (_status != 0) { + vTaskDelay(1); // Must be > index) & 1) == 1; + } + + void IRAM_ATTR I2CExtender::flushWrites() { + auto writeMask = _dirtyWriteBuffer.exchange(0); + + _dirtyWrite |= writeMask; + _status |= 4; + + while (_status != 0) { + vTaskDelay(1); + } + } + + void I2CExtender::attachInterrupt(pinnum_t index, void (*callback)(void*), void* arg, int mode) { + Assert(mode == CHANGE, "Only mode CHANGE is allowed for pin extender ISR's."); + Assert(index < 64 && index >= 0, "Pin index out of range"); + + // log_debug("Attaching interrupt (I2C) on index " << int(index)); + + ISRData& data = _isrData[index]; + data.callback = callback; + data.data = arg; + + // Update continuous operation. + _operation &= ~8; + if (!_interruptPin.defined()) { + _operation |= 8 | 16; + } + + // Trigger task configuration update: + std::atomic_thread_fence(std::memory_order::memory_order_seq_cst); // write fence first! + _status |= 1; + } + + void I2CExtender::detachInterrupt(pinnum_t index) { + Assert(index < 64 && index >= 0, "Pin index out of range"); + + // log_debug("Detaching interrupt (I2C) on index " << int(index)); + + ISRData& data = _isrData[index]; + data.callback = nullptr; + data.data = nullptr; + + // Check if we still need to ISR everything. Use a temporary to ensure thread safety: + auto newop = _operation; + newop &= ~8; + if (!_interruptPin.defined()) { + for (int i = 0; i < 64; ++i) { + if (_isrData[i].defined()) { + newop |= 8 | 16; + } + } + } + _operation = newop; + + // Trigger task configuration update: + std::atomic_thread_fence(std::memory_order::memory_order_seq_cst); // write fence first! + _status |= 1; + } + + const char* I2CExtender::name() const { return "i2c_extender"; } + + I2CExtender::~I2CExtender() { + // The task might have allocated temporary data, so we cannot just destroy it: + _status |= 2; + + // Detach the interrupt pin: + if (_interruptPin.defined()) { + _interruptPin.detachInterrupt(); + } + + // Give enough time for the task to stop: + for (int i = 0; i < 10 && _status != 0; ++i) { + vTaskDelay(TaskDelayBetweenIterations); + } + + // Should be safe now to stop. + _isrHandler = nullptr; + } + + // Register extender: + namespace { + PinExtenderFactory::InstanceBuilder registration("i2c_extender"); + } +} diff --git a/FluidNC/src/Extenders/I2CExtender.h b/FluidNC/src/Extenders/I2CExtender.h new file mode 100644 index 000000000..74ebb2f75 --- /dev/null +++ b/FluidNC/src/Extenders/I2CExtender.h @@ -0,0 +1,177 @@ +// Copyright (c) 2021 - Stefan de Bruijn +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +#pragma once + +#include "PinExtenderDriver.h" +#include "../Configuration/Configurable.h" +#include "../Machine/MachineConfig.h" +#include "../Machine/I2CBus.h" +#include "../Platform.h" + +#include + +namespace Pins { + class PCA9539PinDetail; + class PCA9555PinDetail; +} + +namespace Extenders { + enum class I2CExtenderDevice { + Unknown, + PCA9539, + PCA9555, + }; + + // Pin extenders... + // + // The PCA9539 is identical to the PCA9555 in terms of API. It provides 2 address + // pins, so a maximum of 4 possible values. Per PCA, there are 16 I/O ports in 2 + // separate registers, so that's a total of 16*4 = 64 values. + // Datasheet: https://www.ti.com/lit/ds/symlink/pca9539.pdf + // Speed: 400 kHz + // + // The PCA8574 is quite similar as well, but only has 8 bits per device, so a single + // register. It has 3 address pins, so 8 possible values. 8*8=64 bits. + // Datasheet: https://www.nxp.com/docs/en/data-sheet/PCA8574_PCA8574A.pdf + // Speed: 400 kHz + // + // An optional 'interrupt' line can be used. When the 'interrupt' is called, it means + // that *some* pin has changed state. We don't know which one that was obviously. + // However, we can then query the individual pins (thereby resetting them) and throwing + // the results as individual ISR's. + // + // NOTE: The data sheet explains that interrupts can be chained. If that is the case, the + // interrupt will have the effect that ALL PCA's in the chain have to be queried. Needless + // to say, this is usually a bad idea, because things like endstops become much slower + // as a result. For now, I just felt like not supporting it. + // + // The MCP23017 has two interrupt lines, one for register A and register B. Apart from + // that it appears to be quite similar as well. It has 3 address lines and 16 I/O ports, + // so that's a total of 8 * 16 = 128 I/O ports. + // Datasheet: https://ww1.microchip.com/downloads/en/devicedoc/20001952c.pdf + // Speed: 100 kHz, 400 kHz, 1.7 MHz. + // + // MCP23S17 is similar to MCP23017 but works using SPI instead of I2C (10 MHz). MCP23S08 + // seems to be the same, but 8-bit. + // + // MAX7301 is SPI based, and like all the others, it can generate an ISR when the state + // changes (pin 31). Address is selected like any other SPI device by CS. MAX7301 includes + // pullups and schmitt triggers. + // Datasheet: https://datasheet.lcsc.com/lcsc/1804140032_Maxim-Integrated-MAX7301AAX-_C143583.pdf + // + // How this class works... + // + // A single device has a bunch of pins, normally 8 or 16. These devices also have an address. + // The maximum number of extender I/O pins that we currently support is currently 64. Note that + // the I2C frequency doesn't really matter if you have the ISR handled. We limit the maximum number + // of ports to 64 here, which basically means you *can* wire multiple devices on a single ISR line + // by making good use of the addresses. + // + // Keep in mind that low latency is only possible if you do things correctly with placement on the + // PCB, and with designating I/O ports for very specific purposes. If not, the firmware won't know + // what an interrupt means, and has to figure it out before it can take action. + // + // A typical configuration looks like: + // + // device: pca9539 + // device_id: 0 + // interrupt: gpio.36 + // + class I2CExtender : public PinExtenderDriver { + struct RegisterSet { + union { + uint64_t value = 0; + uint8_t bytes[8]; + }; + }; + + struct ISRData { + using ISRCallback = void (*)(void*); + ISRData() : callback(nullptr), data(nullptr) {} + + ISRCallback callback; + void* data; + + inline bool defined() const { return callback != nullptr; } + }; + + // Device info: + int _device = int(I2CExtenderDevice::Unknown); + int _deviceId = 0; + + static const int TaskDelayBetweenIterations = 10; + + int errorCount = 0; + + // Operation and status work together and form a common bitmask. Operation is just not reset, + // while status is. + uint8_t _operation = 0; + + // This information is filled based on the "device" and "device_id" during initialization: + uint8_t _bus = 0; + uint8_t _address = 0x74; + uint8_t _ports = 16; + uint8_t _invertReg = 0xFF; + uint8_t _operationReg = 0xFF; + uint8_t _inputReg = 0xFF; + uint8_t _outputReg = 0xFF; + Pin _interruptPin; + + RegisterSet _claimed; + + Machine::I2CBus* _i2cBus; + + uint8_t I2CGetValue(uint8_t address, uint8_t reg); + void I2CSetValue(uint8_t address, uint8_t reg, uint8_t value); + + // Current register values: + RegisterSet _configuration; + RegisterSet _invert; + volatile RegisterSet _input; + volatile RegisterSet _output; + + // I2C communications within an ISR is not a good idea, it will crash everything. We offload + // the communications using a task queue. Dirty tells which devices and registers to poll. + // Every I2C roundtrip is always responsible for 8 bytes. + TaskHandle_t _isrHandler = nullptr; + + uint8_t _usedIORegisters; + std::atomic _dirtyWriteBuffer; + std::atomic _dirtyWrite; + + // Status is a bitmask that tells the task handle what needs to happen during the next roundtrip. + // This works together with 'operation'. + std::atomic _status; + ISRData _isrData[64]; + + static void isrTaskLoop(void* arg); + void isrTaskLoopDetail(); + + static void interruptHandler(void* arg); + + void IOError(); + + public: + I2CExtender(); + + void claim(pinnum_t index) override; + void free(pinnum_t index) override; + + void group(Configuration::HandlerBase& handler) override; + void validate() const override; + void init(); + + void IRAM_ATTR setupPin(pinnum_t index, Pins::PinAttributes attr) override; + void IRAM_ATTR writePin(pinnum_t index, bool high) override; + bool IRAM_ATTR readPin(pinnum_t index) override; + void IRAM_ATTR flushWrites() override; + + void attachInterrupt(pinnum_t index, void (*callback)(void*), void* arg, int mode) override; + void detachInterrupt(pinnum_t index) override; + + const char* name() const override; + + ~I2CExtender(); + }; +} diff --git a/FluidNC/src/Extenders/PCA9539.cpp b/FluidNC/src/Extenders/PCA9539.cpp new file mode 100644 index 000000000..d7f40f2a1 --- /dev/null +++ b/FluidNC/src/Extenders/PCA9539.cpp @@ -0,0 +1,271 @@ +// Copyright (c) 2021 - Stefan de Bruijn +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +#include "Extenders.h" +#include "PCA9539.h" +#include "../Logging.h" + +#include +#include + +namespace Extenders { + void PCA9539::claim(pinnum_t index) { + Assert(index >= 0 && index < 16 * 4, "PCA9539 IO index should be [0-63]; %d is out of range", index); + + uint64_t mask = uint64_t(1) << index; + Assert((_claimed & mask) == 0, "PCA9539 IO port %d is already used.", index); + + _claimed |= mask; + } + + void PCA9539::free(pinnum_t index) { + uint64_t mask = uint64_t(1) << index; + _claimed &= ~mask; + } + + uint8_t PCA9539::I2CGetValue(Machine::I2CBus* bus, uint8_t address, uint8_t reg) { + auto err = bus->write(address, ®, 1); + + if (err) { + // log_info("Error writing to i2c bus. Code: " << err); + return 0; + } + + uint8_t inputData; + if (bus->read(address, &inputData, 1) != 1) { + // log_info("Error reading from i2c bus."); + } + + return inputData; + } + + void PCA9539::I2CSetValue(Machine::I2CBus* bus, uint8_t address, uint8_t reg, uint8_t value) { + uint8_t data[2]; + data[0] = reg; + data[1] = uint8_t(value); + auto err = bus->write(address, data, 2); + + if (err) { + log_error("Error writing to i2c bus; PCA9539 failed. Code: " << err); + } + } + + void PCA9539::validate() const { + auto i2c = config->_i2c; + Assert(i2c != nullptr, "PCA9539 works through I2C, but I2C is not configured."); + } + + void PCA9539::group(Configuration::HandlerBase& handler) { + handler.item("interrupt0", _isrData[0]._pin); + handler.item("interrupt1", _isrData[1]._pin); + handler.item("interrupt2", _isrData[2]._pin); + handler.item("interrupt3", _isrData[3]._pin); + } + + void PCA9539::isrTaskLoop(void* arg) { + auto inst = static_cast(arg); + while (true) { + void* ptr; + if (xQueueReceive(inst->_isrQueue, &ptr, portMAX_DELAY)) { + ISRData* valuePtr = static_cast(ptr); + // log_info("PCA state change ISR"); + valuePtr->updateValueFromDevice(); + } + } + } + + void PCA9539::init() { + this->_i2cBus = config->_i2c; + + _isrQueue = xQueueCreate(16, sizeof(void*)); + xTaskCreatePinnedToCore(isrTaskLoop, // task + "isr_handler", // name for task + configMINIMAL_STACK_SIZE + 256, // size of task stack + this, // parameters + 1, // priority + &_isrHandler, + SUPPORT_TASK_CORE // core + ); + + for (int i = 0; i < 4; ++i) { + auto& data = _isrData[i]; + + data._address = uint8_t(0x74 + i); + data._container = this; + data._valueBase = reinterpret_cast(&_value) + i; + + // Update the value first by reading it: + data.updateValueFromDevice(); + + if (!data._pin.undefined()) { + data._pin.setAttr(Pin::Attr::ISR | Pin::Attr::Input); + + // The interrupt pin is 'active low'. So if it falls, we're interested in the new value. + data._pin.attachInterrupt(updatePCAState, FALLING, &data); + } else { + // Reset valueBase so we know it's not bound to an ISR: + data._valueBase = nullptr; + } + } + } + + void PCA9539::ISRData::updateValueFromDevice() { + const uint8_t InputReg = 0; + auto i2cBus = _container->_i2cBus; + + auto r1 = I2CGetValue(i2cBus, _address, InputReg); + auto r2 = I2CGetValue(i2cBus, _address, InputReg + 1); + uint16_t oldValue = *_valueBase; + uint16_t value = (uint16_t(r2) << 8) | uint16_t(r1); + *_valueBase = value; + + if (_hasISR) { + for (int i = 0; i < 16; ++i) { + uint16_t mask = uint16_t(1) << i; + + if (_isrCallback[i] != nullptr && (oldValue & mask) != (value & mask)) { + // log_info("State change pin " << i); + switch (_isrMode[i]) { + case RISING: + if ((value & mask) == mask) { + _isrCallback[i](_isrArgument); + } + break; + case FALLING: + if ((value & mask) == 0) { + _isrCallback[i](_isrArgument); + } + break; + case CHANGE: + _isrCallback[i](_isrArgument); + break; + } + } + } + } + } + + void PCA9539::updatePCAState(void* ptr) { + ISRData* valuePtr = static_cast(ptr); + + BaseType_t xHigherPriorityTaskWoken = false; + xQueueSendFromISR(valuePtr->_container->_isrQueue, &valuePtr, &xHigherPriorityTaskWoken); + } + + void PCA9539::setupPin(pinnum_t index, Pins::PinAttributes attr) { + bool activeLow = attr.has(Pins::PinAttributes::ActiveLow); + bool output = attr.has(Pins::PinAttributes::Output); + + uint64_t mask = uint64_t(1) << index; + _invert = (_invert & ~mask) | (activeLow ? mask : 0); + _configuration = (_configuration & ~mask) | (output ? 0 : mask); + + const uint8_t deviceId = index / 16; + + const uint8_t ConfigReg = 6; + uint8_t address = 0x74 + deviceId; + + uint8_t value = uint8_t(_configuration >> (8 * (index / 8))); + uint8_t reg = ConfigReg + ((index / 8) & 1); + + // log_info("Setup reg " << int(reg) << " with value " << int(value)); + + I2CSetValue(_i2cBus, address, reg, value); + } + + void PCA9539::writePin(pinnum_t index, bool high) { + uint64_t mask = uint64_t(1) << index; + uint64_t oldVal = _value; + uint64_t newVal = high ? mask : uint64_t(0); + _value = (_value & ~mask) | newVal; + + _dirtyRegisters |= ((_value != oldVal) ? 1 : 0) << (index / 8); + } + + bool PCA9539::readPin(pinnum_t index) { + uint8_t reg = uint8_t(index / 8); + uint8_t deviceId = reg / 2; + + // If it's handled by the ISR, we don't need to read anything from the device. + // Otherwise, we do. Check: + if (_isrData[deviceId]._valueBase == nullptr) { + const uint8_t InputReg = 0; + uint8_t address = 0x74 + deviceId; + + auto readReg = InputReg + (reg & 1); + auto value = I2CGetValue(_i2cBus, address, readReg); + uint64_t newValue = uint64_t(value) << (int(reg) * 8); + uint64_t mask = uint64_t(0xff) << (int(reg) * 8); + + _value = ((newValue ^ _invert) & mask) | (_value & ~mask); + + // log_info("Read reg " << int(readReg) << " <- value " << int(newValue) << " gives " << int(_value)); + } + // else { + // log_info("No read, value is " << int(_value)); + // } + + return (_value & (1ull << index)) != 0; + } + + void PCA9539::flushWrites() { + uint64_t write = _value ^ _invert; + for (int i = 0; i < 8; ++i) { + if ((_dirtyRegisters & (1 << i)) != 0) { + const uint8_t OutputReg = 2; + uint8_t address = 0x74 + (i / 2); + + uint8_t val = uint8_t(write >> (8 * i)); + uint8_t reg = OutputReg + (i & 1); + I2CSetValue(_i2cBus, address, reg, val); + } + } + + _dirtyRegisters = 0; + } + + // ISR's: + void PCA9539::attachInterrupt(pinnum_t index, void (*callback)(void*), void* arg, int mode) { + int device = index / 16; + int pinNumber = index % 16; + + Assert(_isrData[device]._isrCallback[pinNumber] == nullptr, "You can only set a single ISR for pin %d", index); + + _isrData[device]._isrCallback[pinNumber] = callback; + _isrData[device]._isrArgument[pinNumber] = arg; + _isrData[device]._isrMode[pinNumber] = mode; + _isrData[device]._hasISR = true; + } + + void PCA9539::detachInterrupt(pinnum_t index) { + int device = index / 16; + int pinNumber = index % 16; + + _isrData[device]._isrCallback[pinNumber] = nullptr; + _isrData[device]._isrArgument[pinNumber] = nullptr; + _isrData[device]._isrMode[pinNumber] = 0; + + bool hasISR = false; + for (int i = 0; i < 16; ++i) { + hasISR |= (_isrData[device]._isrArgument[i] != nullptr); + } + _isrData[device]._hasISR = hasISR; + } + + const char* PCA9539::name() const { return "pca9539"; } + + PCA9539 ::~PCA9539() { + for (int i = 0; i < 4; ++i) { + auto& data = _isrData[i]; + + if (!data._pin.undefined()) { + data._pin.detachInterrupt(); + } + } + } + + // Register extender: + namespace { + PinExtenderFactory::InstanceBuilder registration("pca9539"); + } +} diff --git a/FluidNC/src/Extenders/PCA9539.h b/FluidNC/src/Extenders/PCA9539.h new file mode 100644 index 000000000..d90068370 --- /dev/null +++ b/FluidNC/src/Extenders/PCA9539.h @@ -0,0 +1,126 @@ +// Copyright (c) 2021 - Stefan de Bruijn +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +#pragma once + +#include "PinExtenderDriver.h" +#include "../Configuration/Configurable.h" +#include "../Machine/MachineConfig.h" +#include "../Machine/I2CBus.h" +#include "../Platform.h" + +#include + +namespace Pins { + class PCA9539PinDetail; +} + +namespace Extenders { + // Pin extenders... + // + // The PCA9539 is identical to the PCA9555 in terms of API. It provides 2 address + // pins, so a maximum of 4 possible values. Per PCA, there are 16 I/O ports in 2 + // separate registers, so that's a total of 16*4 = 64 values. + // Datasheet: https://www.ti.com/lit/ds/symlink/pca9539.pdf + // Speed: 400 kHz + // + // The PCA8574 is quite similar as well, but only has 8 bits per device, so a single + // register. It has 3 address pins, so 8 possible values. 8*8=64 bits. + // Datasheet: https://www.nxp.com/docs/en/data-sheet/PCA8574_PCA8574A.pdf + // Speed: 400 kHz + // + // An optional 'interrupt' line can be used. When the 'interrupt' is called, it means + // that *some* pin has changed state. We don't know which one that was obviously. + // However, we can then query the individual pins (thereby resetting them) and throwing + // the results as individual ISR's. + // + // NOTE: The data sheet explains that interrupts can be chained. If that is the case, the + // interrupt will have the effect that ALL PCA's in the chain have to be queried. Needless + // to say, this is usually a bad idea, because things like endstops become much slower + // as a result. For now, I just felt like not supporting it. + // + // The MCP23017 has two interrupt lines, one for register A and register B. Apart from + // that it appears to be quite similar as well. It has 3 address lines and 16 I/O ports, + // so that's a total of 8 * 16 = 128 I/O ports. + // Datasheet: https://ww1.microchip.com/downloads/en/devicedoc/20001952c.pdf + // Speed: 100 kHz, 400 kHz, 1.7 MHz. + // + // MCP23S17 is similar to MCP23017 but works using SPI instead of I2C (10 MHz). MCP23S08 + // seems to be the same, but 8-bit. + // + // MAX7301 is SPI based, and like all the others, it can generate an ISR when the state + // changes (pin 31). Address is selected like any other SPI device by CS. MAX7301 includes + // pullups and schmitt triggers. + // Datasheet: https://datasheet.lcsc.com/lcsc/1804140032_Maxim-Integrated-MAX7301AAX-_C143583.pdf + class PCA9539 : public PinExtenderDriver { + friend class Pins::PCA9539PinDetail; + + // Address can be set for up to 4 devices. Each device supports 16 pins. + + static const int numberPins = 16 * 4; + uint64_t _claimed; + + Machine::I2CBus* _i2cBus; + + static uint8_t IRAM_ATTR I2CGetValue(Machine::I2CBus* bus, uint8_t address, uint8_t reg); + static void IRAM_ATTR I2CSetValue(Machine::I2CBus* bus, uint8_t address, uint8_t reg, uint8_t value); + + // Registers: + // 4x16 = 64 bits. Fits perfectly into an uint64. + uint64_t _configuration = 0; + uint64_t _invert = 0; + volatile uint64_t _value = 0; + + // 4 devices, 2 registers per device. 8 bits is enough: + uint8_t _dirtyRegisters = 0; + + QueueHandle_t _isrQueue = nullptr; + TaskHandle_t _isrHandler = nullptr; + + static void isrTaskLoop(void* arg); + + struct ISRData { + ISRData() = default; + + Pin _pin; + PCA9539* _container = nullptr; + volatile uint16_t* _valueBase = nullptr; + uint8_t _address = 0; + + typedef void (*ISRCallback)(void*); + + bool _hasISR = false; + ISRCallback _isrCallback[16] = { 0 }; + void* _isrArgument[16] = { 0 }; + int _isrMode[16] = { 0 }; + + void IRAM_ATTR updateValueFromDevice(); + }; + + ISRData _isrData[4]; + static void IRAM_ATTR updatePCAState(void* ptr); + + public: + PCA9539() = default; + + void claim(pinnum_t index) override; + void free(pinnum_t index) override; + + void validate() const override; + void group(Configuration::HandlerBase& handler) override; + + void init(); + + void IRAM_ATTR setupPin(pinnum_t index, Pins::PinAttributes attr) override; + void IRAM_ATTR writePin(pinnum_t index, bool high) override; + bool IRAM_ATTR readPin(pinnum_t index) override; + void IRAM_ATTR flushWrites() override; + + void attachInterrupt(pinnum_t index, void (*callback)(void*), void* arg, int mode) override; + void detachInterrupt(pinnum_t index) override; + + const char* name() const override; + + ~PCA9539(); + }; +} diff --git a/FluidNC/src/Extenders/PinExtender.h b/FluidNC/src/Extenders/PinExtender.h new file mode 100644 index 000000000..2c60327c1 --- /dev/null +++ b/FluidNC/src/Extenders/PinExtender.h @@ -0,0 +1,23 @@ +// Copyright (c) 2021 - Stefan de Bruijn +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +#pragma once + +#include "../Configuration/Configurable.h" +#include "PinExtenderDriver.h" + +namespace Extenders { + class PinExtender : public Configuration::Configurable { + public: + // Other configurations? + PinExtenderDriver* _driver; + + PinExtender(); + + void validate() const override; + void group(Configuration::HandlerBase& handler) override; + void init(); + + ~PinExtender(); + }; +} diff --git a/FluidNC/src/Extenders/PinExtenderDriver.cpp b/FluidNC/src/Extenders/PinExtenderDriver.cpp new file mode 100644 index 000000000..efc602acc --- /dev/null +++ b/FluidNC/src/Extenders/PinExtenderDriver.cpp @@ -0,0 +1,12 @@ +// Copyright (c) 2021 - Stefan de Bruijn +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +#include "PinExtenderDriver.h" + +namespace Extenders { + + void PinExtenderDriver::attachInterrupt(pinnum_t index, void (*callback)(void*), void* arg, int mode) { + Assert(false, "Interrupts are not supported by pin extender for pin %d", index); + } + void PinExtenderDriver::detachInterrupt(pinnum_t index) { Assert(false, "Interrupts are not supported by pin extender"); } +} diff --git a/FluidNC/src/Extenders/PinExtenderDriver.h b/FluidNC/src/Extenders/PinExtenderDriver.h new file mode 100644 index 000000000..dfc4e79ad --- /dev/null +++ b/FluidNC/src/Extenders/PinExtenderDriver.h @@ -0,0 +1,33 @@ +// Copyright (c) 2021 - Stefan de Bruijn +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +#pragma once + +#include "../Configuration/Configurable.h" +#include "../Pins/PinAttributes.h" + +#include "../Platform.h" + +namespace Extenders { + class PinExtenderDriver : public Configuration::Configurable { + public: + virtual void init() = 0; + + virtual void claim(pinnum_t index) = 0; + virtual void free(pinnum_t index) = 0; + + virtual void IRAM_ATTR setupPin(pinnum_t index, Pins::PinAttributes attr) = 0; + virtual void IRAM_ATTR writePin(pinnum_t index, bool high) = 0; + virtual bool IRAM_ATTR readPin(pinnum_t index) = 0; + virtual void IRAM_ATTR flushWrites() = 0; + + virtual void attachInterrupt(pinnum_t index, void (*callback)(void*), void* arg, int mode); + virtual void detachInterrupt(pinnum_t index); + + // Name is required for the configuration factory to work. + virtual const char* name() const = 0; + + // Virtual base classes require a virtual destructor. + virtual ~PinExtenderDriver() {} + }; +} diff --git a/FluidNC/src/Machine/I2CBus.cpp b/FluidNC/src/Machine/I2CBus.cpp new file mode 100644 index 000000000..41c4f0d51 --- /dev/null +++ b/FluidNC/src/Machine/I2CBus.cpp @@ -0,0 +1,66 @@ +// Copyright (c) 2021 - Stefan de Bruijn +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +#include "I2CBus.h" + +#include +#include + +namespace Machine { + void I2CBus::validate() const { + if (_sda.defined() || _scl.defined()) { + Assert(_sda.defined(), "I2C SDA pin should be configured once"); + Assert(_scl.defined(), "I2C SCL pin should be configured once"); + Assert(_busNumber == 0 || _busNumber == 1, "The ESP32 only has 2 I2C buses. Number %d is invalid", _busNumber); + } + } + + void I2CBus::group(Configuration::HandlerBase& handler) { + handler.item("sda", _sda); + handler.item("scl", _scl); + handler.item("bus", _busNumber); + handler.item("frequency", _frequency); + } + + void I2CBus::init() { + auto sdaPin = _sda.getNative(Pin::Capabilities::Native | Pin::Capabilities::Input | Pin::Capabilities::Output); + auto sclPin = _scl.getNative(Pin::Capabilities::Native | Pin::Capabilities::Input | Pin::Capabilities::Output); + + Assert(_busNumber == 0 || _busNumber == 1, "Bus # has to be 0 or 1; the ESP32 does not have more i2c peripherals."); + + if (_busNumber == 0) { + i2c = &Wire; + } else { + i2c = &Wire1; + } + i2c->begin(int(sdaPin), int(sclPin), _frequency); + + log_info("I2C SDA: " << _sda.name() << ", SCL: " << _scl.name() << ", Freq: " << _frequency << ", Bus #: " << _busNumber); + } + + const char* I2CBus::ErrorDescription(int code) { return esp_err_to_name(code); } + + int I2CBus::write(uint8_t address, const uint8_t* data, size_t count) { + // log_debug("I2C write addr=" << int(address) << ", count=" << int(count) << ", data " << (data ? "non null" : "null") << ", i2c " + // << (i2c ? "non null" : "null")); + + i2c->beginTransmission(address); + for (size_t i = 0; i < count; ++i) { + i2c->write(data[i]); + } + return i2c->endTransmission(); // i2c_err_t, see header file + } + + int I2CBus::read(uint8_t address, uint8_t* data, size_t count) { + // log_debug("I2C read addr=" << int(address) << ", count=" << int(count) << ", data " << (data ? "non null" : "null") << ", i2c " + // << (i2c ? "non null" : "null")); + + size_t c = i2c->requestFrom((int)address, count); + + for (size_t i = 0; i < c; ++i) { + data[i] = i2c->read(); + } + return c; + } + +} diff --git a/FluidNC/src/Machine/I2CBus.h b/FluidNC/src/Machine/I2CBus.h new file mode 100644 index 000000000..f7ddab35e --- /dev/null +++ b/FluidNC/src/Machine/I2CBus.h @@ -0,0 +1,36 @@ +// Copyright (c) 2021 - Stefan de Bruijn +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +#pragma once + +#include "../Configuration/Configurable.h" + +#include + +class TwoWire; + +namespace Machine { + class I2CBus : public Configuration::Configurable { + protected: + TwoWire* i2c = nullptr; + + public: + I2CBus() = default; + + int _busNumber = 0; + Pin _sda; + Pin _scl; + uint32_t _frequency = 100000; + + void init(); + void validate() const override; + void group(Configuration::HandlerBase& handler) override; + + static const char* ErrorDescription(int code); + + int IRAM_ATTR write(uint8_t address, const uint8_t* data, size_t count); + int IRAM_ATTR read(uint8_t address, uint8_t* data, size_t count); + + ~I2CBus() = default; + }; +} diff --git a/FluidNC/src/Machine/LimitPin.cpp b/FluidNC/src/Machine/LimitPin.cpp index 89182dee3..56e06e858 100644 --- a/FluidNC/src/Machine/LimitPin.cpp +++ b/FluidNC/src/Machine/LimitPin.cpp @@ -45,6 +45,7 @@ namespace Machine { void IRAM_ATTR LimitPin::handleISR() { read(); + if (sys.state != State::Alarm && sys.state != State::ConfigAlarm && sys.state != State::Homing) { if (_pHardLimits && rtAlarm == ExecAlarm::None) { #if 0 @@ -57,7 +58,7 @@ namespace Machine { } #endif - // log_debug("Hard limits"); // This might not work from ISR context + // log_debug("Hard limits"); // This might not work from ISR context mc_reset(); // Initiate system kill. rtAlarm = ExecAlarm::HardLimit; // Indicate hard limit critical event } @@ -91,6 +92,7 @@ namespace Machine { if (_pin.undefined()) { return; } + set_bitnum(Axes::limitMask, _axis); _pin.report(_legend.c_str()); auto attr = Pin::Attr::Input | Pin::Attr::ISR; diff --git a/FluidNC/src/Machine/LimitPin.h b/FluidNC/src/Machine/LimitPin.h index 509907c25..8a6a60647 100644 --- a/FluidNC/src/Machine/LimitPin.h +++ b/FluidNC/src/Machine/LimitPin.h @@ -35,7 +35,7 @@ namespace Machine { void read(); public: - LimitPin(Pin& pin, int axis, int motorNum, int direction, bool& phardLimits, bool& pLimited); + LimitPin(Pin& pin, int axis, int motor, int direction, bool& pHardLimits, bool& pLimited); Pin& _pin; diff --git a/FluidNC/src/Machine/MachineConfig.cpp b/FluidNC/src/Machine/MachineConfig.cpp index 81707ec50..8529c3450 100644 --- a/FluidNC/src/Machine/MachineConfig.cpp +++ b/FluidNC/src/Machine/MachineConfig.cpp @@ -40,12 +40,14 @@ namespace Machine { handler.section("axes", _axes); handler.section("kinematics", _kinematics); handler.section("i2so", _i2so); + handler.section("i2c", _i2c); handler.section("spi", _spi); handler.section("sdcard", _sdCard); handler.section("control", _control); handler.section("coolant", _coolant); handler.section("probe", _probe); handler.section("macros", _macros); + handler.section("extenders", _extenders); handler.section("start", _start); handler.section("user_outputs", _userOutputs); diff --git a/FluidNC/src/Machine/MachineConfig.h b/FluidNC/src/Machine/MachineConfig.h index 49e0819db..981ab8a71 100644 --- a/FluidNC/src/Machine/MachineConfig.h +++ b/FluidNC/src/Machine/MachineConfig.h @@ -11,6 +11,7 @@ #include "../CoolantControl.h" #include "../Kinematics/Kinematics.h" #include "../WebUI/BTConfig.h" +#include "../Extenders/Extenders.h" #include "../Control.h" #include "../Probe.h" #include "../SDCard.h" @@ -21,6 +22,7 @@ #include "../Config.h" #include "Axes.h" #include "SPIBus.h" +#include "I2CBus.h" #include "I2SOBus.h" #include "UserOutputs.h" #include "Macros.h" @@ -58,6 +60,7 @@ namespace Machine { Axes* _axes = nullptr; Kinematics* _kinematics = nullptr; SPIBus* _spi = nullptr; + I2CBus* _i2c = nullptr; I2SOBus* _i2so = nullptr; Stepping* _stepping = nullptr; CoolantControl* _coolant = nullptr; @@ -68,6 +71,7 @@ namespace Machine { Macros* _macros = nullptr; Start* _start = nullptr; Spindles::SpindleList _spindles; + Extenders::Extenders* _extenders = nullptr; float _arcTolerance = 0.002f; float _junctionDeviation = 0.01f; diff --git a/FluidNC/src/Machine/Motor.cpp b/FluidNC/src/Machine/Motor.cpp index c86009b5c..39f20e7c7 100644 --- a/FluidNC/src/Machine/Motor.cpp +++ b/FluidNC/src/Machine/Motor.cpp @@ -10,6 +10,8 @@ #include "Axes.h" namespace Machine { + Motor::Motor(int axis, int motorNum) : _axis(axis), _motorNum(motorNum) {} + void Motor::group(Configuration::HandlerBase& handler) { handler.item("limit_neg_pin", _negPin); handler.item("limit_pos_pin", _posPin); @@ -26,6 +28,8 @@ namespace Machine { } void Motor::init() { + log_debug("Initializing motor / limits..."); + if (strcmp(_driver->name(), "null_motor") != 0) { set_bitnum(Machine::Axes::motorMask, Machine::Axes::motor_bit(_axis, _motorNum)); } diff --git a/FluidNC/src/Machine/Motor.h b/FluidNC/src/Machine/Motor.h index 90674fd45..cec791ee7 100644 --- a/FluidNC/src/Machine/Motor.h +++ b/FluidNC/src/Machine/Motor.h @@ -25,7 +25,7 @@ namespace Machine { int _motorNum; public: - Motor(int axis, int motorNum) : _axis(axis), _motorNum(motorNum) {} + Motor(int axis, int motorNum); MotorDrivers::MotorDriver* _driver = nullptr; float _pulloff = 1.0f; // mm @@ -35,9 +35,10 @@ namespace Machine { Pin _allPin; bool _hardLimits = false; - int32_t _steps = 0; - bool _limited = false; // _limited is set by the LimitPin ISR - bool _blocked = false; // _blocked is used during asymmetric homing pulloff + int32_t _stepsOffset = 0; + int32_t _steps = 0; + bool _limited = false; // _limited is set by the LimitPin ISR + bool _blocked = false; // _blocked is used during asymmetric homing pulloff // Configuration system helpers: void group(Configuration::HandlerBase& handler) override; diff --git a/FluidNC/src/Main.cpp b/FluidNC/src/Main.cpp index f3c443031..4626a7d84 100644 --- a/FluidNC/src/Main.cpp +++ b/FluidNC/src/Main.cpp @@ -72,17 +72,22 @@ void setup() { config->_sdCard->init(); } } + if (config->_i2c) { + config->_i2c->init(); + } + + // We have to initialize the extenders first, before pins are used + if (config->_extenders) { + config->_extenders->init(); + } config->_stepping->init(); // Configure stepper interrupt timers plan_init(); config->_userOutputs->init(); - config->_axes->init(); - config->_control->init(); - config->_kinematics->init(); auto n_axis = config->_axes->_numberAxis; @@ -123,7 +128,6 @@ void setup() { config->_coolant->init(); config->_probe->init(); } - } catch (const AssertionFailed& ex) { // This means something is terribly broken: log_error("Critical error in main_init: " << ex.what()); diff --git a/FluidNC/src/MotionControl.cpp b/FluidNC/src/MotionControl.cpp index 45ff79c25..839be53f6 100644 --- a/FluidNC/src/MotionControl.cpp +++ b/FluidNC/src/MotionControl.cpp @@ -133,7 +133,7 @@ void mc_arc(float* target, auto n_axis = config->_axes->_numberAxis; - float previous_position[n_axis] = { 0.0 }; + float previous_position[MAX_N_AXIS] = { 0.0f }; for (size_t i = 0; i < n_axis; i++) { previous_position[i] = position[i]; } @@ -165,7 +165,7 @@ void mc_arc(float* target, pl_data->motion.inverseTime = 0; // Force as feed absolute mode over arc segments. } float theta_per_segment = angular_travel / segments; - float linear_per_segment[n_axis]; + float linear_per_segment[MAX_N_AXIS]; linear_per_segment[axis_linear] = (target[axis_linear] - position[axis_linear]) / segments; for (size_t i = A_AXIS; i < n_axis; i++) { linear_per_segment[i] = (target[i] - position[i]) / segments; diff --git a/FluidNC/src/Pin.cpp b/FluidNC/src/Pin.cpp index 0f825ff76..0cbee91ce 100644 --- a/FluidNC/src/Pin.cpp +++ b/FluidNC/src/Pin.cpp @@ -10,6 +10,7 @@ #include "Pins/VoidPinDetail.h" #include "Pins/I2SOPinDetail.h" #include "Pins/ErrorPinDetail.h" +#include "Pins/ExtPinDetail.h" #include // snprintf() Pins::PinDetail* Pin::undefinedPin = new Pins::VoidPinDetail(); @@ -94,6 +95,16 @@ const char* Pin::parse(StringRange tmp, Pins::PinDetail*& pinImplementation) { pinImplementation = new Pins::VoidPinDetail(); } + if (prefix.startsWith("pinext")) { + if (prefix.length() == 7 && prefix[6] >= '0' && prefix[6] <= '9') { + auto deviceId = prefix[6] - '0'; + pinImplementation = new Pins::ExtPinDetail(deviceId, pinnum_t(pinNumber), parser); + } else { + // For now this should be sufficient, if not we can easily change it to 100 extenders: + return "Incorrect pin extender specification. Expected 'pinext[0-9].[port number]'."; + } + } + if (pinImplementation == nullptr) { log_error("Unknown prefix:" << prefix); return "Unknown pin prefix"; diff --git a/FluidNC/src/Pins/ExtPinDetail.cpp b/FluidNC/src/Pins/ExtPinDetail.cpp new file mode 100644 index 000000000..a5809f1b0 --- /dev/null +++ b/FluidNC/src/Pins/ExtPinDetail.cpp @@ -0,0 +1,93 @@ +// Copyright (c) 2021 - Stefan de Bruijn +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +#include "ExtPinDetail.h" + +namespace Pins { + ExtPinDetail::ExtPinDetail(int device, pinnum_t index, const PinOptionsParser& options) : + PinDetail(index), _device(device), _capabilities(PinCapabilities::Output | PinCapabilities::Input | PinCapabilities::ISR), + _attributes(Pins::PinAttributes::Undefined) { + // User defined pin capabilities + for (auto opt : options) { + if (opt.is("low")) { + _attributes = _attributes | PinAttributes::ActiveLow; + } else if (opt.is("high")) { + // Default: Active HIGH. + } else { + Assert(false, "Unsupported I2SO option '%s'", opt()); + } + } + } + + PinCapabilities ExtPinDetail::capabilities() const { return PinCapabilities::Input | PinCapabilities::Output | PinCapabilities::ISR; } + + // I/O: + void ExtPinDetail::write(int high) { + Assert(_owner != nullptr, "Cannot write to uninitialized pin"); + _owner->writePin(_index, high); + } + + void ExtPinDetail::synchronousWrite(int high) { + Assert(_owner != nullptr, "Cannot write to uninitialized pin"); + _owner->writePin(_index, high); + _owner->flushWrites(); + } + + int ExtPinDetail::read() { + Assert(_owner != nullptr, "Cannot read from uninitialized pin"); + return _owner->readPin(_index); + } + + void ExtPinDetail::setAttr(PinAttributes value) { + // We setup the driver in setAttr. Before this time, the owner might not be valid. + + // Check the attributes first: + Assert(value.has(PinAttributes::Input) || value.has(PinAttributes::Output), "PCA9539 pins can be used as either input or output."); + Assert(value.has(PinAttributes::Input) != value.has(PinAttributes::Output), "PCA9539 pins can be used as either input or output."); + Assert(value.validateWith(this->_capabilities), "Requested attributes do not match the PCA9539 pin capabilities."); + Assert(!_attributes.conflictsWith(value), "Attributes on this pin have been set before, and there's a conflict."); + + _attributes = value; + + bool activeLow = _attributes.has(PinAttributes::ActiveLow); + + if (_owner == nullptr) { + auto ext = config->_extenders; + if (ext != nullptr && ext->_pinDrivers[_device] != nullptr && ext->_pinDrivers[_device]->_driver != nullptr) { + _owner = ext->_pinDrivers[_device]->_driver; + } else { + Assert(false, "Cannot find pin extender definition in configuration for pin pinext%d.%d", _device, _index); + } + + _owner->claim(_index); + } + + _owner->setupPin(_index, _attributes); + _owner->writePin(_index, value.has(PinAttributes::InitialOn)); + } + + PinAttributes ExtPinDetail::getAttr() const { return _attributes; } + + void ExtPinDetail::attachInterrupt(void (*callback)(void*), void* arg, int mode) { + Assert(_owner != nullptr, "Cannot attach ISR on uninitialized pin"); + _owner->attachInterrupt(_index, callback, arg, mode); + } + void ExtPinDetail::detachInterrupt() { + Assert(_owner != nullptr, "Cannot detach ISR on uninitialized pin"); + _owner->detachInterrupt(_index); + } + + String ExtPinDetail::toString() { + auto s = String("pinext") + int(_device) + String(".") + int(_index); + if (_attributes.has(PinAttributes::ActiveLow)) { + s += ":low"; + } + return s; + } + + ExtPinDetail::~ExtPinDetail() { + if (_owner) { + _owner->free(_index); + } + } +} diff --git a/FluidNC/src/Pins/ExtPinDetail.h b/FluidNC/src/Pins/ExtPinDetail.h new file mode 100644 index 000000000..df1f9320f --- /dev/null +++ b/FluidNC/src/Pins/ExtPinDetail.h @@ -0,0 +1,43 @@ +// Copyright (c) 2021 - Stefan de Bruijn +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +#pragma once + +#include "PinDetail.h" +#include "../Extenders/PinExtenderDriver.h" +#include "../Configuration/Configurable.h" +#include "../Machine/MachineConfig.h" +#include "../Machine/I2CBus.h" + +#include + +namespace Pins { + class ExtPinDetail : public PinDetail { + Extenders::PinExtenderDriver* _owner = nullptr; + int _device; + + PinCapabilities _capabilities; + PinAttributes _attributes; + + public: + ExtPinDetail(int device, pinnum_t index, const PinOptionsParser& options); + + PinCapabilities capabilities() const override; + + // I/O: + void write(int high) override; + void synchronousWrite(int high) override; + int read() override; + + // ISR's: + void attachInterrupt(void (*callback)(void*), void* arg, int mode) override; + void detachInterrupt() override; + + void setAttr(PinAttributes value) override; + PinAttributes getAttr() const override; + + String toString() override; + + ~ExtPinDetail() override; + }; +} diff --git a/FluidNC/src/ProcessSettings.cpp b/FluidNC/src/ProcessSettings.cpp index bb203d8de..25da260f8 100644 --- a/FluidNC/src/ProcessSettings.cpp +++ b/FluidNC/src/ProcessSettings.cpp @@ -596,6 +596,23 @@ static Error motors_init(const char* value, WebUI::AuthenticationLevel auth_leve return Error::Ok; } +static Error motorPosition(const char* value, WebUI::AuthenticationLevel auth_level, Channel& out) { + out << "[MSG: DBG: Motor position: ("; + auto axes = config->_axes; + auto n_axis = axes->_numberAxis; + for (size_t axis = 0; axis < n_axis; axis++) { + auto m = axes->_axis[axis]->_motors[0]; + auto steps = m ? (m->_steps + m->_stepsOffset) : 0; + if (axis != 0) { + out << ", "; + } + out << steps; + } + out << ")]\n"; + + return Error::Ok; +} + static Error macros_run(const char* value, WebUI::AuthenticationLevel auth_level, Channel& out) { if (value) { log_info("Running macro " << *value); @@ -723,6 +740,7 @@ void make_user_commands() { new UserCommand("H", "Home", home_all, notIdleOrAlarm); new UserCommand("MD", "Motor/Disable", motor_disable, notIdleOrAlarm); new UserCommand("MI", "Motors/Init", motors_init, notIdleOrAlarm); + new UserCommand("MP", "Motors/Position", motorPosition, anyState); new UserCommand("RM", "Macros/Run", macros_run, notIdleOrAlarm); diff --git a/FluidNC/src/System.cpp b/FluidNC/src/System.cpp index a610dcc99..659042de1 100644 --- a/FluidNC/src/System.cpp +++ b/FluidNC/src/System.cpp @@ -53,6 +53,7 @@ void set_motor_steps(size_t axis, int32_t steps) { for (size_t motor = 0; motor < Machine::Axis::MAX_MOTORS_PER_AXIS; motor++) { auto m = a->_motors[motor]; if (m) { + m->_stepsOffset += (m->_steps - steps); m->_steps = steps; } } diff --git a/FluidNC/test/Extender/I2CExtenderTests.cpp b/FluidNC/test/Extender/I2CExtenderTests.cpp new file mode 100644 index 000000000..95e6f8dbb --- /dev/null +++ b/FluidNC/test/Extender/I2CExtenderTests.cpp @@ -0,0 +1,869 @@ +#include "../TestFramework.h" + +#include +#include +#include +#include +#include +#include + +#include "Capture.h" + +#include +#include + +namespace { + struct GPIONative { + // We wire 15 to 20. + static void WriteVirtualCircuitHystesis(SoftwarePin* pins, int pin, bool value) { + // switch (pin) { + // case 20: + // pins[15].handlePadChange(value); + // break; + // } + } + + inline static void initialize() { SoftwareGPIO::instance().reset(WriteVirtualCircuitHystesis, true); } + inline static void mode(int pin, uint8_t mode) { SoftwareGPIO::instance().setMode(pin, mode); } + inline static void write(int pin, bool val) { SoftwareGPIO::instance().writeOutput(pin, val); } + inline static bool read(int pin) { return SoftwareGPIO::instance().read(pin); } + }; + + class PCA9539Emulator { + uint8_t reg_config[2]; + uint8_t reg_invert[2]; + uint8_t reg_input[2]; + uint8_t reg_output[2]; + uint8_t pad_value[2]; // input values + + uint8_t accessedRegisters = 0; + uint8_t previousRegisters = 0; // For debugging purposes. + + int isrPin_; + + void setRegister(int reg, uint8_t value) { + accessedRegisters |= uint8_t(1 << reg); + switch (reg) { + // input + case 2: + reg_output[0] = value; + break; + case 3: + reg_output[1] = value; + break; + // invert + case 4: + reg_invert[0] = value; + reg_input[0] = reg_invert[0] ^ pad_value[0]; + break; + case 5: + reg_invert[1] = value; + reg_input[1] = reg_invert[1] ^ pad_value[1]; + break; + // config + case 6: + reg_config[0] = value; + break; + case 7: + reg_config[1] = value; + break; + default: + Assert(false, "Not supported"); + break; + } + } + + void handler(TwoWire* theWire, std::vector& data) { + if (data.size() == 1) { + if (data[0] == 0 || data[0] == 1) { + accessedRegisters |= uint8_t(1 << data[0]); + Assert(theWire->SendSize() == 0); + theWire->Send(uint8_t(reg_input[data[0]])); + data.clear(); + + // Clear ISR: + if (isrPin_ >= 0) { + GPIONative::write(isrPin_, true); + } + } else if (data[0] >= 2 && data[0] <= 7) { + // ignore until next roundtrip + } else { + Assert(false, "Unknown register"); + } + } else if (data.size() == 2) { + if (data[0] >= 2 && data[0] <= 7) { + setRegister(data[0], data[1]); + data.clear(); + } else { + Assert(false, "Unknown register"); + } + } else { + Assert(false, "Unknown size"); + } + } + + public: + PCA9539Emulator(int isrPin) : isrPin_(isrPin) { + for (int i = 0; i < 2; ++i) { + reg_config[i] = 0; + reg_invert[i] = 0; + reg_input[i] = 0; + reg_output[i] = 0; + pad_value[i] = 0; + } + + if (isrPin_ >= 0) { + GPIONative::write(isrPin_, true); + } + } + + static void wireResponseHandler(TwoWire* theWire, std::vector& data, void* userData) { + static_cast(userData)->handler(theWire, data); + } + + void setPadValue(int pinId, bool v) { + uint8_t mask = uint8_t(1 << (pinId % 8)); + int idx = pinId / 8; + + if (reg_config[idx] & mask) // input + { + auto oldValue = pad_value[idx] & mask; + auto newValue = v ? mask : uint8_t(0); + + if (oldValue != newValue) { + pad_value[idx] = (pad_value[idx] & ~mask) | newValue; + reg_input[idx] = reg_invert[idx] ^ pad_value[idx]; + + // Trigger ISR on 'falling'. + if (isrPin_ >= 0) { + GPIONative::write(isrPin_, false); + } + } + } + } + + bool getPadValue(int pinId) { + uint8_t mask = uint8_t(1 << (pinId % 8)); + int idx = pinId / 8; + + if ((reg_config[idx] & mask) == 0) { + // This is an output pin, so combine registers: + return ((reg_output[idx] ^ reg_invert[idx]) & mask) != 0; + } else { + // This is an input pin, so use the pad_value + return (pad_value[idx] & mask) != 0; + } + } + + uint8_t registersUsed() { + auto result = accessedRegisters; + previousRegisters = result; + accessedRegisters = 0; + return result; + } + }; + + class Roundtrip { + uint32_t before; + + public: + Roundtrip() { before = Capture::instance().current(); } + + ~Roundtrip() { + for (int i = 0; i < 3; ++i) { + while (Capture::instance().current() < before + 1) { + delay(10); + } + before = Capture::instance().current(); + } + } + }; + + std::mutex single_thread; +} + +namespace Configuration { + Test(I2CExtender, I2CBasics) { + // Initialize I2C bus + Machine::I2CBus bus; + bus._sda = Pin::create("gpio.16"); + bus._scl = Pin::create("gpio.17"); + bus._frequency = 100000; + bus._busNumber = 0; + + bus.validate(); + bus.init(); + + Wire.Clear(); + + Assert(0 == bus.write(1, reinterpret_cast("aap"), 3), "Bad write"); + auto data = Wire.Receive(); + + Assert(data.size() == 3, "Expected 3 bytes"); + data.push_back(0); + Assert(!strcmp(reinterpret_cast(data.data()), "aap"), "Incorrect data read"); + + uint8_t tmp[4]; + tmp[3] = 0; + Assert(bus.read(1, tmp, 3) == 0, "Expected no data available for read"); + + std::vector tmp2; + tmp2.push_back(uint8_t('p')); + tmp2.push_back(uint8_t('i')); + tmp2.push_back(uint8_t('m')); + + Wire.Send(tmp2); + Assert(bus.read(1, tmp, 3) == 3, "Expected 3 bytes data available for read"); + Assert(bus.read(1, tmp, 3) == 0, "Expected no data available for read"); + Assert(!strcmp(reinterpret_cast(tmp), "pim"), "Incorrect data read"); + } + + // Helper class for initialization of I2C extender + class FakeInitHandler : public Configuration::HandlerBase { + bool hasISR_; + + protected: + void enterSection(const char* name, Configurable* value) override {} + bool matchesUninitialized(const char* name) override { return true; } + + public: + FakeInitHandler(bool hasISR) : hasISR_(hasISR) {} + + void item(const char* name, float& value, float minValue = -3e38, float maxValue = 3e38) override {} + void item(const char* name, std::vector& value) override {} + void item(const char* name, UartData& wordLength, UartParity& parity, UartStop& stopBits) override {} + void item(const char* name, Pin& value) override { + if (!strcmp(name, "interrupt") && hasISR_) { + value = Pin::create("gpio.15"); + } + } + void item(const char* name, IPAddress& value) override {} + void item(const char* name, int& value, EnumItem* e) override { + if (!strcmp(name, "device")) { + value = int(Extenders::I2CExtenderDevice::PCA9539); + } + } + + void item(const char* name, String& value, int minLength = 0, int maxLength = 255) override {} + + HandlerType handlerType() override { return HandlerType::Parser; } + + void item(const char* name, bool& value) override {} + void item(const char* name, int32_t& value, int32_t minValue = 0, int32_t maxValue = INT32_MAX) override { + if (!strcmp(name, "device_id")) { + value = 0; + } + } + }; + + Test(I2CExtender, InitDeinit) { + std::lock_guard guard(single_thread); + + PCA9539Emulator pca(-1); + + // Initialize I2C bus + Machine::I2CBus bus; + bus._sda = Pin::create("gpio.16"); + bus._scl = Pin::create("gpio.17"); + bus._frequency = 100000; + bus._busNumber = 0; + bus.init(); + + // We need to set up the I2C config in the global 'config', or init of the extender will fail. + Machine::MachineConfig mconfig; + mconfig._i2c = &bus; + config = &mconfig; + + Wire.Clear(); + Wire.SetResponseHandler(PCA9539Emulator::wireResponseHandler, &pca); + + // Setup the extender + Extenders::I2CExtender i2c; + FakeInitHandler fakeInit(false); + i2c.group(fakeInit); + i2c.validate(); + i2c.init(); + } + + Test(I2CExtender, ClaimRelease) { + std::lock_guard guard(single_thread); + PCA9539Emulator pca(-1); + + // Initialize I2C bus + Machine::I2CBus bus; + bus._sda = Pin::create("gpio.16"); + bus._scl = Pin::create("gpio.17"); + bus._frequency = 100000; + bus._busNumber = 0; + bus.init(); + + // We need to set up the I2C config in the global 'config', or init of the extender will fail. + Machine::MachineConfig mconfig; + mconfig._i2c = &bus; + config = &mconfig; + + Wire.Clear(); + Wire.SetResponseHandler(PCA9539Emulator::wireResponseHandler, &pca); + + // Setup the extender + Extenders::I2CExtender i2c; + FakeInitHandler fakeInit(false); + i2c.group(fakeInit); + i2c.validate(); + i2c.init(); + + i2c.claim(1); + i2c.claim(0); + AssertThrow(i2c.claim(1)); + i2c.claim(2); + AssertThrow(i2c.claim(64)); + AssertThrow(i2c.claim(-1)); + i2c.free(1); + i2c.free(1); + i2c.claim(1); + AssertThrow(i2c.claim(1)); + i2c.free(0); + i2c.free(1); + i2c.free(2); + } + + Test(I2CExtender, ExtenderNoInterrupt) { + std::lock_guard guard(single_thread); + PCA9539Emulator pca(-1); + + // Initialize I2C bus + Machine::I2CBus bus; + bus._sda = Pin::create("gpio.16"); + bus._scl = Pin::create("gpio.17"); + bus._frequency = 100000; + bus._busNumber = 0; + bus.init(); + + // We need to set up the I2C config in the global 'config', or init of the extender will fail. + Machine::MachineConfig mconfig; + mconfig._i2c = &bus; + config = &mconfig; + + Wire.Clear(); + Wire.SetResponseHandler(PCA9539Emulator::wireResponseHandler, &pca); + + // Setup the extender + Extenders::I2CExtender i2c; + FakeInitHandler fakeInit(false); + i2c.group(fakeInit); + i2c.validate(); + i2c.init(); + + { + // Setup will trigger some events on I2C: 'config', 'invert', 'write', 'read'. + + i2c.claim(0); + i2c.setupPin(0, Pin::Attr::Output); + + // Wait until synced (should be immediate after the thread gets some cpu) and check I2C comms: + { Roundtrip rt; } + + // Check PCA values: + Assert(pca.registersUsed() == 0x55, "Expected invert, config, write, read bytes being used"); + Assert(!pca.getPadValue(0)); + } + + // Read will trigger an update, because we don't have an ISR + { + bool readPin = i2c.readPin(0); + { Roundtrip rt; } + Assert(pca.registersUsed() == 0x01, "Expected roundtrip for read / no ISR"); + Assert(readPin == false, "Expected 'true' on pin"); + } + + // Test write pin: + { + // Write to set it 'high'. + i2c.writePin(0, true); + i2c.flushWrites(); + { Roundtrip rt; } + Assert(pca.registersUsed() == 0x04, "Expected roundtrip for write / no ISR"); + Assert(pca.getPadValue(0), "Expected pad to be 'true'"); + } + { + // Write to set it 'low'. + i2c.writePin(0, false); + i2c.flushWrites(); + { Roundtrip rt; } + Assert(pca.registersUsed() == 0x04, "Expected roundtrip for write / no ISR"); + Assert(!pca.getPadValue(0), "Expected pad to be 'false'"); + } + { + // Write to set it 'low'. It's already low, no-op. + i2c.writePin(0, false); + i2c.flushWrites(); + // no-op. + Assert(pca.registersUsed() == 0x00, "Expected roundtrip for write / no ISR"); + Assert(!pca.getPadValue(0), "Expected pad to be 'false'"); + } + { + // Write to set it 'high'. + i2c.writePin(0, true); + i2c.flushWrites(); + { Roundtrip rt; } + auto recv = Wire.Receive(); + + Assert(pca.registersUsed() == 0x04, "Expected roundtrip for write / no ISR"); + Assert(pca.getPadValue(0), "Expected pad to be 'false'"); + } + + // NOTE: We ended with setting pin #0 to 'high' = 0x01 + + // Setup pin for reading: + { + i2c.claim(1); + i2c.setupPin(1, Pin::Attr::Input); + + // Wait until synced (should be immediate after the thread gets some cpu) and check I2C comms: + { Roundtrip rt; } + + Assert(pca.registersUsed() == 0x55, "Expected invert, config, write, read bytes being used"); + Assert(pca.getPadValue(0)); + Assert(!pca.getPadValue(1)); + } + + // Setup another pin for reading with an invert mask and a PU: + { + i2c.claim(2); + i2c.setupPin(2, Pin::Attr::Input | Pin::Attr::ActiveLow | Pin::Attr::PullUp); + + // Wait until synced (should be immediate after the thread gets some cpu) and check I2C comms: + { Roundtrip rt; } + + Assert(pca.registersUsed() == 0x55, "Expected invert, config, write, read bytes being used"); + Assert(pca.getPadValue(0)); + Assert(!pca.getPadValue(1)); + Assert(!pca.getPadValue(2)); + } + + // Test read pin: + { + bool readPin = i2c.readPin(1); + { Roundtrip rt; } + + Assert(pca.registersUsed() == 0x01, "Expected invert, config, write, read bytes being used"); + Assert(readPin == false); + } + + // Test read pin: + { + bool readPin = i2c.readPin(2); + { Roundtrip rt; } + + Assert(pca.registersUsed() == 0x01, "Expected invert, config, write, read bytes being used"); + Assert(readPin == true, "Expected 'true' on pin"); + } + + pca.setPadValue(1, true); + pca.setPadValue(2, true); + + // Test read pin: + { + bool readPin = i2c.readPin(1); + { Roundtrip rt; } + + Assert(pca.registersUsed() == 0x01, "Expected invert, config, write, read bytes being used"); + Assert(readPin == true); + } + + // Test read pin: + { + bool readPin = i2c.readPin(2); + { Roundtrip rt; } + + Assert(pca.registersUsed() == 0x01, "Expected invert, config, write, read bytes being used"); + Assert(readPin == false, "Expected 'true' on pin"); + } + } + + Test(I2CExtender, ExtenderWithInterrupt) { + std::lock_guard guard(single_thread); + GPIONative::initialize(); + PCA9539Emulator pca(15); + + // Initialize I2C bus + Machine::I2CBus bus; + bus._sda = Pin::create("gpio.16"); + bus._scl = Pin::create("gpio.17"); + bus._frequency = 100000; + bus._busNumber = 0; + bus.init(); + + // We need to set up the I2C config in the global 'config', or init of the extender will fail. + Machine::MachineConfig mconfig; + mconfig._i2c = &bus; + config = &mconfig; + + Wire.Clear(); + Wire.SetResponseHandler(PCA9539Emulator::wireResponseHandler, &pca); + + // Setup the extender with ISR on gpio.15 + Extenders::I2CExtender i2c; + FakeInitHandler fakeInit(true); + i2c.group(fakeInit); + i2c.validate(); + i2c.init(); + + { + i2c.claim(0); + i2c.setupPin(0, Pin::Attr::Output); + { Roundtrip rt; } + + Assert(pca.registersUsed() == 0x55, "Expected invert, config, write, read bytes being used"); + } + + // Read will NOT trigger an update because we have an ISR to tell us when it changes: + { + bool readPin = i2c.readPin(0); + Assert(readPin == false, "Expected 'false' on pin"); + Assert(pca.registersUsed() == 0, "Expected no-op for read"); + } + + // Test write pin: + { + // Write to set it 'high'. + i2c.writePin(0, true); + i2c.flushWrites(); + { Roundtrip rt; } + Assert(pca.registersUsed() == 0x04, "Expected no-op for read"); + } + { + // Write to set it 'low'. + i2c.writePin(0, false); + i2c.flushWrites(); + { Roundtrip rt; } + Assert(pca.registersUsed() == 0x04); + } + { + // Write to set it 'low'. It's already low, no-op. + i2c.writePin(0, false); + i2c.flushWrites(); + // no-op. + + Assert(pca.registersUsed() == 0, "Expected no-op"); + } + { + // Write to set it 'high'. + i2c.writePin(0, true); + i2c.flushWrites(); + { Roundtrip rt; } + Assert(pca.registersUsed() == 0x04); + } + + // NOTE: We ended with setting pin #0 to 'high' = 0x01 + + // Setup pin for reading: + { + i2c.claim(1); + i2c.setupPin(1, Pin::Attr::Input); + + // Wait until synced (should be immediate after the thread gets some cpu) and check I2C comms: + { Roundtrip rt; } + Assert(pca.registersUsed() == 0x55); + } + + // Setup another pin for reading with an invert mask and a PU: + { + i2c.claim(2); + i2c.setupPin(2, Pin::Attr::Input | Pin::Attr::ActiveLow | Pin::Attr::PullUp); + + // Wait until synced (should be immediate after the thread gets some cpu) and check I2C comms: + { Roundtrip rt; } + Assert(pca.registersUsed() == 0x55); + } + + // Test read pin: + { + bool readPin = i2c.readPin(1); + { Roundtrip rt; } + Assert(pca.registersUsed() == 0x0); + Assert(readPin == false, "Expected 'true' on pin"); + } + + // Test read pin: + { + bool readPin = i2c.readPin(2); + { Roundtrip rt; } + Assert(pca.registersUsed() == 0x0); + Assert(readPin == true, "Expected 'true' on pin"); + } + + // Trigger an ISR, change both pins + { + pca.setPadValue(1, true); + pca.setPadValue(2, true); + { Roundtrip rt; } + Assert(pca.registersUsed() == 0x01); + } + + // Test read pin: + { + bool readPin = i2c.readPin(1); + Assert(pca.registersUsed() == 0x0); + Assert(readPin == true, "Expected 'true' on pin"); + } + + // Test read pin: + { + bool readPin = i2c.readPin(2); + Assert(pca.registersUsed() == 0x0); + Assert(readPin == false, "Expected 'true' on pin"); + } + } + + void HandleInterrupt(void* data) { ++(*reinterpret_cast(data)); } + + Test(I2CExtender, ISRTriggerWithInterrupt) { + std::lock_guard guard(single_thread); + GPIONative::initialize(); + PCA9539Emulator pca(15); + + // Initialize I2C bus + Machine::I2CBus bus; + bus._sda = Pin::create("gpio.16"); + bus._scl = Pin::create("gpio.17"); + bus._frequency = 100000; + bus._busNumber = 0; + bus.init(); + + // We need to set up the I2C config in the global 'config', or init of the extender will fail. + Machine::MachineConfig mconfig; + mconfig._i2c = &bus; + config = &mconfig; + + Wire.Clear(); + Wire.SetResponseHandler(PCA9539Emulator::wireResponseHandler, &pca); + + // Setup the extender + Extenders::I2CExtender i2c; + FakeInitHandler fakeInit(true); + i2c.group(fakeInit); + i2c.validate(); + i2c.init(); + + { + i2c.claim(9); + i2c.setupPin(9, Pin::Attr::Input | Pin::Attr::ISR); + + // Wait until synced (should be immediate after the thread gets some cpu) and check I2C comms: + { Roundtrip rt; } + + auto regUsed = pca.registersUsed(); + Assert(regUsed >= 0xfd && regUsed <= 0xFF); + } + + uint32_t isrCounter = 0; + + { + i2c.attachInterrupt(9, HandleInterrupt, &isrCounter, CHANGE); + + // Wait until synced (should be immediate after the thread gets some cpu) and check I2C comms: + { Roundtrip rt; } + + auto regUsed = pca.registersUsed(); + Assert(regUsed >= 0xfd && regUsed <= 0xFF); + } + + { Roundtrip rt; } + + // Test read pin: + { + bool readPin = i2c.readPin(9); + Assert(readPin == false, "Expected 'true' on pin"); + Assert(pca.registersUsed() == 0x00); + } + + // Change state, wait till roundtrip + { + pca.setPadValue(9, true); + { Roundtrip rt; } + + // Test if ISR update went correctly: + Assert(isrCounter == 1); + Assert(pca.registersUsed() == 0x03); + + // Test read pin: + bool readPin = i2c.readPin(9); + Assert(readPin == true, "Expected 'true' on pin"); + Assert(pca.registersUsed() == 0x00); + } + + { + i2c.detachInterrupt(9); + + // Wait until synced (should be immediate after the thread gets some cpu) and check I2C comms: + { Roundtrip rt; } + + auto regUsed = pca.registersUsed(); + Assert(regUsed >= 0xfd && regUsed <= 0xFF); + } + + // Change state, wait till roundtrip + { + pca.setPadValue(9, false); + { Roundtrip rt; } + + // Test if ISR detach went correctly: + Assert(isrCounter == 1); + Assert(pca.registersUsed() == 0x03); + } + } + + Test(I2CExtender, ISRTriggerWithoutInterrupt) { + std::lock_guard guard(single_thread); + GPIONative::initialize(); + PCA9539Emulator pca(15); + + // Initialize I2C bus + Machine::I2CBus bus; + bus._sda = Pin::create("gpio.16"); + bus._scl = Pin::create("gpio.17"); + bus._frequency = 100000; + bus._busNumber = 0; + bus.init(); + + // We need to set up the I2C config in the global 'config', or init of the extender will fail. + Machine::MachineConfig mconfig; + mconfig._i2c = &bus; + config = &mconfig; + + Wire.Clear(); + Wire.SetResponseHandler(PCA9539Emulator::wireResponseHandler, &pca); + + // Setup the extender + Extenders::I2CExtender i2c; + FakeInitHandler fakeInit(false); + i2c.group(fakeInit); + i2c.validate(); + i2c.init(); + + { + i2c.claim(9); + i2c.setupPin(9, Pin::Attr::Input | Pin::Attr::ISR); + + // Wait until synced (should be immediate after the thread gets some cpu) and check I2C comms: + { Roundtrip rt; } + + auto regUsed = pca.registersUsed(); + Assert(regUsed >= 0xfd && regUsed <= 0xFF); + } + + uint32_t isrCounter = 0; + + { + // From this point on, we just need to respond to wire requests + i2c.attachInterrupt(9, HandleInterrupt, &isrCounter, CHANGE); + } + + // Test read pin: + { + bool readPin = i2c.readPin(9); + Assert(readPin == false, "Expected 'true' on pin"); + } + + // Change state, wait till roundtrip + { + pca.setPadValue(9, true); + + { Roundtrip rt; } + + // Test if ISR update went correctly: + Assert(isrCounter == 1); + + // Test read pin: + bool readPin = i2c.readPin(9); + Assert(readPin == true, "Expected 'true' on pin"); + } + + { + pca.setPadValue(9, false); + + { Roundtrip rt; } + + // Test if ISR update went correctly: + Assert(isrCounter == 2); + + // Test read pin: + bool readPin2 = i2c.readPin(9); + Assert(readPin2 == false, "Expected 'false' on pin"); + } + + { + i2c.detachInterrupt(9); + + // Wait until synced (should be immediate after the thread gets some cpu) and check I2C comms: + { Roundtrip rt; } + + auto regUsed = pca.registersUsed(); + Assert(regUsed >= 0xfd && regUsed <= 0xFF); + } + + // Change state, wait till roundtrip + { + pca.setPadValue(9, false); + { Roundtrip rt; } + + // Test if ISR detach went correctly: + Assert(isrCounter == 2); + } + } + + void ReadInISRHandler(void* data) { + auto i2c = static_cast(data); + auto value = i2c->readPin(9); + Assert(value == true); + } + + Test(I2CExtender, ReadInISR) { + std::lock_guard guard(single_thread); + GPIONative::initialize(); + PCA9539Emulator pca(15); + + // Initialize I2C bus + Machine::I2CBus bus; + bus._sda = Pin::create("gpio.16"); + bus._scl = Pin::create("gpio.17"); + bus._frequency = 100000; + bus._busNumber = 0; + bus.init(); + + // We need to set up the I2C config in the global 'config', or init of the extender will fail. + Machine::MachineConfig mconfig; + mconfig._i2c = &bus; + config = &mconfig; + + Wire.Clear(); + Wire.SetResponseHandler(PCA9539Emulator::wireResponseHandler, &pca); + + // Setup the extender + Extenders::I2CExtender i2c; + FakeInitHandler fakeInit(false); + i2c.group(fakeInit); + i2c.validate(); + i2c.init(); + + { + i2c.claim(9); + i2c.setupPin(9, Pin::Attr::Input | Pin::Attr::ISR); + + // Wait until synced (should be immediate after the thread gets some cpu) and check I2C comms: + { Roundtrip rt; } + + auto regUsed = pca.registersUsed(); + Assert(regUsed >= 0xfd && regUsed <= 0xFF); + } + + { + pca.setPadValue(9, false); + i2c.attachInterrupt(9, ReadInISRHandler, &i2c, CHANGE); + pca.setPadValue(9, true); + i2c.detachInterrupt(9); + pca.setPadValue(9, false); + } + } +} diff --git a/FluidNC/test/Pins/GPIO.cpp b/FluidNC/test/Pins/GPIO.cpp index c17acdde3..e9dd09635 100644 --- a/FluidNC/test/Pins/GPIO.cpp +++ b/FluidNC/test/Pins/GPIO.cpp @@ -8,38 +8,41 @@ extern "C" void __pinMode(uint8_t pin, uint8_t mode); extern "C" int __digitalRead(uint8_t pin); extern "C" void __digitalWrite(uint8_t pin, uint8_t val); -struct GPIONative { - inline static void initialize() { - for (int i = 16; i <= 17; ++i) { - __pinMode(i, OUTPUT); - __digitalWrite(i, LOW); +namespace { + struct GPIONative { + inline static void initialize() { + for (int i = 16; i <= 17; ++i) { + __pinMode(i, OUTPUT); + __digitalWrite(i, LOW); + } } - } - inline static void mode(int pin, uint8_t mode) { __pinMode(pin, mode); } - inline static void write(int pin, bool val) { __digitalWrite(pin, val ? HIGH : LOW); } - inline static bool read(int pin) { return __digitalRead(pin) != LOW; } -}; + inline static void mode(int pin, uint8_t mode) { __pinMode(pin, mode); } + inline static void write(int pin, bool val) { __digitalWrite(pin, val ? HIGH : LOW); } + inline static bool read(int pin) { return __digitalRead(pin) != LOW; } + }; +} #else # include -struct GPIONative { - // We test GPIO pin 16 and 17, and GPIO 16 is wired directly to 17: - static void WriteVirtualCircuitHystesis(SoftwarePin* pins, int pin, bool value) { - switch (pin) { - case 16: - case 17: - pins[16].handlePadChange(value); - pins[17].handlePadChange(value); - break; +namespace { + struct GPIONative { + // We test GPIO pin 16 and 17, and GPIO 16 is wired directly to 17: + static void WriteVirtualCircuitHystesis(SoftwarePin* pins, int pin, bool value) { + switch (pin) { + case 16: + case 17: + pins[16].handlePadChange(value); + pins[17].handlePadChange(value); + break; + } } - } - - inline static void initialize() { SoftwareGPIO::instance().reset(WriteVirtualCircuitHystesis, false); } - inline static void mode(int pin, uint8_t mode) { SoftwareGPIO::instance().setMode(pin, mode); } - inline static void write(int pin, bool val) { SoftwareGPIO::instance().writeOutput(pin, val); } - inline static bool read(int pin) { return SoftwareGPIO::instance().read(pin); } -}; + inline static void initialize() { SoftwareGPIO::instance().reset(WriteVirtualCircuitHystesis, false); } + inline static void mode(int pin, uint8_t mode) { SoftwareGPIO::instance().setMode(pin, mode); } + inline static void write(int pin, bool val) { SoftwareGPIO::instance().writeOutput(pin, val); } + inline static bool read(int pin) { return SoftwareGPIO::instance().read(pin); } + }; +} void digitalWrite(uint8_t pin, uint8_t val); void pinMode(uint8_t pin, uint8_t mode); int digitalRead(uint8_t pin); @@ -301,7 +304,7 @@ namespace Pins { hitCount = 0; int expected = 0; - gpio16.attachInterrupt(this, mode); + // gpio16.attachInterrupt(this, mode); // Two ways to set I/O: // 1. using on/off diff --git a/UnitTests.vcxproj b/UnitTests.vcxproj index 1f9d64333..f74585854 100644 --- a/UnitTests.vcxproj +++ b/UnitTests.vcxproj @@ -33,8 +33,15 @@ + + + + + + + @@ -97,7 +104,6 @@ - @@ -176,6 +182,7 @@ + @@ -206,10 +213,17 @@ + + + + + + + @@ -223,6 +237,7 @@ + @@ -240,7 +255,6 @@ - @@ -265,6 +279,7 @@ + @@ -305,7 +320,6 @@ - @@ -347,6 +361,7 @@ + diff --git a/UnitTests.vcxproj.filters b/UnitTests.vcxproj.filters index 34ac5140a..716078b48 100644 --- a/UnitTests.vcxproj.filters +++ b/UnitTests.vcxproj.filters @@ -46,6 +46,12 @@ {dc4ba3bc-4342-4a67-aea2-f68877f1ee69} + + {2b9b9202-4bb3-450e-a90b-99f042de0af2} + + + {4f2af482-2e54-41bf-b698-1ab77a1a5ea3} + @@ -225,9 +231,6 @@ src\Configuration - - src - src\Motors @@ -573,6 +576,33 @@ X86TestSupport + + X86TestSupport + + + src + + + src\Machine + + + src\Extenders + + + src\Extenders + + + src\Extenders + + + src\Pins + + + X86TestSupport + + + src\Extenders + @@ -665,9 +695,6 @@ src\Configuration - - src - src\StackTrace @@ -815,9 +842,6 @@ src\Spindles - - src - src\Machine @@ -989,6 +1013,33 @@ X86TestSupport + + src + + + src + + + src\Machine + + + src\Extenders + + + src\Extenders + + + X86TestSupport + + + src\Pins + + + src\Extenders + + + test\Extender + diff --git a/X86TestSupport/TestSupport/Capture.h b/X86TestSupport/TestSupport/Capture.h index f8d3be1df..e9ce30a7c 100644 --- a/X86TestSupport/TestSupport/Capture.h +++ b/X86TestSupport/TestSupport/Capture.h @@ -21,7 +21,7 @@ class Capture { Capture() = default; std::vector events; - uint32_t currentTime = 0; + volatile uint32_t currentTime = 0; public: static Capture& instance() { diff --git a/X86TestSupport/TestSupport/SoftwareGPIO.h b/X86TestSupport/TestSupport/SoftwareGPIO.h index 061578f9b..7332c1c98 100644 --- a/X86TestSupport/TestSupport/SoftwareGPIO.h +++ b/X86TestSupport/TestSupport/SoftwareGPIO.h @@ -128,7 +128,7 @@ class SoftwareGPIO { pins[index].handlePadChangeWithHystesis(value); } } else { - pins[index].driverValue = value; + pins[index].handlePadChange(value); } } diff --git a/X86TestSupport/TestSupport/Wire.cpp b/X86TestSupport/TestSupport/Wire.cpp new file mode 100644 index 000000000..35ff5cdad --- /dev/null +++ b/X86TestSupport/TestSupport/Wire.cpp @@ -0,0 +1,170 @@ +#include "Wire.h" + +#include + +TwoWire Wire(0); +TwoWire Wire1(1); + +TwoWire::TwoWire(uint8_t bus_num) : handler(nullptr) {} + +// For unit tests: +void TwoWire::Send(std::vector data) { + for (auto it : data) { + Send(it); + } +} +void TwoWire::Send(uint8_t value) { + std::lock_guard guard(mut); + receivedData.push_back(value); +} +std::vector TwoWire::Receive() { + std::lock_guard guard(mut); + std::vector data; + std::swap(sentData, data); + return data; +} + +void TwoWire::Clear() { + std::lock_guard guard(mut); + sentData.clear(); + receivedData.clear(); + handler = nullptr; + handlerUserData = nullptr; +} + +// TwoWire interface: + +// call setPins() first, so that begin() can be called without arguments from libraries +bool TwoWire::setPins(int sda, int scl) { + return true; +} + +bool TwoWire::begin(int sda, int scl, uint32_t frequency) { + return true; +} // returns true, if successful init of i2c bus + +bool TwoWire::begin(uint8_t slaveAddr, int sda, int scl, uint32_t frequency) { + return true; +} +bool TwoWire::end() { + return true; +} + +void TwoWire::setTimeOut(uint16_t timeOutMillis) {} // default timeout of i2c transactions is 50ms +uint16_t TwoWire::getTimeOut() { + return 0; +} + +bool TwoWire::setClock(uint32_t) { + return true; +} +uint32_t TwoWire::getClock() { + return 0; +} + +void TwoWire::beginTransmission(uint16_t address) { + Assert(!inTransmission, "Already in a transmission"); + inTransmission = true; +} +void TwoWire::beginTransmission(uint8_t address) { + Assert(!inTransmission, "Already in a transmission"); + inTransmission = true; +} +void TwoWire::beginTransmission(int address) { + Assert(!inTransmission, "Already in a transmission"); + inTransmission = true; +} + +uint8_t TwoWire::endTransmission(bool sendStop) { + Assert(inTransmission, "Should be in a transmission"); + inTransmission = false; + return 0; +} +uint8_t TwoWire::endTransmission(void) { + Assert(inTransmission, "Should be in a transmission"); + inTransmission = false; + return 0; +} + +size_t TwoWire::requestFrom(uint16_t address, size_t size, bool sendStop) { + std::lock_guard guard(mut); + + auto available = receivedData.size(); + if (available > size) { + available = size; + } + return available; +} +uint8_t TwoWire::requestFrom(uint16_t address, uint8_t size, bool sendStop) { + return uint8_t(requestFrom(address, size_t(size), sendStop)); +} +uint8_t TwoWire::requestFrom(uint16_t address, uint8_t size, uint8_t sendStop) { + return uint8_t(requestFrom(address, size_t(size), sendStop != 0)); +} +size_t TwoWire::requestFrom(uint8_t address, size_t len, bool stopBit) { + return uint8_t(requestFrom(uint16_t(address), size_t(len), stopBit)); +} +uint8_t TwoWire::requestFrom(uint16_t address, uint8_t size) { + return uint8_t(requestFrom(address, size_t(size), false)); +} +uint8_t TwoWire::requestFrom(uint8_t address, uint8_t size, uint8_t sendStop) { + return uint8_t(requestFrom(address, size_t(size), sendStop != 0)); +} +uint8_t TwoWire::requestFrom(uint8_t address, uint8_t size) { + return uint8_t(requestFrom(address, size_t(size), false)); +} +uint8_t TwoWire::requestFrom(int address, int size, int sendStop) { + return uint8_t(requestFrom(uint16_t(address), size_t(size), sendStop != 0)); +} +uint8_t TwoWire::requestFrom(int address, int size) { + return uint8_t(requestFrom(uint16_t(address), size_t(size), false)); +} + +size_t TwoWire::write(uint8_t ch) { + { + Assert(inTransmission, "Should be in a transmission"); + std::lock_guard guard(mut); + sentData.push_back(ch); + } + + if (handler) { + (*handler)(this, sentData, handlerUserData); + } + return 0; +} + +size_t TwoWire::write(const uint8_t* buf, size_t size) { + for (size_t i = 0; i < size; ++i) { + write(buf[i]); + } + return size; +} +int TwoWire::available(void) { + std::lock_guard guard(mut); + return receivedData.size(); +} +int TwoWire::read(void) { + std::lock_guard guard(mut); + if (receivedData.size()) { + auto result = receivedData[0]; + receivedData.erase(receivedData.begin()); + return result; + } else { + return -1; + } +} +int TwoWire::peek(void) { + std::lock_guard guard(mut); + if (receivedData.size()) { + auto result = receivedData[0]; + return result; + } else { + return -1; + } +} +void TwoWire::flush(void) {} + +TwoWire::~TwoWire() {} + +extern TwoWire Wire; +extern TwoWire Wire1; diff --git a/X86TestSupport/TestSupport/Wire.h b/X86TestSupport/TestSupport/Wire.h new file mode 100644 index 000000000..866597748 --- /dev/null +++ b/X86TestSupport/TestSupport/Wire.h @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include + +#include "esp32-hal-i2c.h" +#include "Stream.h" +#include "../FluidNC/test/TestFramework.h" + +class TwoWire : public Stream { + bool inTransmission = false; + std::vector receivedData; + std::vector sentData; + std::mutex mut; + + using ResponseHandler = void (*)(TwoWire* theWire, std::vector& data, void* userData); + void* handlerUserData; + ResponseHandler handler; + +public: + TwoWire(uint8_t bus_num); + ~TwoWire(); + + // For unit tests: + void Send(std::vector data); + void Send(uint8_t value); + size_t SendSize() { return receivedData.size(); } + std::vector Receive(); + size_t ReceiveSize() { return sentData.size(); } + void Clear(); + void SetResponseHandler(ResponseHandler handler, void* userData) { + this->handlerUserData = userData; + this->handler = handler; + } + + // TwoWire interface: + + // call setPins() first, so that begin() can be called without arguments from libraries + bool setPins(int sda, int scl); + + bool begin(int sda = -1, int scl = -1, uint32_t frequency = 0); // returns true, if successful init of i2c bus + bool begin(uint8_t slaveAddr, int sda = -1, int scl = -1, uint32_t frequency = 0); + bool end(); + + void setTimeOut(uint16_t timeOutMillis); // default timeout of i2c transactions is 50ms + uint16_t getTimeOut(); + + bool setClock(uint32_t); + uint32_t getClock(); + + void beginTransmission(uint16_t address); + void beginTransmission(uint8_t address); + void beginTransmission(int address); + + uint8_t endTransmission(bool sendStop); + uint8_t endTransmission(void); + + size_t requestFrom(uint16_t address, size_t size, bool sendStop); + uint8_t requestFrom(uint16_t address, uint8_t size, bool sendStop); + uint8_t requestFrom(uint16_t address, uint8_t size, uint8_t sendStop); + size_t requestFrom(uint8_t address, size_t len, bool stopBit); + uint8_t requestFrom(uint16_t address, uint8_t size); + uint8_t requestFrom(uint8_t address, uint8_t size, uint8_t sendStop); + uint8_t requestFrom(uint8_t address, uint8_t size); + uint8_t requestFrom(int address, int size, int sendStop); + uint8_t requestFrom(int address, int size); + + size_t write(uint8_t ch); + size_t write(const uint8_t* buf, size_t size); + int available(void); + int read(void); + int peek(void); + void flush(void); + + inline size_t write(const char* s) { return write((uint8_t*)s, strlen(s)); } + inline size_t write(unsigned long n) { return write((uint8_t)n); } + inline size_t write(long n) { return write((uint8_t)n); } + inline size_t write(unsigned int n) { return write((uint8_t)n); } + inline size_t write(int n) { return write((uint8_t)n); } +}; + +extern TwoWire Wire; +extern TwoWire Wire1; diff --git a/X86TestSupport/TestSupport/driver/uart.h b/X86TestSupport/TestSupport/driver/uart.h index 9b7192685..8efa6add0 100644 --- a/X86TestSupport/TestSupport/driver/uart.h +++ b/X86TestSupport/TestSupport/driver/uart.h @@ -77,6 +77,8 @@ typedef struct { bool use_ref_tick; /*!< Set to true if UART should be clocked from REF_TICK */ } uart_config_t; +const int UART_FIFO_LEN = 128; + esp_err_t uart_flush(uart_port_t uart_num); esp_err_t uart_param_config(uart_port_t uart_num, const uart_config_t* uart_config); esp_err_t uart_driver_install( diff --git a/X86TestSupport/TestSupport/esp32-hal-i2c.h b/X86TestSupport/TestSupport/esp32-hal-i2c.h new file mode 100644 index 000000000..066108aad --- /dev/null +++ b/X86TestSupport/TestSupport/esp32-hal-i2c.h @@ -0,0 +1,13 @@ +#pragma once + +typedef enum { + I2C_ERROR_OK = 0, + I2C_ERROR_DEV, + I2C_ERROR_ACK, + I2C_ERROR_TIMEOUT, + I2C_ERROR_BUS, + I2C_ERROR_BUSY, + I2C_ERROR_MEMORY, + I2C_ERROR_CONTINUE, + I2C_ERROR_NO_BEGIN +} i2c_err_t; diff --git a/X86TestSupport/TestSupport/freertos/FreeRTOS.h b/X86TestSupport/TestSupport/freertos/FreeRTOS.h index 6b20e1612..11df765a5 100644 --- a/X86TestSupport/TestSupport/freertos/FreeRTOS.h +++ b/X86TestSupport/TestSupport/freertos/FreeRTOS.h @@ -29,3 +29,5 @@ inline void vTaskEnterCritical(portMUX_TYPE* mux) { inline int32_t xPortGetFreeHeapSize() { return 1024 * 1024 * 4; } + +#define configMINIMAL_STACK_SIZE 768 diff --git a/X86TestSupport/TestSupport/freertos/Task.cpp b/X86TestSupport/TestSupport/freertos/Task.cpp index 8dd60b5c6..05036f34c 100644 --- a/X86TestSupport/TestSupport/freertos/Task.cpp +++ b/X86TestSupport/TestSupport/freertos/Task.cpp @@ -27,6 +27,7 @@ BaseType_t xTaskCreatePinnedToCore(TaskFunction_t pvTaskCode, void vTaskDelay(const TickType_t xTicksToDelay) { Capture::instance().wait(xTicksToDelay); + delay(xTicksToDelay); } void vTaskDelayUntil(TickType_t* const pxPreviousWakeTime, const TickType_t xTimeIncrement) { diff --git a/install_scripts/README-ESPTOOL-SOURCE.txt b/install_scripts/README-ESPTOOL-SOURCE.txt new file mode 100644 index 000000000..7bfa3fa66 --- /dev/null +++ b/install_scripts/README-ESPTOOL-SOURCE.txt @@ -0,0 +1,2 @@ +The esptool source package was downloaded from the Espressif release files at +https://github.com/espressif/esptool . diff --git a/install_scripts/SecurityFusesOK0.bin b/install_scripts/SecurityFusesOK0.bin new file mode 100644 index 000000000..593f4708d Binary files /dev/null and b/install_scripts/SecurityFusesOK0.bin differ diff --git a/install_scripts/linux-python3/HOWTO-INSTALL.txt b/install_scripts/linux-python3/HOWTO-INSTALL.txt new file mode 100644 index 000000000..a381e3b24 --- /dev/null +++ b/install_scripts/linux-python3/HOWTO-INSTALL.txt @@ -0,0 +1,149 @@ +## Installing FluidNC on your ESP32 + +_Please note: These instructions apply to the case where you have +downloaded a release bundle from +https://github.com/bdring/FluidNC/releases - a zip file named +fluidnc-vN.N.N-linux-python3.zip . They do not apply to installing from +a source tree. This HOWTO file is present in the source tree so that +it can be packed into a release bundle by automated release scripting, +but it does not tell you how to install from source. For that, visit +https://github.com/bdring/FluidNC/wiki/FluidNC-Compiling#use-vs-code--platformio-to-compile +_ + +These install scripts use Python3 and the Python version of esptool. +https://docs.espressif.com/projects/esptool/en/latest/esp32/ + +They are suitable for any system supporting esptool.py +eg. non-intel (arm) linux systems, such as Raspberry PI's or +legacy machines running 32bit distributions. + +Most Linux distributions will already have Python3 +preinstalled. If you have problems, search the web for: +"install python3 on " +replacing with the name and version of your operating system. + +The script will attempt to install esptool.py if it is not already +present, if this fails please see the esptool documentation and +forums for support. +https://docs.espressif.com/projects/esptool/en/latest/esp32/installation.html +https://www.esp32.com/viewforum.php?f=23 +_ + +Unpack this FluidNC release Zip file. + +Plug your ESP32 into a USB port. + +At a shell prompt, cd to the directory that contains the file +you are reading and run one of the following commands: + +To install the WiFi version: sh install-wifi.sh +To install the Bluetooth version: sh install-bt.sh +To replace the ESP32 local filesystem: sh install-fs.sh + +### Group membership Considerations + +Your user should be a member of the `dialup` group in order to access +the USB serial device, you can use `id -a` to see your current +groups. To add yourself to the 'dialup' group you need to use: + + sudo usermod -a -G dialup + +### Local Filesystem Considerations + +Replacing the local filesystem is only useful for the wifi version. +The Bluetooth version can start with an empty local filesystem. + +The disadvantage of replacing the local filesystem is that it will +overwrite any files that you might already have there, such as FluidNC +config files, WebUI preferences and macros. The advantage is that you +will get the latest version of the index.html.gz file that contains +the WebUI code. Another way to get a new index.html.gz is to upload +it via WebUI from wifi/index.html.gz herein. + +A good approach is to use install-fs only on your first FluidNC +installation, to start from a clean slate. + +### Running Fluidterm + +The FluidNC install scripts run Fluidterm automatically at the end, +but if you want to run it separately, you can type + + sh fluidterm.sh + +or just + + ./fluidterm.sh + +### If Fluidterm Won't Start ... + +Fluidterm is intended to be helpful but it is not absolutely necessary +for using FluidNC. Fluidterm lets you interact directly with FluidNC +over a USB serial port. There are other ways to do that, so if you +have trouble running Fluidterm, you can just ignore it. + +On headless (no GUI installed) machines fluidterm may fail with: + + ModuleNotFoundError: No module named 'tkinter' + +In this case you will need to install the correct system package for tkinter; +- note that trying to install via pip/pypi is usually not sufficient. + + Debian/Raspbian: $ sudo apt install python3-tk + Fedora/RHEL: $ sudo dnf install python3-tkinter +etc.. + +### Alternatives to Fluidterm + +Most GCode sender programs have some way to send commands directly to +FluidNC, but it can sometimes be helpful to bypass the complexity of +a sender and use a more direct path. Fluidterm is one such direct +path, but there are others, typically called "serial terminals". + +For Linux, there are many such programs, such as "screen", "minicom", "cu", +"picocom", and "PuTTY". The one that is most likely to be preinstalled +is named "screen". If screen is not preinstalled, you might be able to +install it with (on Ubuntu or Debian). + + sudo apt update + sudo apt install screen + +To use screen, go to a shell window and type: + + ls /dev/tty* + +That will give you a list of serial ports. You need to find the one +that is connected to FluidNC. It will probably have a name that starts +with "/dev/ttyUSB". Once you have found that name, type + + screen /dev/ttyUSBWHATEVER 115200 + +To exit from screen, type Control-A k + +Search the web for more documentation about screen, or for instructions +for installing it on other versions of Linux. + +### What Can Go Wrong? + +esptool.py is only available on Python3.6 and higher, if your system +does not support that you may be able to use the python2.7 version of +esptool.py by manually installing it first: + pip install esptool.py +The install_* scripts should then be able to complete the firmware install, +but will fail after that when they cannot run fluidterm. See above +for alternatives to fluidterm. + +If Python3.6+ is available on your system, but (for whatever reason) you +use a lower Python version as the system default you can use a +python virtual environment (venv) to do the install without needing to +modify your system-wide python environment: +https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/ + +### FreeBSD and other *Nix *BSD platforms + +This tool has been used successfully on FreeBSD 13 and should work +on any unix-like platform where Python3 and esptool.py are available. +However this is unsupported; and for advanced users only. +For reference: +FreeBSD(13): + User had to be added to 'dialer' group, and py-tkinter installed from + ports to enable fluidterm. diff --git a/install_scripts/linux-python3/checksecurity.sh b/install_scripts/linux-python3/checksecurity.sh new file mode 100644 index 000000000..d2f0283f2 --- /dev/null +++ b/install_scripts/linux-python3/checksecurity.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +. ./tools.sh + +esptool_basic dump_mem 0x3ff5a018 4 SecurityFuses.bin + +if ! cmp -s SecurityFuses.bin common/SecurityFusesOK.bin ; then + echo ******************************************* + echo * Secure boot is enabled on this ESP32 * + echo * Loading FluidNC would probably brick it * + echo * !ABORTED! Read Wiki for more Info * + echo ******************************************* + cmp -l SecurityFuses.bin common/SecurityFusesOK.bin + rm SecurityFuses.bin + exit 1 +fi + +rm SecurityFuses.bin +exit 0 diff --git a/install_scripts/linux-python3/fluidterm.sh b/install_scripts/linux-python3/fluidterm.sh new file mode 100644 index 000000000..242112766 --- /dev/null +++ b/install_scripts/linux-python3/fluidterm.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +# Install dependencies if needed +python3 -m pip install -q pyserial xmodem + +# Run fluidterm +python3 fluidterm/fluidterm.py $* + diff --git a/install_scripts/linux-python3/install-bt.sh b/install_scripts/linux-python3/install-bt.sh new file mode 100644 index 000000000..c776cdb36 --- /dev/null +++ b/install_scripts/linux-python3/install-bt.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +if ! ./checksecurity.sh; then + exit +fi + +. ./tools.sh + +BuildType=bt + +Bootloader="0x1000 common/bootloader_dio_80m.bin" +Bootapp="0xe000 common/boot_app0.bin" +Firmware="0x10000 ${BuildType}/firmware.bin" +Partitions="0x8000 ${BuildType}/partitions.bin" + +esptool_write $Bootloader $Bootapp $Firmware $Partitions + +echo Starting fluidterm +sh fluidterm.sh diff --git a/install_scripts/linux-python3/install-fs.sh b/install_scripts/linux-python3/install-fs.sh new file mode 100644 index 000000000..74a1c0453 --- /dev/null +++ b/install_scripts/linux-python3/install-fs.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +if ! ./checksecurity.sh; then + exit +fi + +. ./tools.sh + +LocalFS="0x3d0000 wifi/spiffs.bin" + +esptool_write $LocalFS + +echo Starting fluidterm +sh fluidterm.sh diff --git a/install_scripts/linux-python3/install-wifi.sh b/install_scripts/linux-python3/install-wifi.sh new file mode 100644 index 000000000..1213d0e02 --- /dev/null +++ b/install_scripts/linux-python3/install-wifi.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +if ! ./checksecurity.sh; then + exit +fi + +. ./tools.sh + +BuildType=wifi + +Bootloader="0x1000 common/bootloader_dio_80m.bin" +Bootapp="0xe000 common/boot_app0.bin" +Firmware="0x10000 ${BuildType}/firmware.bin" +Partitions="0x8000 ${BuildType}/partitions.bin" + +esptool_write $Bootloader $Bootapp $Firmware $Partitions + +echo Starting fluidterm +sh fluidterm.sh diff --git a/install_scripts/linux-python3/tools.sh b/install_scripts/linux-python3/tools.sh new file mode 100644 index 000000000..e25c07362 --- /dev/null +++ b/install_scripts/linux-python3/tools.sh @@ -0,0 +1,42 @@ +# Subroutines to call esptool with common arguments + +# Ensure local binary path is in $PATH (pip3 default target path) +echo "$PATH" | grep '$HOME\/\.local\/bin' 2>&1 >/dev/null +if test "$?" != "0"; then + export PATH="$HOME/.local/bin:$PATH" +fi + +# Install esptool.py if needed +which esptool.py 2>&1 >/dev/null +if test "$?" != "0"; then + echo esptool.py not found, attempting to install + python3 -m pip install --user esptool + if test "$?" != "0"; then + echo esptool.py install failed + exit 1 + fi + which esptool.py 2>&1 >/dev/null + if test "$?" != "0"; then + echo esptool.py claims to have installed successfully, but cannot be found in PATH + echo PATH= $PATH + exit 1 + fi +fi + +EsptoolPath=esptool.py + +BaseArgs="--chip esp32 --baud 230400" + +SetupArgs="--before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size detect" + +esptool_basic () { + echo echo $EsptoolPath $BaseArgs $* + $EsptoolPath $BaseArgs $BaseArgs $* + if test "$?" != "0"; then + echo esptool.py failed + exit 1 + fi +} +esptool_write () { + esptool_basic $SetupArgs $* +}