diff options
-rw-r--r-- | .cproject | 128 | ||||
-rw-r--r-- | .settings/language.settings.xml | 11 | ||||
-rw-r--r-- | .settings/org.eclipse.cdt.core.prefs | 8 | ||||
-rw-r--r-- | src/Configuration.h | 10 | ||||
-rw-r--r-- | src/DuetNG/FirmwareUpdater.cpp | 64 | ||||
-rw-r--r-- | src/DuetNG/FirmwareUpdater.h | 20 | ||||
-rw-r--r-- | src/DuetNG/Network.cpp | 1084 | ||||
-rw-r--r-- | src/DuetNG/Network.h | 121 | ||||
-rw-r--r-- | src/DuetNG/Pins_DuetNG.h | 186 | ||||
-rw-r--r-- | src/DuetNG/TransactionBuffer.cpp | 58 | ||||
-rw-r--r-- | src/DuetNG/TransactionBuffer.h | 165 | ||||
-rw-r--r-- | src/DuetNG/Webserver.cpp | 1059 | ||||
-rw-r--r-- | src/DuetNG/Webserver.h | 171 | ||||
-rw-r--r-- | src/DuetNG/WifiFirmwareUploader.cpp | 743 | ||||
-rw-r--r-- | src/DuetNG/WifiFirmwareUploader.h | 96 | ||||
-rw-r--r-- | src/ExternalDrivers.cpp | 72 | ||||
-rw-r--r-- | src/GCodeBuffer.cpp | 23 | ||||
-rw-r--r-- | src/GCodes.cpp | 181 | ||||
-rw-r--r-- | src/GCodes.h | 9 | ||||
-rw-r--r-- | src/Heat.cpp | 13 | ||||
-rw-r--r-- | src/Platform.cpp | 162 | ||||
-rw-r--r-- | src/Platform.h | 15 | ||||
-rw-r--r-- | src/Reprap.cpp | 59 | ||||
-rw-r--r-- | src/TemperatureError.cpp | 5 |
24 files changed, 4266 insertions, 197 deletions
@@ -137,7 +137,7 @@ </extensions> </storageModule> <storageModule moduleId="cdtBuildSystem" version="4.0.0"> - <configuration artifactExtension="elf" artifactName="${ProjName}" buildArtefactType="org.eclipse.cdt.build.core.buildArtefactType.exe" buildProperties="org.eclipse.cdt.build.core.buildType=org.eclipse.cdt.build.core.buildType.release,org.eclipse.cdt.build.core.buildArtefactType=org.eclipse.cdt.build.core.buildArtefactType.exe" cleanCommand="rm -rf" description="" id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451" name="SAM4E_CoreNG" parent="cdt.managedbuild.config.gnu.cross.exe.release" postannouncebuildStep="Generating binary file" postbuildStep="arm-none-eabi-objcopy -O binary ${workspace_loc:/${ProjName}/${ConfigName}}/${ProjName}.elf ${workspace_loc:/${ProjName}/${ConfigName}}/${ProjName}.bin"> + <configuration artifactExtension="elf" artifactName="DuetWiFiFirmware" buildArtefactType="org.eclipse.cdt.build.core.buildArtefactType.exe" buildProperties="org.eclipse.cdt.build.core.buildType=org.eclipse.cdt.build.core.buildType.release,org.eclipse.cdt.build.core.buildArtefactType=org.eclipse.cdt.build.core.buildArtefactType.exe" cleanCommand="rm -rf" description="" id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451" name="SAM4E_CoreNG" parent="cdt.managedbuild.config.gnu.cross.exe.release" postannouncebuildStep="Generating binary file" postbuildStep="arm-none-eabi-objcopy -O binary ${workspace_loc:/${ProjName}/${ConfigName}}/DuetWiFiFirmware.elf ${workspace_loc:/${ProjName}/${ConfigName}}/DuetWiFiFirmware.bin"> <folderInfo id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451." name="/" resourcePath=""> <toolChain id="cdt.managedbuild.toolchain.gnu.cross.exe.release.1362047835" name="Cross GCC" superClass="cdt.managedbuild.toolchain.gnu.cross.exe.release"> <option id="cdt.managedbuild.option.gnu.cross.path.1491883811" name="Path" superClass="cdt.managedbuild.option.gnu.cross.path" value="C:\Arduino-1.5.8\hardware\tools\gcc-arm-none-eabi-4.8.3-2014q1\bin" valueType="string"/> @@ -206,7 +206,10 @@ <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/common/utils}""/> <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/common/services/clock}""/> <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/common/services/ioport}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/drivers/dmac}""/> <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/drivers/efc}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/drivers/matrix}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/drivers/pdc}""/> <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/drivers/pmc}""/> <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/drivers/spi}""/> <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/drivers/twi}""/> @@ -234,6 +237,129 @@ </toolChain> </folderInfo> <sourceEntries> + <entry excluding="src/Libraries/MCP4461|src/Duet" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/> + </sourceEntries> + </configuration> + </storageModule> + <storageModule moduleId="org.eclipse.cdt.core.externalSettings"/> + </cconfiguration> + <cconfiguration id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080"> + <storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080" moduleId="org.eclipse.cdt.core.settings" name="SAM4E_PROTO1"> + <macros> + <stringMacro name="CoreName" type="VALUE_TEXT" value="CoreNG"/> + </macros> + <externalSettings/> + <extensions> + <extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/> + <extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/> + <extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/> + <extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/> + <extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/> + <extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/> + </extensions> + </storageModule> + <storageModule moduleId="cdtBuildSystem" version="4.0.0"> + <configuration artifactExtension="elf" artifactName="${ProjName}" buildArtefactType="org.eclipse.cdt.build.core.buildArtefactType.exe" buildProperties="org.eclipse.cdt.build.core.buildType=org.eclipse.cdt.build.core.buildType.release,org.eclipse.cdt.build.core.buildArtefactType=org.eclipse.cdt.build.core.buildArtefactType.exe" cleanCommand="rm -rf" description="" id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080" name="SAM4E_PROTO1" parent="cdt.managedbuild.config.gnu.cross.exe.release" postannouncebuildStep="Generating binary file" postbuildStep="arm-none-eabi-objcopy -O binary ${workspace_loc:/${ProjName}/${ConfigName}}/${ProjName}.elf ${workspace_loc:/${ProjName}/${ConfigName}}/${ProjName}.bin"> + <folderInfo id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080." name="/" resourcePath=""> + <toolChain id="cdt.managedbuild.toolchain.gnu.cross.exe.release.1721367471" name="Cross GCC" superClass="cdt.managedbuild.toolchain.gnu.cross.exe.release"> + <option id="cdt.managedbuild.option.gnu.cross.path.775185210" name="Path" superClass="cdt.managedbuild.option.gnu.cross.path" value="C:\Arduino-1.5.8\hardware\tools\gcc-arm-none-eabi-4.8.3-2014q1\bin" valueType="string"/> + <option id="cdt.managedbuild.option.gnu.cross.prefix.662072815" name="Prefix" superClass="cdt.managedbuild.option.gnu.cross.prefix" value="arm-none-eabi-" valueType="string"/> + <targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.targetPlatform.gnu.cross.1984540835" isAbstract="false" osList="all" superClass="cdt.managedbuild.targetPlatform.gnu.cross"/> + <builder buildPath="${workspace_loc:/RepRapFirmware}/Release" id="cdt.managedbuild.builder.gnu.cross.614311341" keepEnvironmentInBuildfile="false" managedBuildOn="true" name="Gnu Make Builder" superClass="cdt.managedbuild.builder.gnu.cross"/> + <tool id="cdt.managedbuild.tool.gnu.cross.assembler.1598889104" name="Cross GCC Assembler" superClass="cdt.managedbuild.tool.gnu.cross.assembler"> + <inputType id="cdt.managedbuild.tool.gnu.assembler.input.2008444256" superClass="cdt.managedbuild.tool.gnu.assembler.input"/> + </tool> + <tool commandLinePattern="${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${INPUTS}" id="cdt.managedbuild.tool.gnu.cross.c.compiler.669564787" name="Cross GCC Compiler" superClass="cdt.managedbuild.tool.gnu.cross.c.compiler"> + <option defaultValue="gnu.c.optimization.level.most" id="gnu.c.compiler.option.optimization.level.925369674" name="Optimization Level" superClass="gnu.c.compiler.option.optimization.level" useByScannerDiscovery="false" valueType="enumerated"/> + <option id="gnu.c.compiler.option.debugging.level.1437680267" name="Debug Level" superClass="gnu.c.compiler.option.debugging.level" useByScannerDiscovery="false" value="gnu.c.debugging.level.none" valueType="enumerated"/> + <option id="gnu.c.compiler.option.misc.verbose.454077281" name="Verbose (-v)" superClass="gnu.c.compiler.option.misc.verbose" useByScannerDiscovery="false" value="false" valueType="boolean"/> + <option id="gnu.c.compiler.option.misc.other.1262081974" name="Other flags" superClass="gnu.c.compiler.option.misc.other" useByScannerDiscovery="false" value="-c -std=gnu99 -mcpu=cortex-m4 -mthumb -ffunction-sections -fdata-sections -nostdlib --param max-inline-insns-single=500" valueType="string"/> + <option id="gnu.c.compiler.option.include.paths.1815693236" name="Include paths (-I)" superClass="gnu.c.compiler.option.include.paths" useByScannerDiscovery="false" valueType="includePath"> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/cores/arduino}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/libraries/Storage}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/common/utils}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/common/services/ioport}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/drivers/hsmci}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/drivers/rstc}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/drivers/rtc}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/utils}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/utils/cmsis/sam4e/include}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/utils/header_files}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/utils/preprocessor}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/thirdparty/CMSIS/Include}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/variants/duetNG}""/> + </option> + <option id="gnu.c.compiler.option.preprocessor.def.symbols.1739570632" name="Defined symbols (-D)" superClass="gnu.c.compiler.option.preprocessor.def.symbols" useByScannerDiscovery="false" valueType="definedSymbols"> + <listOptionValue builtIn="false" value="__SAM4E8E__"/> + <listOptionValue builtIn="false" value="CORE_NG"/> + <listOptionValue builtIn="false" value="DUET_NG"/> + <listOptionValue builtIn="false" value="printf=iprintf"/> + <listOptionValue builtIn="false" value="PROTOTYPE_1"/> + </option> + <inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.1283635516" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/> + </tool> + <tool id="cdt.managedbuild.tool.gnu.cross.c.linker.316456764" name="Cross GCC Linker" superClass="cdt.managedbuild.tool.gnu.cross.c.linker"/> + <tool id="cdt.managedbuild.tool.gnu.cross.archiver.81082459" name="Cross GCC Archiver" superClass="cdt.managedbuild.tool.gnu.cross.archiver"/> + <tool command="gcc" commandLinePattern="${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${LINK_FLAGS_1} ${workspace_loc:/${CoreName}/SAM4E8E/cores/arduino/syscalls.o} ${INPUTS} ${LINK_FLAGS_2}" id="cdt.managedbuild.tool.gnu.cross.cpp.linker.1781803617" name="Cross G++ Linker" superClass="cdt.managedbuild.tool.gnu.cross.cpp.linker"> + <option id="gnu.cpp.link.option.nostdlibs.1188962244" name="No startup or default libs (-nostdlib)" superClass="gnu.cpp.link.option.nostdlibs" value="false" valueType="boolean"/> + <option id="gnu.cpp.link.option.paths.2059496497" name="Library search path (-L)" superClass="gnu.cpp.link.option.paths" valueType="libPaths"> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/SAM4E_PROTO1/}""/> + </option> + <option id="gnu.cpp.link.option.libs.1868917950" name="Libraries (-l)" superClass="gnu.cpp.link.option.libs" valueType="libs"> + <listOptionValue builtIn="false" value="${CoreName}"/> + </option> + <option id="gnu.cpp.link.option.flags.748151728" name="Linker flags" superClass="gnu.cpp.link.option.flags" value="-Os -Wl,--gc-sections -Wl,--fatal-warnings -mcpu=cortex-m3 -T${workspace_loc:/${CoreName}/variants/duetNG/linker_scripts/gcc/flash.ld} -Wl,-Map,${workspace_loc:/${ProjName}/${ConfigName}}/${ProjName}.map" valueType="string"/> + <inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.1649038990" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input"> + <additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/> + <additionalInput kind="additionalinput" paths="$(LIBS)"/> + </inputType> + </tool> + <tool command="g++" id="cdt.managedbuild.tool.gnu.cross.cpp.compiler.2138612171" name="Cross G++ Compiler" superClass="cdt.managedbuild.tool.gnu.cross.cpp.compiler"> + <option id="gnu.cpp.compiler.option.optimization.level.1445246033" name="Optimization Level" superClass="gnu.cpp.compiler.option.optimization.level" useByScannerDiscovery="false" value="gnu.cpp.compiler.optimization.level.more" valueType="enumerated"/> + <option id="gnu.cpp.compiler.option.debugging.level.213346939" name="Debug Level" superClass="gnu.cpp.compiler.option.debugging.level" useByScannerDiscovery="false" value="gnu.cpp.compiler.debugging.level.none" valueType="enumerated"/> + <option id="gnu.cpp.compiler.option.other.verbose.1378702640" name="Verbose (-v)" superClass="gnu.cpp.compiler.option.other.verbose" useByScannerDiscovery="false" value="false" valueType="boolean"/> + <option id="gnu.cpp.compiler.option.other.other.1875098822" name="Other flags" superClass="gnu.cpp.compiler.option.other.other" useByScannerDiscovery="false" value="-c -std=gnu++11 -mcpu=cortex-m4 -mthumb -ffunction-sections -fdata-sections -fno-threadsafe-statics -fno-rtti -fno-exceptions -nostdlib --param max-inline-insns-single=500" valueType="string"/> + <option id="gnu.cpp.compiler.option.include.paths.254420427" name="Include paths (-I)" superClass="gnu.cpp.compiler.option.include.paths" useByScannerDiscovery="false" valueType="includePath"> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/cores/arduino}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/libraries/Wire}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/libraries/SharedSpi}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/libraries/Storage}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/common/utils}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/common/services/clock}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/common/services/ioport}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/drivers/dmac}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/drivers/efc}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/drivers/pdc}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/drivers/pmc}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/drivers/spi}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/drivers/twi}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/services/flash_efc}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/utils}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/utils/cmsis/sam4e/include}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/utils/header_files}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/sam/utils/preprocessor}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/asf/thirdparty/CMSIS/Include}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${CoreName}/variants/duetNG}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${ProjName}/src}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${ProjName}/src/DuetNG}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${ProjName}/src/Libraries/Fatfs}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${ProjName}/src/Libraries/MCP4461}""/> + <listOptionValue builtIn="false" value=""${workspace_loc:/${ProjName}/src/Libraries/Flash}""/> + </option> + <option id="gnu.cpp.compiler.option.preprocessor.def.486383061" name="Defined symbols (-D)" superClass="gnu.cpp.compiler.option.preprocessor.def" useByScannerDiscovery="false" valueType="definedSymbols"> + <listOptionValue builtIn="false" value="__SAM4E8E__"/> + <listOptionValue builtIn="false" value="CORE_NG"/> + <listOptionValue builtIn="false" value="DUET_NG"/> + <listOptionValue builtIn="false" value="printf=iprintf"/> + <listOptionValue builtIn="false" value="PROTOTYPE_1"/> + </option> + <inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.2012484327" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/> + </tool> + </toolChain> + </folderInfo> + <sourceEntries> <entry excluding="src/Duet" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/> </sourceEntries> </configuration> diff --git a/.settings/language.settings.xml b/.settings/language.settings.xml index 585ff377..e47fc4b9 100644 --- a/.settings/language.settings.xml +++ b/.settings/language.settings.xml @@ -22,4 +22,15 @@ </provider> </extension> </configuration> + <configuration id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080" name="SAM4E_PROTO1"> + <extension point="org.eclipse.cdt.core.LanguageSettingsProvider"> + <provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/> + <provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/> + <provider-reference id="org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider" ref="shared-provider"/> + <provider class="org.eclipse.cdt.internal.build.crossgcc.CrossGCCBuiltinSpecsDetector" console="false" env-hash="337623625225680352" id="org.eclipse.cdt.build.crossgcc.CrossGCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT Cross GCC Built-in Compiler Settings" parameter="${COMMAND} ${FLAGS} -E -P -v -dD "${INPUTS}"" prefer-non-shared="true"> + <language-scope id="org.eclipse.cdt.core.gcc"/> + <language-scope id="org.eclipse.cdt.core.g++"/> + </provider> + </extension> + </configuration> </project> diff --git a/.settings/org.eclipse.cdt.core.prefs b/.settings/org.eclipse.cdt.core.prefs index e296b810..0d147d57 100644 --- a/.settings/org.eclipse.cdt.core.prefs +++ b/.settings/org.eclipse.cdt.core.prefs @@ -1,4 +1,12 @@ eclipse.preferences.version=1 +environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080/LINK_FLAGS_1/delimiter=; +environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080/LINK_FLAGS_1/operation=append +environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080/LINK_FLAGS_1/value=-mthumb -Wl,--cref -Wl,--check-sections -Wl,--gc-sections -Wl,--entry\=Reset_Handler -Wl,--unresolved-symbols\=report-all -Wl,--warn-common -Wl,--warn-section-align -Wl,--warn-unresolved-symbols -Wl,--start-group +environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080/LINK_FLAGS_2/delimiter=; +environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080/LINK_FLAGS_2/operation=append +environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080/LINK_FLAGS_2/value=-Wl,--end-group -lm -gcc +environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080/append=true +environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080/appendContributed=true environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451/LINK_FLAGS_1/delimiter=; environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451/LINK_FLAGS_1/operation=append environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451/LINK_FLAGS_1/value=-mthumb -Wl,--cref -Wl,--check-sections -Wl,--gc-sections -Wl,--entry\=Reset_Handler -Wl,--unresolved-symbols\=report-all -Wl,--warn-common -Wl,--warn-section-align -Wl,--warn-unresolved-symbols -Wl,--start-group diff --git a/src/Configuration.h b/src/Configuration.h index e972ca60..32962045 100644 --- a/src/Configuration.h +++ b/src/Configuration.h @@ -26,11 +26,11 @@ Licence: GPL // Firmware name is now defined in the Pins file #ifndef VERSION -# define VERSION "1.13beta1" +# define VERSION "1.13" #endif #ifndef DATE -# define DATE "2016-05-27" +# define DATE "2016-06-13" #endif #define AUTHORS "reprappro, dc42, zpl, t3p3, dnewman" @@ -68,7 +68,7 @@ const unsigned int MAIN_BAUD_RATE = 115200; // Default communication speed of const unsigned int AUX_BAUD_RATE = 57600; // Ditto - for auxiliary UART device const unsigned int AUX2_BAUD_RATE = 115200; // Ditto - for second auxiliary UART device -const uint32_t SERIAL_MAIN_TIMEOUT = 1000; // timeout in ms for sending dara to the main serial/USB port +const uint32_t SERIAL_MAIN_TIMEOUT = 1000; // timeout in ms for sending data to the main serial/USB port // Heater values @@ -83,7 +83,7 @@ const float TIME_TO_HOT = 150.0; // Seconds const uint8_t MAX_BAD_TEMPERATURE_COUNT = 4; // Number of bad temperature samples permitted before a heater fault is reported const float BAD_LOW_TEMPERATURE = -10.0; // Celsius -const float DEFAULT_TEMPERATURE_LIMIT = 260.0; // Celsius +const float DEFAULT_TEMPERATURE_LIMIT = 262.0; // Celsius const float HOT_END_FAN_TEMPERATURE = 45.0; // Temperature at which a thermostatic hot end fan comes on const float BAD_ERROR_TEMPERATURE = 2000.0; // Must exceed any reasonable 5temperature limit including DEFAULT_TEMPERATURE_LIMIT @@ -93,7 +93,7 @@ const unsigned int FirstRtdChannel = 200; // Temperature sensor channels 200.. // PWM frequencies const unsigned int SlowHeaterPwmFreq = 10; // slow PWM frequency for bed and chamber heaters, compatible with DC/AC SSRs -const unsigned int NormalHeaterPwmFreq = 500; // normal PWM frequency used for hot ends +const unsigned int NormalHeaterPwmFreq = 250; // normal PWM frequency used for hot ends const unsigned int DefaultFanPwmFreq = 500; // increase to 25kHz using M106 command to meet Intel 4-wire PWM fan specification // Default Z probe values diff --git a/src/DuetNG/FirmwareUpdater.cpp b/src/DuetNG/FirmwareUpdater.cpp new file mode 100644 index 00000000..aace1bef --- /dev/null +++ b/src/DuetNG/FirmwareUpdater.cpp @@ -0,0 +1,64 @@ +/* + * FirmwareUpdater.cpp + * + * Created on: 21 May 2016 + * Author: David + */ + +#include "FirmwareUpdater.h" +#include "RepRapFirmware.h" +#include "WifiFirmwareUploader.h" + +namespace FirmwareUpdater +{ + const unsigned int WifiFirmwareModule = 1; + const unsigned int WifiFilesModule = 2; + const unsigned int WifiExternalFirmwareModule = 3; + + // Check that the prerequisites are satisfied. + // Return true if yes, else print a message and return false. + bool CheckFirmwareUpdatePrerequisites(uint8_t moduleMap) + { + if ((moduleMap & (1 << WifiExternalFirmwareModule)) != 0 && (moduleMap & ((1 << WifiFirmwareModule) | (1 << WifiFilesModule))) != 0) + { + reprap.GetPlatform()->Message(GENERIC_MESSAGE, "Invalid combination of firmware update modules\n"); + return false; + } + if ((moduleMap & (1 << WifiFirmwareModule)) != 0 && !reprap.GetPlatform()->GetMassStorage()->FileExists(SYS_DIR, WIFI_FIRMWARE_FILE)) + { + reprap.GetPlatform()->MessageF(GENERIC_MESSAGE, "File %s not found\n", WIFI_FIRMWARE_FILE); + return false; + } + if ((moduleMap & (1 << WifiFilesModule)) != 0 && !reprap.GetPlatform()->GetMassStorage()->FileExists(SYS_DIR, WIFI_WEB_FILE)) + { + reprap.GetPlatform()->MessageF(GENERIC_MESSAGE, "File %s not found\n", WIFI_WEB_FILE); + return false; + } + return true; + } + + bool IsReady() + { + return reprap.GetNetwork()->GetWifiUploader()->IsReady(); + } + + void UpdateModule(unsigned int module) + { + switch(module) + { + case WifiExternalFirmwareModule: + Network::ResetWiFiForExternalUpload(); + break; + + case WifiFirmwareModule: + reprap.GetNetwork()->GetWifiUploader()->SendUpdateFile(WIFI_FIRMWARE_FILE, SYS_DIR, WifiFirmwareUploader::FirmwareAddress); + break; + + case WifiFilesModule: + reprap.GetNetwork()->GetWifiUploader()->SendUpdateFile(WIFI_WEB_FILE, SYS_DIR, WifiFirmwareUploader::WebFilesAddress); + break; + } + } +} + +// End diff --git a/src/DuetNG/FirmwareUpdater.h b/src/DuetNG/FirmwareUpdater.h new file mode 100644 index 00000000..67b1b710 --- /dev/null +++ b/src/DuetNG/FirmwareUpdater.h @@ -0,0 +1,20 @@ +/* + * FirmwareUpdater.h + * + * Created on: 21 May 2016 + * Author: David + */ + +#ifndef SRC_DUETNG_FIRMWAREUPDATER_H_ +#define SRC_DUETNG_FIRMWAREUPDATER_H_ + +#include <cstdint> + +namespace FirmwareUpdater +{ + bool CheckFirmwareUpdatePrerequisites(uint8_t moduleMap); + bool IsReady(); + void UpdateModule(unsigned int module); +} + +#endif /* SRC_DUETNG_FIRMWAREUPDATER_H_ */ diff --git a/src/DuetNG/Network.cpp b/src/DuetNG/Network.cpp new file mode 100644 index 00000000..1578dda2 --- /dev/null +++ b/src/DuetNG/Network.cpp @@ -0,0 +1,1084 @@ +/**************************************************************************************************** + + RepRapFirmware network comms to ESP8266-based device + + ****************************************************************************************************/ + +#include "WifiFirmwareUploader.h" +#include "RepRapFirmware.h" +#include "compiler.h" +#include "Pins.h" +#include "WifiFirmwareUploader.h" +#include "TransactionBuffer.h" + +// Define exactly one of the following as 1, thje other as zero +#define USE_PDC 0 // use peripheral DMA controller +#define USE_DMAC 1 // use general DMA controller + +#if USE_PDC +#include "pdc.h" +#endif + +#if USE_DMAC +#include "dmac.h" +#endif + +#include "matrix.h" + +// Forward declarations of static functions +static void spi_dma_disable(); +static bool spi_dma_check_rx_complete(); + +static TransactionBuffer inBuffer, outBuffer; +static uint32_t dummyOutBuffer[TransactionBuffer::headerDwords] = {0, 0, 0, 0, 0}; + +void EspTransferRequestIsr() +{ + reprap.GetNetwork()->EspRequestsTransfer(); +} + +/*-----------------------------------------------------------------------------------*/ +// WiFi interface class + +Network::Network(Platform* p) : platform(p), responseCode(0), responseBody(nullptr), responseText(nullptr), responseFile(nullptr), + spiTxUnderruns(0), spiRxOverruns(0), + state(disabled), activated(false), connectedToAp(false) + +{ + strcpy(hostname, HOSTNAME); + ClearIpAddress(); +} + +void Network::Init() +{ + // Make sure the ESP8266 is held in the reset state + pinMode(EspResetPin, OUTPUT); + digitalWrite(EspResetPin, LOW); + uploader = new WifiFirmwareUploader(Serial1); +} + +void Network::Activate() +{ + activated = true; + if (state == enabled) + { + Start(); + } +} + +void Network::Exit() +{ + Stop(); +} + +void Network::ClearIpAddress() +{ + for (size_t i = 0; i < ARRAY_SIZE(ipAddress); ++i) + { + ipAddress[i] = 0; + } +} + +// Start up the ESP. We assume it is not already started. +// ESP8266 boot modes: +// GPIO0 GPIO2 GPIO15 +// 0 1 0 Firmware download from UART +// 1 1 0 Normal boot from flash memory +// 0 0 1 SD card boot (not used in on Duet) +void Network::Start() +{ + // The ESP8266 is held in a reset state by a pulldown resistor until we enable it. + // Make sure the ESP8266 is in the reset state + pinMode(EspResetPin, OUTPUT); + digitalWrite(EspResetPin, LOW); + + // Take the ESP8266 out of power down + pinMode(EspEnablePin, OUTPUT); + digitalWrite(EspEnablePin, HIGH); + + // Set up our transfer request pin (GPIO4) as an output and set it low + pinMode(SamTfrReadyPin, OUTPUT); + digitalWrite(SamTfrReadyPin, LOW); + + // Set up our data ready pin (ESP GPIO0) as an output and set it high ready to boot the ESP from flash + pinMode(EspTransferRequestPin, OUTPUT); + digitalWrite(EspTransferRequestPin, HIGH); + + // GPIO2 also needs to be high to boot. It's connected to MISO on the SAM, so set the pullup resistor on that pin + pinMode(APIN_SPI_MISO, INPUT_PULLUP); + + // Set our CS input (ESP GPIO15) low ready for booting the ESP. This also clears the transfer ready latch. + pinMode(SamCsPin, OUTPUT); + digitalWrite(SamCsPin, LOW); + + // Make sure it has time to reset - no idea how long it needs, but 20ms should be plenty + delay(50); + + // Release the reset on the ESP8266 + digitalWrite(EspResetPin, HIGH); + + // Give it time to sample GPIO0 and GPIO15 + // GPIO0 has to be held high for sufficient time: + // - 10ms is not enough + // - 18ms after reset is released, an oscillating signal appears on GPIO0 for 55ms + // - so 18ms is probably long enough. Use 25ms for safety. + delay(50); + + // Relinquish control of our CS pin so that the ESP can take it over + pinMode(SamCsPin, INPUT); + + // Set the data request pin to be an input + pinMode(EspTransferRequestPin, INPUT_PULLUP); + attachInterrupt(EspTransferRequestPin, EspTransferRequestIsr, RISING); + + // The ESP takes about 300ms before it starts talking to use, so don't wait for it here, do that in Spin() + + // Clear the transaction buffers + inBuffer.Clear(); + outBuffer.Clear(); + + state = starting; + + (void) SPI->SPI_SR; // clear any pending interrupt + NVIC_SetPriority(SPI_IRQn, 10); + NVIC_EnableIRQ(SPI_IRQn); + + connectedToAp = false; + spiTxUnderruns = spiRxOverruns = 0; +} + +// Stop the ESP +void Network::Stop() +{ + if (state != disabled) + { + digitalWrite(SamTfrReadyPin, LOW); // tell the ESP we can't receive + for (int i = 0; i < 10 && (state == receivePending || state == sendReceivePending); ++i) + { + delay(1); + } + digitalWrite(EspResetPin, LOW); // put the ESP back into reset + NVIC_DisableIRQ(SPI_IRQn); + spi_disable(SPI); + spi_dma_check_rx_complete(); + spi_dma_disable(); + + ClearIpAddress(); + state = disabled; + connectedToAp = false; + } +} + +void Network::Spin() +{ +// static float lastTime = 0.0; + + // Main state machine. + // Take care with this, because ISRs may cause the following state transitions: + // idle -> transferPending + // transferPending -> processing + switch (state) + { + case starting: + // See if the ESP8266 has set CS high yet + if (digitalRead(SamCsPin) == HIGH) + { + // Setup the SPI controller in slave mode and assign the CS pin to it + platform->Message(HOST_MESSAGE, "WiFi server starting up\n"); + SetupSpi(); // set up the SPI subsystem + state = idle; + TryStartTransfer(); + } + break; + + case transferDone: +// platform->Message(HOST_MESSAGE, "Transfer done\n"); + if (spi_dma_check_rx_complete()) + { + if (inBuffer.IsReady()) + { +// platform->MessageF(DEBUG_MESSAGE, "Rec %u\n", inBuffer.GetFragment()); + if (inBuffer.IsValid()) + { + inBuffer.AppendNull(); +// platform->Message(HOST_MESSAGE, "Got data\n"); + } + else + { + if (reprap.Debug(moduleNetwork)) + { + platform->MessageF(DEBUG_MESSAGE, "Bad msg in: ip=%u.%u.%u.%u opcode=%04x frag=%u length=%u\n", + inBuffer.GetIp() & 255, + (inBuffer.GetIp() >> 8) & 255, + (inBuffer.GetIp() >> 16) & 255, + (inBuffer.GetIp() >> 24) & 255, + inBuffer.GetOpcode(), + inBuffer.GetFragment(), + inBuffer.GetLength() + ); + } + inBuffer.Clear(); + } + } + else + { +// platform->MessageF(DEBUG_MESSAGE, "Rec null %u %u %u %u %u\n", +// inBuffer.GetOpcode(), inBuffer.GetIp(), inBuffer.GetSeq(), inBuffer.GetFragment(), inBuffer.GetLength()); + } + state = processing; + } + else + { + break; + } + // no break + case processing: + // Deal with incoming data, if any + if (inBuffer.IsReady()) + { + ProcessIncomingData(inBuffer); // this may or may not clear inBuffer + } + if (!inBuffer.IsEmpty()) + { + break; // still processing + } + responseFragment = 0; + state = sending; + // no break + case sending: + if (outBuffer.IsEmpty()) + { + // See if we have more of the current response to send + if (responseBody != nullptr) + { + // We have a reply contained in an OutputBuffer + outBuffer.SetMessage(trTypeResponse | ttRr, responseIp, responseFragment); + if (responseFragment == 0) + { + // Put the return code and content length at the start of the message + outBuffer.AppendU32(responseCode); + outBuffer.AppendU32(responseBody->Length()); + } + + do + { + const size_t len = responseBody->BytesLeft(); + const size_t bytesWritten = outBuffer.AppendData(responseBody->Read(0), len); + if (bytesWritten < len) + { + // Output buffer is full so will will need to send another fragment + (void)responseBody->Read(bytesWritten); // say how much data we have taken from the buffer + break; + } + responseBody = OutputBuffer::Release(responseBody); + } + while (responseBody != nullptr); + + if (responseBody == nullptr) + { + outBuffer.SetLastFragment(); + DebugPrintResponse(); + state = idle; + } + else + { + ++responseFragment; + DebugPrintResponse(); + } + } + else if (responseText != nullptr) + { + // We have a simple text reply to send + outBuffer.SetMessage(trTypeResponse | ttRr, responseIp, responseFragment); + if (responseFragment == 0) + { + // Put the return code and content length at the start of the message + outBuffer.AppendU32(responseCode); + outBuffer.AppendU32(strlen(responseText)); + } + + const size_t len = strlen(responseText); + const size_t lenSent = outBuffer.AppendData(responseText, len); + if (lenSent < len) + { + responseText += lenSent; + ++responseFragment; + DebugPrintResponse(); + } + else + { + responseText = nullptr; + outBuffer.SetLastFragment(); + DebugPrintResponse(); + state = idle; + } + } + else if (responseFile != nullptr) + { + // We have a file reply to send + outBuffer.SetMessage(trTypeResponse | ttRr, responseIp, responseFragment); + if (responseFragment == 0) + { + // Put the return code and content length at the start of the message + outBuffer.AppendU32(responseCode); + outBuffer.AppendU32(responseFileBytes); + } + + size_t spaceLeft; + char *p = outBuffer.GetBuffer(spaceLeft); + uint32_t bytesToRead = min<uint32_t>(spaceLeft, responseFileBytes); + int bytesRead = responseFile->Read(p, bytesToRead); + if (bytesRead >= 0 && (uint32_t)bytesRead <= bytesToRead) + { + outBuffer.DataAppended((uint32_t)bytesRead); + } + + bool finished; + if (bytesRead == (int)bytesToRead) + { + responseFileBytes -= (uint32_t)bytesRead; + finished = (responseFileBytes == 0); + } + else + { + // We have a file read error, however it's too late to signal it unless this is the first fragment + finished = true; + } + + if (finished) + { + responseFile->Close(); + responseFile = nullptr; + outBuffer.SetLastFragment(); + DebugPrintResponse(); + state = idle; + } + else + { + ++responseFragment; + DebugPrintResponse(); + } + } + else + { + state = idle; + } + TryStartTransfer(); + } + break; + + case idle: + TryStartTransfer(); + break; + + case disabled: + uploader->Spin(); + break; + + default: + break; + } + + platform->ClassReport(longWait); +} + +void Network::DebugPrintResponse() +{ + if (reprap.Debug(moduleNetwork)) + { + char buffer[200]; + StringRef reply(buffer, ARRAY_SIZE(buffer)); + outBuffer.AppendNull(); + size_t len; + const char* s = (const char*)outBuffer.GetData(len); + uint32_t frag = outBuffer.GetFragment() & ~lastFragment; + + reply.printf("Resp %u: ", outBuffer.GetFragment()); + if (frag == 0 && len >= 8) + { + // First fragment, so there is a 2-word header + reply.catf("%08x %08x ", *(const uint32_t*)s, *(const uint32_t*)(s + 4)); + s += 8; + len -= 8; + } + if (len < 38) + { + reply.catf("%s\n", s); + } + else + { + reply.catf("%c%c%c%c...s\n", s[0], s[1], s[2], s[3], s + len - 30); + } + + platform->Message(HOST_MESSAGE, reply.Pointer()); + } +} + +void Network::ProcessIncomingData(TransactionBuffer &buf) +{ + uint32_t opcode = inBuffer.GetOpcode(); + size_t length; + const void *data = inBuffer.GetData(length); + switch(opcode & 0xFF0000FF) + { + case trTypeInfo | ttNetworkInfo: + // Network info received from host + // Data is 4 bytes of IP address, 4 bytes of free heap, 4 bytes of reset reason, 64 chars of host name, and 32 bytes of ssid + if (length >= 12) + { + const uint8_t *ip = (const uint8_t*) data; + uint32_t freeHeap = *(reinterpret_cast<const uint32_t*>(data) + 1); + uint32_t resetReason = *(reinterpret_cast<const uint32_t*>(data) + 2); + const char *hostName = reinterpret_cast<const char*>(data) + 12; + const char *ssid = reinterpret_cast<const char*>(data) + 76; + platform->MessageF(HOST_MESSAGE, "WiFi server connected to access point %s, IP=%u.%u.%u.%u\n", ssid, ip[0], ip[1], ip[2], ip[3]); + platform->MessageF(HOST_MESSAGE, "WiFi host name %s, free memory %u bytes, reset reason %u\n", hostName, freeHeap, resetReason); + memcpy(ipAddress, ip, 4); + connectedToAp = true; + } + inBuffer.Clear(); + break; + + case trTypeRequest | ttRr: +#if 0 + { + size_t length; + const char* data = (const char*)inBuffer.GetData(length); + if (length > 30) { data += (length - 30); } + platform->MessageF(DEBUG_MESSAGE, "IP %u.%u.%u.%u Frag %u %s\n", + inBuffer.GetIp() & 255, + (inBuffer.GetIp() >> 8) & 255, + (inBuffer.GetIp() >> 16) & 255, + (inBuffer.GetIp() >> 24) & 255, + inBuffer.GetFragment(), + data); + } +#else + // Do nothing - the webserver module will pick it up +#endif + break; + + default: + { + size_t length; + const char* data = (const char*)inBuffer.GetData(length); + platform->MessageF(DEBUG_MESSAGE, "Received opcode %08x length %u data %s\n", opcode, length, data); + } + inBuffer.Clear(); + break; + } +} + +// Called by the webserver module to get an incoming request +const char *Network::GetRequest(uint32_t& ip, size_t& length, uint32_t& fragment) const +{ + if (state == processing) + { + uint32_t opcode = inBuffer.GetOpcode(); + if ((opcode & 0xFF0000FF) == (trTypeRequest | ttRr)) + { + const void *data = inBuffer.GetData(length); + if (length > 0) + { + ip = inBuffer.GetIp(); + fragment = inBuffer.GetFragment(); + if ((fragment & 0x7FFFFFFF) == 0) + { + length += 1; // allow client to read the null at the end too + } +// platform->MessageF(HOST_MESSAGE, "Req: %s\n", (const char*)data); + return (const char*)data; + } + else + { + platform->Message(DEBUG_MESSAGE, "Bad request\n"); + inBuffer.Clear(); // bad request + } + } + } + return nullptr; +} + +// Send a reply from an OutputBuffer chain. Release the chain when we have finished with it. +void Network::SendReply(uint32_t ip, unsigned int code, OutputBuffer *body) +{ + if (responseBody != nullptr || responseText != nullptr || responseFile != nullptr) + { + platform->Message(HOST_MESSAGE, "response already being sent in SendReply(ob*)\n"); + } + else + { + responseIp = ip; + responseCode = code; + responseBody = body; + +#if 0 + //debug + { + char buf[101]; + size_t len = min<size_t>(ARRAY_UPB(buf), responseBody->DataLength()); + strncpy(buf, responseBody->Data(), len); + buf[len] = 0; + platform->MessageF(HOST_MESSAGE, "%s %u %s\n", (responseCode & rcJson) ? "JSON reply" : "Reply", responseCode & rcNumber, buf); + } +#endif + // Say we have taken the request + if (state == processing) + { + uint32_t opcode = inBuffer.GetOpcode(); + if ((opcode & 0xFF0000FF) == (trTypeRequest | ttRr)) + { + inBuffer.Clear(); + } + } + } +} + +// Send a reply from a null-terminated string +void Network::SendReply(uint32_t ip, unsigned int code, const char *text) +{ + if (responseBody != nullptr || responseText != nullptr || responseFile != nullptr) + { + platform->Message(HOST_MESSAGE, "response already being sent in SendReply(cc*)\n"); + } + else + { + responseIp = ip; + responseCode = code; + responseText = text; + + //debug + //platform->MessageF(HOST_MESSAGE, "%s %u %s\n", (responseCode & rcJson) ? "JSON reply" : "Reply", responseCode & rcNumber, text); + + // Say we have taken the request + if (state == processing) + { + uint32_t opcode = inBuffer.GetOpcode(); + if ((opcode & 0xFF0000FF) == (trTypeRequest | ttRr)) + { + inBuffer.Clear(); + } + } + } +} + +// Send a file as the reply. Close the file at the end. +void Network::SendReply(uint32_t ip, unsigned int code, FileStore *file) +{ + if (responseBody != nullptr || responseText != nullptr || responseFile != nullptr) + { + platform->Message(HOST_MESSAGE, "response already being sent in SendReply(fs*)\n"); + file->Close(); + } + else + { + responseIp = ip; + responseCode = code; + responseFile = file; + responseFileBytes = file->Length(); + + // Say we have taken the request + if (state == processing) + { + uint32_t opcode = inBuffer.GetOpcode(); + if ((opcode & 0xFF0000FF) == (trTypeRequest | ttRr)) + { + inBuffer.Clear(); + } + } + } +} + +// This is called when we have read a message fragment and there is no reply to send +void Network::DiscardMessage() +{ + if (responseBody != nullptr || responseText != nullptr || responseFile != nullptr) + { + platform->Message(HOST_MESSAGE, "response being sent in DiscardMessage\n"); + } + + //debug + //platform->MessageF(HOST_MESSAGE, "%s %u %s\n", (responseCode & rcJson) ? "JSON reply" : "Reply", responseCode & rcNumber, text); + + // Say we have taken the request + if (state == processing) + { + uint32_t opcode = inBuffer.GetOpcode(); + if ((opcode & 0xFF0000FF) == (trTypeRequest | ttRr)) + { + inBuffer.Clear(); + } + + // For faster response, change the state to idle so we can accept another packet immediately + state = idle; + TryStartTransfer(); + } +} + +void Network::Diagnostics(MessageType mtype) +{ + platform->Message(mtype, "Network Diagnostics:\n"); + const char* text = (state == starting) ? "starting" + : (state == disabled) ? "disabled" + : (state == enabled) ? "enabled but not running" + : "running"; + platform->MessageF(mtype, "WiFiServer is %s\n", text); + platform->MessageF(mtype, "SPI underruns %u, overruns %u\n", spiTxUnderruns, spiRxOverruns); +} + +void Network::Enable() +{ + if (state == disabled) + { + state = enabled; + if (activated) + { + Start(); + } + } +} + +void Network::Disable() +{ + if (activated && state != disabled) + { + Stop(); + platform->Message(GENERIC_MESSAGE, "WiFi server stopped\n"); + } +} + +bool Network::IsEnabled() const +{ + return state != disabled; +} + +const uint8_t *Network::IPAddress() const +{ + return ipAddress; +} + +uint16_t Network::GetHttpPort() const +{ + return DEFAULT_HTTP_PORT; +} + +void Network::SetHttpPort(uint16_t port) +{ + // Not supported +} + +// Set the DHCP hostname. Removes all whitespaces and converts the name to lower-case. +void Network::SetHostname(const char *name) +{ + size_t i = 0; + while (*name && i < ARRAY_UPB(hostname)) + { + char c = *name++; + if (c >= 'A' && c <= 'Z') + { + c += 'a' - 'A'; + } + + if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '-') || (c == '_')) + { + hostname[i++] = c; + } + } + + if (i) + { + hostname[i] = 0; + } + else + { + strcpy(hostname, HOSTNAME); + } +} + +#if USE_PDC +static Pdc *spi_pdc; +#endif + +#if USE_DMAC + +// Our choice of DMA channels to use +const uint32_t CONF_SPI_DMAC_TX_CH = 1; +const uint32_t CONF_SPI_DMAC_RX_CH = 2; + +// Hardware IDs of the SPI transmit and receive DMA interfaces. See atsam datasheet. +const uint32_t DMA_HW_ID_SPI_TX = 1; +const uint32_t DMA_HW_ID_SPI_RX = 2; + +#endif + +static inline void spi_rx_dma_enable() +{ +#if USE_PDC + pdc_enable_transfer(spi_pdc, PERIPH_PTCR_RXTEN); +#endif + +#if USE_DMAC + dmac_channel_enable(DMAC, CONF_SPI_DMAC_RX_CH); +#endif +} + +static inline void spi_tx_dma_enable() +{ +#if USE_PDC + pdc_enable_transfer(spi_pdc, PERIPH_PTCR_TXTEN); +#endif + +#if USE_DMAC + dmac_channel_enable(DMAC, CONF_SPI_DMAC_TX_CH); +#endif +} + +static inline void spi_rx_dma_disable() +{ +#if USE_PDC + pdc_disable_transfer(spi_pdc, PERIPH_PTCR_RXTDIS); +#endif + +#if USE_DMAC + dmac_channel_disable(DMAC, CONF_SPI_DMAC_RX_CH); +#endif +} + +static inline void spi_tx_dma_disable() +{ +#if USE_PDC + pdc_disable_transfer(spi_pdc, PERIPH_PTCR_TXTDIS); +#endif + +#if USE_DMAC + dmac_channel_disable(DMAC, CONF_SPI_DMAC_TX_CH); +#endif +} + +static void spi_dma_disable() +{ + spi_tx_dma_disable(); + spi_rx_dma_disable(); +} + +static bool spi_dma_check_rx_complete() +{ +#if USE_PDC + return true; +#endif + +#if USE_DMAC + uint32_t status = DMAC->DMAC_CHSR; + if ( ((status & (DMAC_CHSR_ENA0 << CONF_SPI_DMAC_RX_CH)) == 0) // controller is not enabled, perhaps because it finished a full buffer transfer + || ((status & (DMAC_CHSR_EMPT0 << CONF_SPI_DMAC_RX_CH)) != 0) // controller is enabled, probably suspended, and the FIFO is empty + ) + { + // Disable the channel. + // We also need to set the resume bit, otherwise it remains suspended when we re-enable it. + DMAC->DMAC_CHDR = (DMAC_CHDR_DIS0 << CONF_SPI_DMAC_RX_CH) | (DMAC_CHDR_RES0 << CONF_SPI_DMAC_RX_CH); + return true; + } + return false; +#endif +} + +static void spi_tx_dma_setup(const TransactionBuffer *buf, uint32_t maxTransmitLength) +{ +#if USE_PDC + pdc_packet_t pdc_spi_packet; + pdc_spi_packet.ul_addr = reinterpret_cast<uint32_t>(buf); + pdc_spi_packet.ul_size = buf->PacketLength() * 4; // need length in bytes + pdc_tx_init(spi_pdc, &pdc_spi_packet, NULL); +#endif + +#if USE_DMAC + DMAC->DMAC_EBCISR; // clear any pending interrupts + + dmac_channel_set_source_addr(DMAC, CONF_SPI_DMAC_TX_CH, reinterpret_cast<uint32_t>(buf)); + dmac_channel_set_destination_addr(DMAC, CONF_SPI_DMAC_TX_CH, reinterpret_cast<uint32_t>(& SPI->SPI_TDR)); + dmac_channel_set_descriptor_addr(DMAC, CONF_SPI_DMAC_TX_CH, 0); + dmac_channel_set_ctrlA(DMAC, CONF_SPI_DMAC_TX_CH, maxTransmitLength | DMAC_CTRLA_SRC_WIDTH_WORD | DMAC_CTRLA_DST_WIDTH_BYTE); + dmac_channel_set_ctrlB(DMAC, CONF_SPI_DMAC_TX_CH, + DMAC_CTRLB_SRC_DSCR | DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_MEM2PER_DMA_FC | DMAC_CTRLB_SRC_INCR_INCREMENTING | DMAC_CTRLB_DST_INCR_FIXED); +#endif +} + +static void spi_rx_dma_setup(const TransactionBuffer *buf) +{ +#if USE_PDC + pdc_packet_t pdc_spi_packet; + pdc_spi_packet.ul_addr = reinterpret_cast<uint32_t>(buf); + pdc_spi_packet.ul_size = TransactionBuffer::MaxReceiveBytes; + pdc_rx_init(spi_pdc, &pdc_spi_packet, NULL); +#endif + +#if USE_DMAC + DMAC->DMAC_EBCISR; // clear any pending interrupts + + dmac_channel_set_source_addr(DMAC, CONF_SPI_DMAC_RX_CH, reinterpret_cast<uint32_t>(& SPI->SPI_RDR)); + dmac_channel_set_destination_addr(DMAC, CONF_SPI_DMAC_RX_CH, reinterpret_cast<uint32_t>(buf)); + dmac_channel_set_descriptor_addr(DMAC, CONF_SPI_DMAC_RX_CH, 0); + dmac_channel_set_ctrlA(DMAC, CONF_SPI_DMAC_RX_CH, TransactionBuffer::MaxTransferBytes | DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_DST_WIDTH_WORD); + dmac_channel_set_ctrlB(DMAC, CONF_SPI_DMAC_RX_CH, + DMAC_CTRLB_SRC_DSCR | DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_PER2MEM_DMA_FC | DMAC_CTRLB_SRC_INCR_FIXED | DMAC_CTRLB_DST_INCR_INCREMENTING); +#endif +} + +/** + * \brief Set SPI slave transfer. + */ +static void spi_slave_dma_setup(bool dataToSend, bool allowReceive) +{ +#if USE_PDC + pdc_disable_transfer(spi_pdc, PERIPH_PTCR_TXTDIS | PERIPH_PTCR_RXTDIS); + + TransactionBuffer *outBufPointer = (dataToSend) ? &outBuffer : reinterpret_cast<TransactionBuffer*>(&dummyOutBuffer); + spi_tx_dma_setup(outBufPointer); + if (allowReceive) + { + outBufPointer->SetDataTaken(); + spi_rx_dma_setup(&inBuffer); + pdc_enable_transfer(spi_pdc, PERIPH_PTCR_TXTEN | PERIPH_PTCR_RXTEN); + } + else + { + outBufPointer->ClearDataTaken(); + pdc_enable_transfer(spi_pdc, PERIPH_PTCR_TXTEN); + } +#endif + +#if USE_DMAC + spi_dma_disable(); + + TransactionBuffer *outBufPointer = (dataToSend) ? &outBuffer : reinterpret_cast<TransactionBuffer*>(&dummyOutBuffer); + if (allowReceive) + { + spi_rx_dma_setup(&inBuffer); + spi_rx_dma_enable(); + outBufPointer->SetDataTaken(); + } + else + { + outBufPointer->ClearDataTaken(); + } + + spi_tx_dma_setup(outBufPointer, (dataToSend) ? TransactionBuffer::MaxTransferBytes : 4 * TransactionBuffer::headerDwords); + spi_tx_dma_enable(); +#endif +} + +// Set up the SPI system +void Network::SetupSpi() +{ +#if USE_PDC + spi_pdc = spi_get_pdc_base(SPI); + // The PDCs are masters 2 and 3 and the SRAM is slave 0. Give the PDCs the highest priority. + matrix_set_master_burst_type(0, MATRIX_ULBT_8_BEAT_BURST); + matrix_set_slave_default_master_type(0, MATRIX_DEFMSTR_LAST_DEFAULT_MASTER); + matrix_set_slave_priority(0, (3 << MATRIX_PRAS0_M2PR_Pos) | (3 << MATRIX_PRAS0_M3PR_Pos)); + matrix_set_slave_slot_cycle(0, 8); +#endif + +#if USE_DMAC + pmc_enable_periph_clk(ID_DMAC); + dmac_init(DMAC); + dmac_set_priority_mode(DMAC, DMAC_PRIORITY_ROUND_ROBIN); + dmac_enable(DMAC); + // The DMAC is master 4 and the SRAM is slave 0. Give the DMAC the highest priority. + matrix_set_slave_default_master_type(0, MATRIX_DEFMSTR_LAST_DEFAULT_MASTER); + matrix_set_slave_priority(0, (3 << MATRIX_PRAS0_M4PR_Pos)); + // Set the slave slot cycle limit. + // If we leave it at the default value of 511 clock cycles, we get transmit underruns due to the HSMCI using the bus for too long. + // A value of 8 seems to work. I haven't tried other values yet. + matrix_set_slave_slot_cycle(0, 8); +#endif + + pmc_enable_periph_clk(ID_SPI); + spi_dma_disable(); + spi_reset(SPI); // this clears the transmit and receive registers and puts the SPI into slave mode + + // Set up the SPI pins + ConfigurePin(g_APinDescription[APIN_SPI_SCK]); + ConfigurePin(g_APinDescription[APIN_SPI_MOSI]); + ConfigurePin(g_APinDescription[APIN_SPI_MISO]); + ConfigurePin(g_APinDescription[APIN_SPI_SS0]); + +#if USE_DMAC + // Configure DMA RX channel + dmac_channel_set_configuration(DMAC, CONF_SPI_DMAC_RX_CH, + DMAC_CFG_SRC_PER(DMA_HW_ID_SPI_RX) | DMAC_CFG_SRC_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ASAP_CFG); + + // Configure DMA TX channel + dmac_channel_set_configuration(DMAC, CONF_SPI_DMAC_TX_CH, + DMAC_CFG_DST_PER(DMA_HW_ID_SPI_TX) | DMAC_CFG_DST_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ASAP_CFG); +#endif +} + +// Start a transfer if necessary. Not called from an ISR. +void Network::TryStartTransfer() +{ + cpu_irq_disable(); + if (state == idle) + { + if (outBuffer.IsReady()) + { + PrepareForTransfer(true, true); + } + else if (digitalRead(EspTransferRequestPin) == HIGH) + { + PrepareForTransfer(false, true); + } + } + else if (state == sending && outBuffer.IsReady()) + { + PrepareForTransfer(true, false); + } + cpu_irq_enable(); +} + +// This is called from the ISR when the ESP requests to send data +void Network::EspRequestsTransfer() +{ + irqflags_t flags = cpu_irq_save(); + if (state == idle) + { + PrepareForTransfer(false, true); + } + cpu_irq_restore(flags); +} + +// Set up the DMA controller and assert transfer ready. Must be called with interrupts disabled. +void Network::PrepareForTransfer(bool dataToSend, bool allowReceive) +//pre(state == idle || state == sending) +{ + if (allowReceive) + { + state = (dataToSend) ? sendReceivePending : receivePending; + } + + // DMA may have transferred an extra word to the SPI transmit data register. We need to clear this. + // The only way I can find to do this is to issue a software reset to the SPI system. + // Fortunately, this leaves the SPI system in slave mode. + spi_reset(SPI); + spi_set_bits_per_transfer(SPI, 0, SPI_CSR_BITS_8_BIT); + + // Set up the DMA controller + spi_slave_dma_setup(dataToSend, allowReceive); + spi_enable(SPI); + + // Enable the end-of transfer interrupt + (void)SPI->SPI_SR; // clear any pending interrupt + SPI->SPI_IER = SPI_IER_NSSR; // enable the NSS rising interrupt + + // Tell the ESP that we are ready to accept data + digitalWrite(SamTfrReadyPin, HIGH); +} + +// SPI interrupt handler, called when NSS goes high +void SPI_Handler() +{ + reprap.GetNetwork()->SpiInterrupt(); +} + +void Network::SpiInterrupt() +{ + uint32_t status = SPI->SPI_SR; // read status and clear interrupt + SPI->SPI_IDR = SPI_IER_NSSR; // disable the interrupt + if ((status & SPI_SR_NSSR) != 0) + { + if (state == sendReceivePending || state == receivePending) + { +#if USE_PDC + pdc_disable_transfer(spi_pdc, PERIPH_PTCR_TXTDIS | PERIPH_PTCR_RXTDIS); +#endif + +#if USE_DMAC + spi_tx_dma_disable(); + dmac_channel_suspend(DMAC, CONF_SPI_DMAC_RX_CH); // suspend the receive channel, don't disable it because the FIFO needs to empty first +#endif + spi_disable(SPI); + digitalWrite(SamTfrReadyPin, LOW); + if (state == sendReceivePending) + { + outBuffer.Clear(); // don't send the data again + } + if ((status & SPI_SR_OVRES) != 0) + { + ++spiRxOverruns; + } + if (state == sendReceivePending && (status & SPI_SR_UNDES) != 0) + { + ++spiTxUnderruns; + } + state = transferDone; + } + else if (state == sending) + { + spi_tx_dma_disable(); + spi_disable(SPI); + digitalWrite(SamTfrReadyPin, LOW); + outBuffer.Clear(); // don't send the data again + if ((status & SPI_SR_UNDES) != 0) + { + ++spiTxUnderruns; + } + } + } +} + +// Reset the ESP8266 and leave held in reset +void Network::ResetWiFi() +{ + pinMode(EspResetPin, OUTPUT); + digitalWrite(EspResetPin, LOW); +} + +// Reset the ESP8266 to take commands from the UART. The caller must wait for the reset to complete after calling this. +// ESP8266 boot modes: +// GPIO0 GPIO2 GPIO15 +// 0 1 0 Firmware download from UART +// 1 1 0 Normal boot from flash memory +// 0 0 1 SD card boot (not used in on Duet) +void Network::ResetWiFiForUpload() +{ + // Make sure the ESP8266 is in the reset state + pinMode(EspResetPin, OUTPUT); + digitalWrite(EspResetPin, LOW); + + // Take the ESP8266 out of power down + pinMode(EspEnablePin, OUTPUT); + digitalWrite(EspEnablePin, HIGH); + + // Set up our transfer request pin (GPIO4) as an output and set it low + pinMode(SamTfrReadyPin, OUTPUT); + digitalWrite(SamTfrReadyPin, LOW); + + // Set up our data ready pin (ESP GPIO0) as an output and set it low ready to boot the ESP from UART + pinMode(EspTransferRequestPin, OUTPUT); + digitalWrite(EspTransferRequestPin, LOW); + + // GPIO2 also needs to be high to boot up. It's connected to MISO on the SAM, so set the pullup resistor on that pin + pinMode(APIN_SPI_MISO, INPUT_PULLUP); + + // Set our CS input (ESP GPIO15) low ready for booting the ESP. This also clears the transfer ready latch. + pinMode(SamCsPin, OUTPUT); + digitalWrite(SamCsPin, LOW); + + // Make sure it has time to reset - no idea how long it needs, but 50ms should be plenty + delay(50); + + // Release the reset on the ESP8266 + digitalWrite(EspResetPin, HIGH); +} + +// Reset the ESP8266 to take commands from an external input. The caller must wait for the reset to complete after calling this. +void Network::ResetWiFiForExternalUpload() +{ + ResetWiFiForUpload(); + + // Set our TxD pin low to make things easier for the FTDI chip to drive the ESP RxD input + pinMode(APIN_UART1_TXD, OUTPUT); + digitalWrite(APIN_UART1_TXD, LOW); +} + +// End diff --git a/src/DuetNG/Network.h b/src/DuetNG/Network.h new file mode 100644 index 00000000..2555b830 --- /dev/null +++ b/src/DuetNG/Network.h @@ -0,0 +1,121 @@ +/**************************************************************************************************** + +RepRapFirmware - Network: RepRapPro Ormerod with Duet controller + +Separated out from Platform.h by dc42 and extended by zpl + +****************************************************************************************************/ + +#ifndef NETWORK_H +#define NETWORK_H + +#include <cctype> +#include <cstring> +#include <cstdlib> +#include <climits> + +#include "MessageType.h" + +// Return code definitions +const uint32_t rcNumber = 0x0000FFFF; +const uint32_t rcJson = 0x00010000; +const uint32_t rcKeepOpen = 0x00020000; + +static const uint8_t IP_ADDRESS[4] = { 192, 168, 1, 10 }; // Need some sort of default... +static const uint8_t NET_MASK[4] = { 255, 255, 255, 0 }; +static const uint8_t GATE_WAY[4] = { 192, 168, 1, 1 }; +static const uint16_t DEFAULT_HTTP_PORT = 80; + +class TransactionBuffer; +class WifiFirmwareUploader; + +// The main network class that drives the network. +class Network +{ + enum TransferState + { + disabled, // WiFi not active + enabled, // WiFi enabled but not started yet + starting, // starting up (waiting for WiFi to initialise) + idle, // nothing happening + receivePending, // we have asserted TransferReady and await completion of a receive-only transaction + sendReceivePending, // we have asserted TransferReady and await completion of a transmit/receive + transferDone, // transfer completed but receive DMA fifo may not have been flushed yet + processing, // a transaction has been completed but we haven't released the input buffer yet + sending // a transaction has been completed and we are sending the response + }; + +public: + const uint8_t *IPAddress() const; + void SetIPAddress(const uint8_t ipAddress[], const uint8_t netmask[], const uint8_t gateway[]); + + Network(Platform* p); + void Init(); + void Activate(); + void Exit(); + void Spin(); + void SpiInterrupt(); + void Diagnostics(MessageType mtype); + void Start(); + void Stop(); + + bool InLwip() const { return false; } + + void Enable(); + void Disable(); + bool IsEnabled() const; + + void SetHttpPort(uint16_t port); + uint16_t GetHttpPort() const; + + void SetHostname(const char *name); + void EspRequestsTransfer(); + +#if 0 + void AcquireBus(); + void ReleaseBus(); +#endif + + const char *GetRequest(uint32_t& ip, size_t& length, uint32_t& fragment) const; + void SendReply(uint32_t ip, unsigned int code, OutputBuffer *body); + void SendReply(uint32_t ip, unsigned int code, const char *text); + void SendReply(uint32_t ip, unsigned int code, FileStore *file); + void DiscardMessage(); + + WifiFirmwareUploader *GetWifiUploader() { return uploader; } + + static void ResetWiFi(); + static void ResetWiFiForUpload(); + static void ResetWiFiForExternalUpload(); + +private: + void SetupSpi(); + void PrepareForTransfer(bool dataToSend, bool allowReceive); + void ProcessIncomingData(TransactionBuffer &buf); + void ClearIpAddress(); + void TryStartTransfer(); + void DebugPrintResponse(); + + Platform *platform; + WifiFirmwareUploader *uploader; + + uint32_t responseIp; + uint32_t responseCode; + uint32_t responseFragment; + OutputBuffer *responseBody; + const char* responseText; + FileStore *responseFile; + uint32_t responseFileBytes; + + uint32_t spiTxUnderruns; + uint32_t spiRxOverruns; + + float longWait; + TransferState state; + bool activated; + bool connectedToAp; + uint8_t ipAddress[4]; + char hostname[16]; // Limit DHCP hostname to 15 characters + terminating 0 +}; + +#endif diff --git a/src/DuetNG/Pins_DuetNG.h b/src/DuetNG/Pins_DuetNG.h new file mode 100644 index 00000000..1a80ea9d --- /dev/null +++ b/src/DuetNG/Pins_DuetNG.h @@ -0,0 +1,186 @@ +#ifndef PINS_DUETNG_H__ +#define PINS_DUETNG_H__ + +#define NAME "RepRapFirmware for Duet WiFi" + +const size_t NumFirmwareUpdateModules = 4; // 3 modules, plus one for manual upload to WiFi module +#define IAP_UPDATE_FILE "iap4e.bin" +#define IAP_FIRMWARE_FILE "DuetWiFiFirmware.bin" +#define WIFI_FIRMWARE_FILE "DuetWiFiServer.bin" +#define WIFI_WEB_FILE "DuetWebControl.bin" + +// Default board type +#ifdef PROTOTYPE_1 +#define DEFAULT_BOARD_TYPE BoardType::DuetNG_06 +#else +#define DEFAULT_BOARD_TYPE BoardType::DuetNG_10 +#endif + +#define SUPPORT_ETHERNET 0 // set nonzero to support embedded web interface over Ethernet +#define SUPPORT_INKJET 0 // set nonzero to support inkjet control +#define SUPPORT_ROLAND 0 // set nonzero to support Roland mill + +// The physical capabilities of the machine + +const size_t DRIVES = 9; // The number of drives in the machine, including X, Y, and Z plus extruder drives +#define DRIVES_(a,b,c,d,e,f,g,h,i) { a,b,c,d,e,f,g,h,i } + +// If enabled, the following control the use of the optional ExternalDrivers module +#define EXTERNAL_DRIVERS (9) +#define FIRST_EXTERNAL_DRIVE (0) + +const int8_t HEATERS = 7; // The number of heaters in the machine; 0 is the heated bed even if there isn't one +#define HEATERS_(a,b,c,d,e,f,g) { a,b,c,d,e,f,g } + +const size_t AXES = 3; // The number of movement axes in the machine, usually just X, Y and Z. <= DRIVES +const size_t NUM_SERIAL_CHANNELS = 2; // The number of serial IO channels (USB and one auxiliary UART) +#define SERIAL_MAIN_DEVICE SerialUSB +#define SERIAL_AUX_DEVICE Serial + +// The numbers of entries in each array must correspond with the values of DRIVES, AXES, or HEATERS. Set values to -1 to flag unavailability. + +// DRIVES + +const Pin ENABLE_PINS[DRIVES] = { 78, 41, 42, 49, 57, 87, 88, 89, 90 }; +const bool ENABLE_VALUES[DRIVES] = { false, false, false, false, false, false, false, false, false }; // What to send to enable a drive +const Pin STEP_PINS[DRIVES] = { 70, 71, 72, 69, 68, 66, 65, 64, 67 }; +const Pin DIRECTION_PINS[DRIVES] = { 75, 76, 77, 01, 73, 92, 86, 80, 81 }; +const bool DIRECTIONS[DRIVES] = { BACKWARDS, FORWARDS, FORWARDS, FORWARDS, FORWARDS, FORWARDS, FORWARDS, FORWARDS, FORWARDS }; // What each axis needs to make it go forwards - defaults + +// Endstops +// RepRapFirmware only has a single endstop per axis. gcode defines if it is a max ("high end") or min ("low end") endstop. gcode also sets if it is active HIGH or LOW. +const Pin END_STOP_PINS[DRIVES] = { 46, 02, 93, 74, 48, 96, 97, 98, 99 }; + +// Indices for motor current digipots (if any): first 4 are for digipot 1 (on duet), second 4 for digipot 2 (on expansion board) +#ifdef PROTOTYPE_1 +const uint8_t POT_WIPES[8] = { 1, 3, 2, 0, 1, 3, 2, 0 }; +const float SENSE_RESISTOR = 0.1; // Stepper motor current sense resistor +const float MAX_STEPPER_DIGIPOT_VOLTAGE = (3.3 * 2.5 / (2.7 + 2.5)); // Stepper motor current reference voltage +const float STEPPER_DAC_VOLTAGE_RANGE = 2.02; // Stepper motor current reference voltage for E1 if using a DAC +const float STEPPER_DAC_VOLTAGE_OFFSET = -0.11; // Stepper motor current offset voltage for E1 if using a DAC +#endif + +// HEATERS + +const bool HEAT_ON = false; // false for inverted heater (e.g. Duet v0.6), true for not (e.g. Duet v0.4) + +const Pin TEMP_SENSE_PINS[HEATERS] = { 45, 47, 44, 61, 62, 63, 59 }; // Thermistor pin numbers + +#ifdef PROTOTYPE_1 +const Pin HEAT_ON_PINS[HEATERS] = { 19, 20, 16, 15, 37, 40, 43 }; // Heater pin numbers +#else +const Pin HEAT_ON_PINS[HEATERS] = { 19, 20, 16, 35, 37, 40, 43 }; // Heater pin numbers +#endif + +// Default thermistor parameters +// Bed thermistor: now assuming 100K +// Hot end thermistor: http://www.digikey.co.uk/product-search/en?x=20&y=11&KeyWords=480-3137-ND +const float BED_R25 = 100000.0; +const float BED_BETA = 3988.0; +const float EXT_R25 = 100000.0; +const float EXT_BETA = 4388.0; + +// Thermistor series resistor value in Ohms +const float THERMISTOR_SERIES_RS = 4700.0; + +// Number of SPI temperature sensors to support + +#if SUPPORT_ROLAND + +// chrishamm's pin assignments +const size_t MaxSpiTempSensors = 2; + +// Digital pins the 31855s have their select lines tied to +const Pin SpiTempSensorCsPins[MaxSpiTempSensors] = { 56, 27 }; + +#else + +const size_t MaxSpiTempSensors = 4; + +// Digital pins the 31855s have their select lines tied to +# ifdef PROTOTYPE_1 +const Pin SpiTempSensorCsPins[MaxSpiTempSensors] = { 24, 25, 50, 51 }; // SPI1_CS0, SPI1_CS1, CS2, CS3 +# else +const Pin SpiTempSensorCsPins[MaxSpiTempSensors] = { 28, 50, 51, 52 }; // SPI0_CS1, SPI0_CS2, CS3, CS4 +# endif + +#endif + +// Arduino Due pin number that controls the ATX power on/off +const Pin ATX_POWER_PIN = 79; // Arduino Due pin number that controls the ATX power on/off + +// Analogue pin numbers +const Pin Z_PROBE_PIN = 33; // Z probe analog input + +// Digital pin number to turn the IR LED on (high) or off (low) +const Pin Z_PROBE_MOD_PIN = 34; // Digital pin number to turn the IR LED on (high) or off (low) on Duet v0.6 and v1.0 (PB21) + +// COOLING FANS + +const size_t NUM_FANS = 3; +const Pin COOLING_FAN_PINS[NUM_FANS] = { 55, 58, 00 }; +const Pin COOLING_FAN_RPM_PIN = 32; + +#if SUPPORT_INKJET +// Inkjet control pins +const Pin INKJET_SERIAL_OUT = xx; // Serial bitpattern into the shift register +const Pin INKJET_SHIFT_CLOCK = xx; // Shift the register +const Pin INKJET_STORAGE_CLOCK = xx; // Put the pattern in the output register +const Pin INKJET_OUTPUT_ENABLE = xx; // Make the output visible +const Pin INKJET_CLEAR = xx; // Clear the register to 0 + +#endif + +#if SUPPORT_ROLAND +// Roland mill +const Pin ROLAND_CTS_PIN = xx; // Expansion pin 11, PA12_TXD1 +const Pin ROLAND_RTS_PIN = xx; // Expansion pin 12, PA13_RXD1 + +#endif + +// Definition of which pins we allow to be controlled using M42 +// +// The allowed pins are these ones on the DueX4 expansion connector: +//TODO document these + +const size_t NUM_PINS_ALLOWED = 96; + +#if 1 +#define PINS_ALLOWED { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; //TODO temporary! +#else +#define PINS_ALLOWED { \ + /* pins 00-07 */ 0b00000000, \ + /* pins 08-15 */ 0b00000000, \ + /* pins 16-23 */ 0b00000110, \ + /* pins 24-31 */ 0b10000011, \ + /* pins 32-39 */ 0b00001001, \ + /* pins 40-47 */ 0b00000000, \ + /* pins 48-55 */ 0b00011000, \ + /* pins 56-63 */ 0b00000000, \ + /* pins 64-71 */ 0b00000000, \ + /* pins 72-79 */ 0b00000000, \ + /* pins 80-87 */ 0b00000000, \ + /* pins 88-95 */ 0b00001000 \ +} +#endif +// SAM4E Flash locations (may be expanded in the future) +const uint32_t IAP_FLASH_START = 0x00470000; +const uint32_t IAP_FLASH_END = 0x0047FBFF; + +// Duet pin numbers to control the WiFi interface +const Pin EspResetPin = 100; // Low on this in holds the WiFi module in reset (ESP_RESET) +const Pin EspEnablePin = 101; // High to enable the WiFi module, low to power it down (ESP_CH_PD) +const Pin EspTransferRequestPin = 95; // Input from the WiFi module indicating that it wants to transfer data (ESP GPIO0) +const Pin SamTfrReadyPin = 94; // Output from the SAM to the WiFi module indicating we can accept a data transfer (ESP GPIO4 via 7474) +const Pin SamCsPin = 11; // SPI NPCS pin, input from WiFi module + +// Timer allocation (no network timer on DuetNG) +// TC0 channel 0 is used for FAM2 +// TC0 channel 1 is used for the TMC clock +// TC0 channel 2 is available for us to use +#define STEP_TC (TC0) +#define STEP_TC_CHAN (2) +#define STEP_TC_IRQN TC2_IRQn +#define STEP_TC_HANDLER TC2_Handler + +#endif diff --git a/src/DuetNG/TransactionBuffer.cpp b/src/DuetNG/TransactionBuffer.cpp new file mode 100644 index 00000000..7179d4e0 --- /dev/null +++ b/src/DuetNG/TransactionBuffer.cpp @@ -0,0 +1,58 @@ +/* + * TransactionBuffer.cpp + * + * Created on: 17 May 2016 + * Author: David + */ + +#include "Core.h" +#include "TransactionBuffer.h" +#include <cstring> + +void TransactionBuffer::Clear() +{ + trType = 0; + ip = 0; + seq = 0; + fragment = 0; + dataLength = 0; +} + +bool TransactionBuffer::SetMessage(uint32_t tt, uint32_t p_ip, uint32_t frag) +{ + if (IsReady()) + { + return false; + } + trType = tt; + ip = p_ip; + seq = 0; + fragment = frag; + dataLength = 0; + return true; +} + +// Append data to the message in the output buffer, returning the length appended +size_t TransactionBuffer::AppendData(const void* dataToAppend, uint32_t length) +{ + uint32_t spaceLeft = maxSpiDataLength - dataLength; + uint32_t bytesToCopy = min<size_t>(length, spaceLeft); + memcpy((char*)data + dataLength, dataToAppend, bytesToCopy); + dataLength += bytesToCopy; + return bytesToCopy; +} + +// Get the address and size to write data into +char *TransactionBuffer::GetBuffer(size_t& length) +{ + length = maxSpiDataLength - dataLength; + return reinterpret_cast<char*>(data) + dataLength; +} + +// Say we have appended some data +void TransactionBuffer::DataAppended(size_t length) +{ + dataLength += length; +} + +// End diff --git a/src/DuetNG/TransactionBuffer.h b/src/DuetNG/TransactionBuffer.h new file mode 100644 index 00000000..e42d5531 --- /dev/null +++ b/src/DuetNG/TransactionBuffer.h @@ -0,0 +1,165 @@ +/* + * TransactionBuffer.h + * + * Created on: 17 May 2016 + * Author: David + */ + +#ifndef SRC_DUETNG_TRANSACTIONBUFFER_H_ +#define SRC_DUETNG_TRANSACTIONBUFFER_H_ + +#include <cstdint> + +//------------------------------------------------------------------------------------------- +// Transaction buffer class +// ***** This must be kept in step with the corresponding class in the WiFi module code ***** +const uint32_t maxSpiDataLength = 2048; + +//--------------------------------------------------- +// SPI transaction type field bits +// Byte 3 (MSB) is the packet type. +// Byte 2 holds flags +// Byte 1 is currently unused +// Byte 0 is the opcode if the packet is a request or info message, or the error code if it is a response. + +// Packet types +const uint32_t trTypeRequest = 0x3A000000; // this is a request +const uint32_t trTypeResponse = 0xB6000000; // this is a response to a request +const uint32_t trTypeInfo = 0x93000000; // this is an informational message that does not require a response + +// Flags +const uint32_t ttDataTaken = 0x00010000; // indicates to the SAM the the ESP8266 has read its data, or vice verse + +// Opcodes for requests from web sever to Duet +const uint32_t ttRr = 0x01; // any request starting with "rr_" + +// Opcodes for info messages from web server to Duet +const uint32_t ttNetworkInfo = 0x70; // pass network info to Duet, e.g. when first connected + +// Opcodes for requests and info from Duet to web server +const uint32_t ttNetworkConfig = 0x80; // set network configuration (SSID, password etc.) +const uint32_t ttNetworkEnable = 0x81; // enable WiFi +const uint32_t ttGetNetworkInfo = 0x83; // get IP address etc. + +// Opcodes for info messages from Duet to server +const uint32_t ttMachineConfigChanged = 0x82; // notify server that the machine configuration has changed significantly + +// Last fragment bit +const uint32_t lastFragment = 0x80000000; + +class TransactionBuffer +{ + uint32_t trType; // type of transaction, and flags + uint32_t seq; // sequence number of the request + uint32_t ip; // requesting IP address + uint32_t fragment; // fragment number of this packet, top bit set if last fragment + uint32_t dataLength; // number of bytes of data following the header + uint32_t data[maxSpiDataLength/4]; // the actual data, if needed + uint32_t dummy; // extra so that we can append a null and to allow overrun to be detected + +public: + static const uint32_t headerDwords = 5; + + static const uint32_t MaxTransferBytes = maxSpiDataLength + (4 * headerDwords); + + // Initialise + void Init(); + + // Mark this buffer empty + void Clear(); + + // When we send this packet, tell the ESP that we have taken its data + void SetDataTaken() { trType |= ttDataTaken; } + + // Clear the DataTaken flag + void ClearDataTaken() { trType &= ~ttDataTaken; } + + // Say whether data was taken + bool DataWasTaken() const { return (trType & ttDataTaken) != 0; } + + // Return true if this buffer contains data + bool IsReady() const + { + return trType != 0; + } + + bool IsValid() const + { + return dataLength <= maxSpiDataLength; + } + + bool IsEmpty() const + { + return trType == 0; + } + + uint32_t GetOpcode() const + { + return trType; + } + + uint32_t GetSeq() const + { + return seq; + } + + uint32_t GetIp() const + { + return ip; + } + + uint32_t GetFragment() const + { + return fragment; + } + + uint32_t GetLength() const + { + return dataLength; + } + + const void* GetData(size_t& length) const + { + length = dataLength; + return data; + } + + void AppendNull() + { + if (IsReady()) + { + *((char *)data + dataLength) = 0; + } + } + + // Get SPI packet length in dwords + uint32_t PacketLength() const + { + return (IsReady()) ? (dataLength + 3)/4 + headerDwords : headerDwords; + } + + // Set up a message in this buffer + bool SetMessage(uint32_t tt, uint32_t p_ip, uint32_t frag); + + // Append data to the message in the output buffer, returning the length appended + size_t AppendData(const void* dataToAppend, uint32_t length); + + // Append a single 32-bit integer to the output buffer + size_t AppendU32(uint32_t val) + { + return AppendData(&val, sizeof(val)); + } + + void SetLastFragment() + { + fragment |= lastFragment; + } + + // Get the address and remaining size to write data into + char *GetBuffer(size_t& length); + + // Say we have appended some data + void DataAppended(size_t length); +}; + +#endif /* SRC_DUETNG_TRANSACTIONBUFFER_H_ */ diff --git a/src/DuetNG/Webserver.cpp b/src/DuetNG/Webserver.cpp new file mode 100644 index 00000000..27312f03 --- /dev/null +++ b/src/DuetNG/Webserver.cpp @@ -0,0 +1,1059 @@ +/**************************************************************************************************** + + RepRapFirmware - Webserver + + This class serves a single-page web applications to the attached network. This page forms the user's + interface with the RepRap machine. This software interprests returned values from the page and uses it + to generate G Codes, which it sends to the RepRap. It also collects values from the RepRap like + temperature and uses those to construct the web page. + + The page itself - reprap.htm - uses Jquery.js to perform AJAX. See: + + http://jquery.com/ + + ----------------------------------------------------------------------------------------------------- + + Version 0.2 + + 10 May 2013 + + Adrian Bowyer + RepRap Professional Ltd + http://reprappro.com + + Licence: GPL + + ----------------------------------------------------------------------------------------------------- + + The supported requests are GET requests for files (for which the root is the www directory on the + SD card), and the following. These all start with "/rr_". Ordinary files used for the web interface + must not have names starting "/rr_" or they will not be found. + + rr_connect?password=xxx + Sent by the web interface software to establish an initial connection, indicating that + any state variables relating to the web interface (e.g. file upload in progress) should + be reset. This only happens if the password could be verified. + + rr_fileinfo Returns file information about the file being printed. + + rr_fileinfo?name=xxx + Returns file information about a file on the SD card or a JSON-encapsulated response + with err = 1 if the passed filename was invalid. + + rr_status New-style status response, in which temperatures, axis positions and extruder positions + are returned in separate variables. Another difference is that extruder positions are + returned as absolute positions instead of relative to the previous gcode. A client + may also request different status responses by specifying the "type" keyword, followed + by a custom status response type. Also see "M105 S1". + + rr_files?dir=xxx&flagDirs={1/0} + Returns a listing of the filenames in the /gcode directory of the SD card. 'dir' is a + directory path relative to the root of the SD card. If the 'dir' variable is not present, + it defaults to the /gcode directory. If flagDirs is set to 1, all directories will be + prefixed by an asterisk. + + rr_reply Returns the last-known G-code reply as plain text (not encapsulated as JSON). + + rr_upload?name=xxx + Upload a specified file using a POST request. The payload of this request has to be + the file content. Only one file may be uploaded at once. When the upload has finished, + a JSON response with the variable "err" will be returned, which will be 0 if the job + has finished without problems, it will be set to 1 otherwise. + + rr_upload_begin?name=xxx + Indicates that we wish to upload the specified file. xxx is the filename relative + to the root of the SD card. The directory component of the filename must already + exist. Returns variables ubuff (= max upload data we can accept in the next message) + and err (= 0 if the file was created successfully, nonzero if there was an error). + + rr_upload_data?data=xxx + Provides a data block for the file upload. Returns the samwe variables as rr_upload_begin, + except that err is only zero if the file was successfully created and there has not been + a file write error yet. This response is returned before attempting to write this data block. + + rr_upload_end + Indicates that we have finished sending upload data. The server closes the file and reports + the overall status in err. It may also return ubuff again. + + rr_upload_cancel + Indicates that the user wishes to cancel the current upload. Returns err and ubuff. + + rr_delete?name=xxx + Delete file xxx. Returns err (zero if successful). + + rr_mkdir?dir=xxx + Create a new directory xxx. Return err (zero if successful). + + rr_move?old=xxx&new=yyy + Rename an old file xxx to yyy. May also be used to move a file to another directory. + + ****************************************************************************************************/ + +#include "RepRapFirmware.h" + +const char* overflowResponse = "overflow"; +const char* badEscapeResponse = "bad escape"; + +// Constructor and initialisation +Webserver::Webserver(Platform* p, Network *n) : state(doingFilename), platform(p), network(n), numSessions(0), clientsServed(0) +{ + gcodeReadIndex = gcodeWriteIndex = 0; + gcodeReply = new OutputStack(); + processingDeferredRequest = false; + seq = 0; + webserverActive = false; +} + +void Webserver::Init() +{ + // initialise the webserver class + longWait = platform->Time(); + webserverActive = true; + numSessions = clientsServed = 0; + uploadIp = 0; + + // initialise all protocol handlers + ResetState(); +} + +// Deal with input/output from/to the client (if any) +void Webserver::Spin() +{ + if (webserverActive) + { + uint32_t ip; + size_t length; + uint32_t fragment; + const char* request = network->GetRequest(ip, length, fragment); + if (request != nullptr) + { + if (reprap.Debug(moduleWebserver)) + { + platform->MessageF(HOST_MESSAGE, "Request: %s fragment %u\n", request, fragment); + } + + uint32_t fragNum = fragment & ~lastFragmentFlag; + if (fragNum == 0) + { + HttpSession *session = StartSession(ip); + if (session == nullptr) + { + network->SendReply(ip, 400, "Too many sessions"); + } + else + { + // First fragment, so parse the request + ResetState(); + const char *error = nullptr; + bool finished = false; + while (!finished && length != 0) + { + finished = CharFromClient(*request++, error); + --length; + } + + if (!finished) + { + error = "Incomplete command"; + } + + if (error != nullptr) + { + network->SendReply(ip, 400, error); + } + else + { + ProcessFirstFragment(*session, clientMessage, (fragment & lastFragmentFlag) != 0); + } + } + } + else + { + HttpSession *session = FindSession(ip); + if (session != nullptr) + { + ProcessUploadFragment(*session, request, length, fragment); + } + else + { + // Discard the message + network->DiscardMessage(); + platform->MessageF(DEBUG_MESSAGE, "session not found, fragment=%u\n", fragment); + } + } + } + } + CheckSessions(); + platform->ClassReport(longWait); +} + +// This is called to process a file upload request. +void Webserver::StartUpload(HttpSession& session, const char* fileName, uint32_t fileLength) +{ + CancelUpload(session); + if (uploadIp != 0) + { + session.uploadState = uploadBusy; // upload buffer already in use + } + else + { + FileStore *file = platform->GetFileStore(FS_PREFIX, fileName, true); + if (file == nullptr) + { + session.uploadState = cantCreate; + } + else + { + session.fileBeingUploaded.Set(file); + session.postLength = fileLength; + session.bytesWritten = 0; + session.nextFragment = 1; + session.uploadState = uploading; + session.bufNumber = 0; + session.bufPointer = 0; + uploadIp = session.ip; + } + } +} + +// This is called when we have received the last upload packet +void Webserver::FinishUpload(HttpSession& session) +{ + bool b = session.fileBeingUploaded.Close(); + + if (session.uploadState == uploading) + { + if (!b) + { + session.uploadState = cantClose; + } + else if (session.bytesWritten != session.postLength) + { + session.uploadState = wrongLength; + } + } + + if (session.uploadState != uploading) + { + platform->MessageF(HOST_MESSAGE, "Error: Upload finished in state %d\n", (int)session.uploadState); + } + + network->SendReply(session.ip, 200 | rcJson, (session.uploadState == uploading) ? "{\"err\":0}" : "{\"err\":1}"); + session.uploadState = notUploading; +} + +void Webserver::CancelUpload(HttpSession& session) +{ + if (session.fileBeingUploaded.IsLive()) + { + session.fileBeingUploaded.Close(); + //TODO delete the file as well + } + if (uploadIp == session.ip) + { + uploadIp = 0; + } +} + +void Webserver::ProcessUploadFragment(HttpSession& session, const char* request, size_t length, uint32_t fragment) +{ + if (session.uploadState == uploading) + { + //platform->MessageF(HOST_MESSAGE, "writing fragment=%u\n", fragment); + uint32_t frag = fragment & ~lastFragmentFlag; + if (frag != session.nextFragment) + { + platform->MessageF(HOST_MESSAGE, "expecting fragment %u received %u\n", session.nextFragment, frag); + session.uploadState = wrongFragment; + } + } + + if (session.uploadState == uploading) + { + size_t bytesToCopy = uploadBufLength - session.bufPointer; + if (bytesToCopy > length) + { + bytesToCopy = length; + } + memcpy(reinterpret_cast<uint8_t*>(uploadBuffers[session.bufNumber]) + session.bufPointer, request, bytesToCopy); + session.bufPointer += bytesToCopy; + if (session.bufPointer == uploadBufLength) + { + // Switch to the other buffer. We know the rest of the data will fit because the buffer size is bigger than the SPI packet size. + session.bufNumber ^= 1; + if (bytesToCopy < length) + { + memcpy(uploadBuffers[session.bufNumber], request + bytesToCopy, length - bytesToCopy); + } + session.bufPointer = length - bytesToCopy; + + // Free up the SPI buffer before we start the file write + if ((fragment & lastFragmentFlag) == 0) + { + network->DiscardMessage(); + } + + if (session.fileBeingUploaded.Write(reinterpret_cast<const char*>(uploadBuffers[session.bufNumber ^ 1]), uploadBufLength)) + { + session.bytesWritten += uploadBufLength; + } + else + { + session.uploadState = cantWrite; + } + } + else if ((fragment & lastFragmentFlag) == 0) + { + network->DiscardMessage(); // free up the SPI buffer + } + } + else if ((fragment & lastFragmentFlag) == 0) + { + network->DiscardMessage(); // free up the SPI buffer + } + ++session.nextFragment; + + if ((fragment & lastFragmentFlag) != 0) + { + if (session.bufPointer != 0 && session.uploadState == uploading) + { + if (session.fileBeingUploaded.Write(reinterpret_cast<const char*>(uploadBuffers[session.bufNumber]), session.bufPointer)) + { + session.bytesWritten += session.bufPointer; + } + else + { + session.uploadState = cantWrite; + } + + } + FinishUpload(session); + } +} + +// Start a new session for this requester. Return nullptr if on more sessions available. +Webserver::HttpSession *Webserver::StartSession(uint32_t ip) +{ + HttpSession *s = FindSession(ip); + if (s != nullptr) + { + // Abandon the existing session for this requester and start a new one + s->nextFragment = 0; + s->fileBeingUploaded.Close(); // TODO delete any partially-uploaded file + return s; + } + + // Find an empty session + if (numSessions < maxHttpSessions) + { + s = &sessions[numSessions]; + s->ip = ip; + s->isAuthenticated = false; + s->nextFragment = 0; + s->fileBeingUploaded.Close(); // make sure no file is open + s->lastQueryTime = platform->Time(); + ++numSessions; + return s; + } + return nullptr; +} + +// Find an existing session for this requester, returning nullptr if there isn't one +Webserver::HttpSession *Webserver::FindSession(uint32_t ip) +{ + for (size_t i = 0; i < numSessions; ++i) + { + HttpSession *s = &sessions[i]; + if (s->ip == ip) + { + s->lastQueryTime = platform->Time(); + return s; + } + } + + return nullptr; +} + +// Delete a session +void Webserver::DeleteSession(uint32_t ip) +{ + for (size_t i = 0; i < numSessions; ++i) + { + HttpSession *s = &sessions[i]; + if (s->ip == ip) + { + s->fileBeingUploaded.Close(); // TODO delete it as well + for (size_t j = i + 1; j < numSessions; ++j) + { + memcpy(&sessions[j - 1], &sessions[j], sizeof(HttpSession)); + } + --numSessions; + break; + } + } + if (uploadIp == ip) + { + uploadIp = 0; // free the upload buffer + } +} + +void Webserver::Exit() +{ + platform->Message(GENERIC_MESSAGE, "Webserver class exited.\n"); + webserverActive = false; +} + +void Webserver::ResetState() +{ + clientPointer = 0; + state = doingFilename; + numQualKeys = 0; + processingDeferredRequest = false; +} + +void Webserver::Diagnostics(MessageType mtype) +{ + platform->Message(mtype, "Webserver Diagnostics:\n"); + platform->MessageF(mtype, "HTTP sessions: %d of %d\n", numSessions, maxHttpSessions); +} + +bool Webserver::GCodeAvailable(const WebSource source) const +{ + switch (source) + { + case WebSource::HTTP: + return gcodeReadIndex != gcodeWriteIndex; + + case WebSource::Telnet: + // Telnet not supported + return false; + } + + return false; +} + +char Webserver::ReadGCode(const WebSource source) +{ + switch (source) + { + case WebSource::HTTP: + if (gcodeReadIndex != gcodeWriteIndex) + { + char c = gcodeBuffer[gcodeReadIndex]; + gcodeReadIndex = (gcodeReadIndex + 1u) % gcodeBufferLength; + return c; + } + break; + + case WebSource::Telnet: + // Telnet not supported + return 0; + } + + return 0; +} + +void Webserver::HandleGCodeReply(const WebSource source, OutputBuffer *reply) +{ + switch (source) + { + case WebSource::HTTP: +#if 0 + OutputBuffer::ReleaseAll(reply); +#else + if (reply != nullptr) + { + if (numSessions > 0) + { + // FIXME: This might cause G-code responses to be sent twice to fast HTTP clients, but + // I (chrishamm) cannot think of a nicer way to deal with slow clients at the moment... + gcodeReply->Push(reply); + clientsServed = 0; + seq++; + } + else + { + // Don't use buffers that may never get released... + OutputBuffer::ReleaseAll(reply); + } + } +#endif + break; + + case WebSource::Telnet: + // Telnet not supported + default: + OutputBuffer::ReleaseAll(reply); + break; + } +} + +void Webserver::HandleGCodeReply(const WebSource source, const char *reply) +{ + switch (source) + { + case WebSource::HTTP: +#if 0 +#else + if (numSessions > 0) + { + OutputBuffer *buffer = gcodeReply->GetLastItem(); + if (buffer == nullptr || buffer->IsReferenced()) + { + if (!OutputBuffer::Allocate(buffer)) + { + // No more space available, stop here + return; + } + gcodeReply->Push(buffer); + } + + buffer->cat(reply); + seq++; + } +#endif + break; + + case WebSource::Telnet: + default: + break; + } +} + +//---------------------------------------------------------------------------------------------------- + +// Process the first fragment of input from the client. +// Return true if the session should be kept open. +bool Webserver::ProcessFirstFragment(HttpSession& session, const char* command, bool isOnlyFragment) +{ + // Get the first two key/value pairs + const char* key1 = (numQualKeys >= 1) ? qualifiers[0].key : ""; + const char* value1 = (numQualKeys >= 1) ? qualifiers[0].value : ""; + const char* key2 = (numQualKeys >= 1) ? qualifiers[1].key : ""; + const char* value2 = (numQualKeys >= 1) ? qualifiers[1].value : ""; + + // Process connect messages first + if (StringEquals(command, "connect") && StringEquals(key1, "password")) + { + const char *response; + if (session.isAuthenticated) + { + // This IP is already authenticated, no need to check the password again + response = "{\"err\":0}"; + } + else if (reprap.CheckPassword(value1)) + { + session.isAuthenticated = true; + response = "{\"err\":0}"; + } + else + { + // Wrong password + response = "{\"err\":1}"; + } + network->SendReply(session.ip, 200 | rcJson, response); + return false; + } + + if (StringEquals(command, "disconnect")) + { + network->SendReply(session.ip, 200 | rcJson, "{\"err\":0}"); + DeleteSession(session.ip); + return false; + } + + // Try to authorise the user automatically to retain compatibility with the old web interface + if (!session.isAuthenticated && reprap.NoPasswordSet()) + { + session.isAuthenticated = true; + } + + // If the client is still not authenticated, stop here + if (!session.isAuthenticated) + { + network->SendReply(session.ip, 500, "Not authorized"); + return false; + } + + if (StringEquals(command, "reply")) + { + SendGCodeReply(session); + return false; + } + + // rr_configfile sends the config as plain text well + if (StringEquals(command, "configfile")) + { + SendConfigFile(session); + return false; + } + + if (StringEquals(command, "upload")) + { + if (StringEquals(key1, "name") && StringEquals(key2, "length")) + { + // Deal with file upload request + uint32_t fileLength = atol(value2); + StartUpload(session, value1, fileLength); + if (session.uploadState == uploading) + { + if (isOnlyFragment) + { + FinishUpload(session); + return false; + } + else + { + network->DiscardMessage(); // no reply needed yet + return true; // expecting more data + } + } + } + + network->SendReply(session.ip, 200 | rcJson, "{\"err\":1}"); // TODO return the cause of the error + return false; + } + + if (StringEquals(command, "move")) + { + const char* response = "{\"err\":1}"; // assume failure + if (StringEquals(key1, "old") && StringEquals(key2, "new")) + { + bool success = platform->GetMassStorage()->Rename(value1, value2); + if (success) + { + response = "{\"err\":0}"; + } + } + network->SendReply(session.ip, 200 | rcJson, response); + return false; + } + + if (StringEquals(command, "mkdir")) + { + const char* response = "{\"err\":1}"; // assume failure + if (StringEquals(key1, "dir")) + { + bool ok = (platform->GetMassStorage()->MakeDirectory(value1)); + if (ok) + { + response = "{\"err\":0}"; + } + } + network->SendReply(session.ip, 200 | rcJson, response); + return false; + } + + if (StringEquals(command, "delete")) + { + const char* response = "{\"err\":1}"; // assume failure + if (StringEquals(key1, "name")) + { + bool ok = platform->GetMassStorage()->Delete("0:/", value1); + if (ok) + { + response = "{\"err\":0}"; + } + } + network->SendReply(session.ip, 200 | rcJson, response); + return false; + } + + // The remaining commands use an OutputBuffer for the response + OutputBuffer *response = nullptr; + if (StringEquals(command, "status")) + { + int type = 0; + if (StringEquals(key1, "type")) + { + // New-style JSON status responses + type = atoi(value1); + if (type < 1 || type > 3) + { + type = 1; + } + + response = reprap.GetStatusResponse(type, ResponseSource::HTTP); + } + else + { + response = reprap.GetLegacyStatusResponse(1, 0); + } + } + else if (StringEquals(command, "gcode")) + { + if (StringEquals(key1, "gcode")) + { + LoadGcodeBuffer(value1); + if (OutputBuffer::Allocate(response)) + { + response->printf("{\"buff\":%u}", GetGCodeBufferSpace()); + } + } + else + { + network->SendReply(session.ip, 200 | rcJson, "{\"err\":1}"); + return false; + } + } + else if (StringEquals(command, "files")) + { + const char* dir = (StringEquals(key1, "dir")) ? value1 : platform->GetGCodeDir(); + bool flagDirs = false; + if (numQualKeys >= 2) + { + if (StringEquals(qualifiers[1].key, "flagDirs")) + { + flagDirs = StringEquals(qualifiers[1].value, "1"); + } + } + response = reprap.GetFilesResponse(dir, flagDirs); + } + else if (StringEquals(command, "fileinfo")) + { + if (reprap.GetPrintMonitor()->GetFileInfoResponse(StringEquals(key1, "name") ? value1 : nullptr, response)) + { + processingDeferredRequest = false; + } + else + { + processingDeferredRequest = true; + } + } + else if (StringEquals(command, "config")) + { + response = reprap.GetConfigResponse(); + } + else + { + platform->MessageF(HOST_MESSAGE, "KnockOut request: %s not recognised\n", command); + network->SendReply(session.ip, 400, "Unknown rr_ command"); + return false; + } + + if (response != nullptr) + { + network->SendReply(session.ip, 200 | rcJson, response); + } + else if (!processingDeferredRequest) + { + network->SendReply(session.ip, 500, "No buffer available"); + } + return processingDeferredRequest; +} + +void Webserver::SendGCodeReply(HttpSession& session) +{ + if (gcodeReply->IsEmpty()) + { + network->SendReply(session.ip, 200, ""); + } + else + { + clientsServed++; + if (clientsServed < numSessions) + { + gcodeReply->IncreaseReferences(1); + network->SendReply(session.ip, 200, gcodeReply->GetFirstItem()); + } + else + { + network->SendReply(session.ip, 200, gcodeReply->Pop()); + } + + if (reprap.Debug(moduleWebserver)) + { + platform->MessageF(HOST_MESSAGE, "Serving client %d of %d\n", clientsServed, numSessions); + } + } +} + +void Webserver::SendConfigFile(HttpSession& session) +{ + FileStore *configFile = platform->GetFileStore(platform->GetSysDir(), platform->GetConfigFile(), false); + if (configFile == nullptr) + { + network->SendReply(session.ip, 400, "File config.g not found"); + } + else + { + // Send an initial message containing the response code and the file size (needed for the Content-length field) + network->SendReply(session.ip, 200, configFile); // tell the network layer to send the file + } +} + +// Process a character from the client +// Rewritten as a state machine by dc42 to increase capability and speed, and reduce RAM requirement. +// On entry: +// There is space for at least 1 character in clientMessage. +// On return: +// If we return false: +// We want more characters. There is space for at least 1 character in clientMessage. +// If we return true: +// We have finished processing the message. No more characters may be read from this message. +// Whenever this calls ProcessMessage: +// The first line has been split up into words. Variables numCommandWords and commandWords give the number of words we found +// and the pointers to each word. The second word is treated specially. It is assumed to be a filename followed by an optional +// qualifier comprising key/value pairs. Both may include %xx escapes, and the qualifier may include + to mean space. We store +// a pointer to the filename without qualifier in commandWords[1]. We store the qualifier key/value pointers in array 'qualifiers' +// and the number of them in numQualKeys. +// The remaining lines have been parsed as header name/value pairs. Pointers to them are stored in array 'headers' and the number +// of them in numHeaders. +// If one of our arrays is about to overflow, or the message is not in a format we expect, then we call RejectMessage with an +// appropriate error code and string. +bool Webserver::CharFromClient(char c, const char* &error) +{ + switch(state) + { + case doingFilename: + switch(c) + { + case '\0': + case ' ': + case '\t': + case '\n': + case '\r': + clientMessage[clientPointer++] = 0; + numQualKeys = 0; + error = nullptr; + return true; + case '?': + clientMessage[clientPointer++] = 0; + numQualKeys = 0; + qualifiers[0].key = clientMessage + clientPointer; + state = doingQualifierKey; + break; + case '%': + state = doingFilenameEsc1; + break; + default: + clientMessage[clientPointer++] = c; + break; + } + break; + + case doingQualifierKey: + switch(c) + { + case '=': + clientMessage[clientPointer++] = 0; + qualifiers[numQualKeys].value = clientMessage + clientPointer; + ++numQualKeys; + state = doingQualifierValue; + break; + case '\n': // key with no value + case ' ': + case '\t': + case '\r': + case '%': // none of our keys needs escaping, so treat an escape within a key as an error + case '&': // key with no value + error = "bad qualifier key"; + return true; + default: + clientMessage[clientPointer++] = c; + break; + } + break; + + case doingQualifierValue: + switch(c) + { + case '\0': + case '\n': + case ' ': + case '\t': + case '\r': + clientMessage[clientPointer++] = 0; + qualifiers[numQualKeys].key = clientMessage + clientPointer; // so that we can read the whole value even if it contains a null + error = nullptr; + return true; + case '%': + state = doingQualifierValueEsc1; + break; + case '&': + // Another variable is coming + clientMessage[clientPointer++] = 0; + qualifiers[numQualKeys].key = clientMessage + clientPointer; // so that we can read the whole value even if it contains a null + if (numQualKeys < maxQualKeys) + { + state = doingQualifierKey; + } + else + { + error = "too many keys in qualifier"; + return true; + } + break; + case '+': + clientMessage[clientPointer++] = ' '; + break; + default: + clientMessage[clientPointer++] = c; + break; + } + break; + + case doingFilenameEsc1: + case doingQualifierValueEsc1: + if (c >= '0' && c <= '9') + { + decodeChar = (c - '0') << 4; + state = (HttpState)(state + 1); + } + else if (c >= 'A' && c <= 'F') + { + decodeChar = (c - ('A' - 10)) << 4; + state = (HttpState)(state + 1); + } + else + { + error = badEscapeResponse; + return true; + } + break; + + case doingFilenameEsc2: + case doingQualifierValueEsc2: + if (c >= '0' && c <= '9') + { + clientMessage[clientPointer++] = decodeChar | (c - '0'); + state = (HttpState)(state - 2); + } + else if (c >= 'A' && c <= 'F') + { + clientMessage[clientPointer++] = decodeChar | (c - ('A' - 10)); + state = (HttpState)(state - 2); + } + else + { + error = badEscapeResponse; + return true; + } + break; + } + + if (clientPointer == ARRAY_SIZE(clientMessage)) + { + error = overflowResponse; + return true; + } + return false; +} + +void Webserver::CheckSessions() +{ + // Check if any HTTP session can be purged + const float time = platform->Time(); + for (size_t i = 0; i < numSessions; ++i) + { + if ((time - sessions[i].lastQueryTime) > httpSessionTimeout) + { + DeleteSession(sessions[i].ip); + clientsServed++; // assume the disconnected client hasn't fetched the G-Code reply yet + } + } + + // If we cannot send the G-Code reply to anyone, we may free up some run-time space by dumping it + if (numSessions == 0 || clientsServed >= numSessions) + { + while (!gcodeReply->IsEmpty()) + { + OutputBuffer::ReleaseAll(gcodeReply->Pop()); + } + clientsServed = 0; + } +} + +// Process a received string of gcodes +void Webserver::LoadGcodeBuffer(const char* gc) +{ + char gcodeTempBuf[GCODE_LENGTH]; + uint16_t gtp = 0; + bool inComment = false; + for (;;) + { + char c = *gc++; + if (c == 0) + { + gcodeTempBuf[gtp] = 0; + ProcessGcode(gcodeTempBuf); + return; + } + + if (c == '\n') + { + gcodeTempBuf[gtp] = 0; + ProcessGcode(gcodeTempBuf); + gtp = 0; + inComment = false; + } + else + { + if (c == ';') + { + inComment = true; + } + + if (gtp == ARRAY_UPB(gcodeTempBuf)) + { + // gcode is too long, we haven't room for another character and a null + if (c != ' ' && !inComment) + { + platform->Message(HOST_MESSAGE, "Error: GCode local buffer overflow in HTTP webserver.\n"); + return; + } + // else we're either in a comment or the current character is a space. + // If we're in a comment, we'll silently truncate it. + // If the current character is a space, we'll wait until we see a non-comment character before reporting an error, + // in case the next character is end-of-line or the start of a comment. + } + else + { + gcodeTempBuf[gtp++] = c; + } + } + } +} + +// Process a null-terminated gcode +// We intercept one M Codes so we can deal with emergencies. That +// way things don't get out of sync, and - as a file name can contain +// a valid G code (!) - confusion is avoided. +void Webserver::ProcessGcode(const char* gc) +{ + if (StringStartsWith(gc, "M112") && !isdigit(gc[4])) // emergency stop + { + reprap.EmergencyStop(); + gcodeReadIndex = gcodeWriteIndex; // clear the buffer + reprap.GetGCodes()->Reset(); + } + else + { + StoreGcodeData(gc, strlen(gc) + 1); + } +} + +// Process a received string of gcodes +void Webserver::StoreGcodeData(const char* data, uint16_t len) +{ + if (len > GetGCodeBufferSpace()) + { + platform->Message(HOST_MESSAGE, "Error: GCode buffer overflow in HTTP Webserver!\n"); + } + else + { + uint16_t remaining = gcodeBufferLength - gcodeWriteIndex; + if (len <= remaining) + { + memcpy(gcodeBuffer + gcodeWriteIndex, data, len); + } + else + { + memcpy(gcodeBuffer + gcodeWriteIndex, data, remaining); + memcpy(gcodeBuffer, data + remaining, len - remaining); + } + gcodeWriteIndex = (gcodeWriteIndex + len) % gcodeBufferLength; + } +} + +// End diff --git a/src/DuetNG/Webserver.h b/src/DuetNG/Webserver.h new file mode 100644 index 00000000..ffef6d6f --- /dev/null +++ b/src/DuetNG/Webserver.h @@ -0,0 +1,171 @@ +/**************************************************************************************************** + +RepRapFirmware - Webserver + +This class serves a single-page web applications to the attached network. This page forms the user's +interface with the RepRap machine. This software interprests returned values from the page and uses it +to Generate G Codes, which it sends to the RepRap. It also collects values from the RepRap like +temperature and uses those to construct the web page. + +The page itself - reprap.htm - uses Knockout.js and Jquery.js. See: + +http://knockoutjs.com/ + +http://jquery.com/ + +----------------------------------------------------------------------------------------------------- + +Version 0.2 + +10 May 2013 + +Adrian Bowyer +RepRap Professional Ltd +http://reprappro.com + +Licence: GPL + +****************************************************************************************************/ + +#ifndef WEBSERVER_H +#define WEBSERVER_H + + +// List of protocols that can execute G-Codes +enum class WebSource +{ + HTTP, + Telnet +}; + +const uint16_t gcodeBufferLength = 512; // size of our gcode ring buffer, preferably a power of 2 +const uint16_t webMessageLength = 2000; // maximum length of the web message we accept after decoding +const size_t maxQualKeys = 5; // max number of key/value pairs in the qualifier +const size_t maxHttpSessions = 8; // maximum number of simultaneous HTTP sessions +const float httpSessionTimeout = 20.0; // HTTP session timeout in seconds + +class Webserver +{ +public: + + friend class Platform; + + Webserver(Platform* p, Network *n); + void Init(); + void Spin(); + void Exit(); + void Diagnostics(MessageType mtype); + + bool GCodeAvailable(const WebSource source) const; + char ReadGCode(const WebSource source); + void HandleGCodeReply(const WebSource source, OutputBuffer *reply); + void HandleGCodeReply(const WebSource source, const char *reply); + uint32_t GetReplySeq() const { return seq; } + // Returns the available G-Code buffer space of the HTTP interpreter (may be dropped in a future version) + uint16_t GetGCodeBufferSpace(const WebSource source) const { return 0; } + +private: + static const uint32_t lastFragmentFlag = 0x80000000; + static const size_t uploadBufLength = 8192; + + enum UploadState + { + uploading = 0, + wrongFragment, + cantCreate, + cantWrite, + wrongLength, + cantClose, + notUploading, + uploadBusy + }; + + // HTTP sessions + struct HttpSession + { + uint32_t ip; + uint32_t nextFragment; + float lastQueryTime; + FileData fileBeingUploaded; + uint32_t postLength; + uint32_t bytesWritten; + UploadState uploadState; + size_t bufNumber; + size_t bufPointer; + bool isAuthenticated; + }; + + void ResetState(); + void CheckSessions(); + + bool CharFromClient(char c, const char* &error); + bool ProcessFirstFragment(HttpSession& session, const char* command, bool isOnlyFragment); + void ProcessUploadFragment(HttpSession& session, const char* request, size_t length, uint32_t fragment); + void StartUpload(HttpSession& session, const char* fileName, uint32_t fileLength); + void FinishUpload(HttpSession& session); + void CancelUpload(HttpSession& session); + bool GetJsonResponse(uint32_t remoteIp, const char* request, OutputBuffer *&response, const char* key, const char* value, size_t valueLength, bool& keepOpen); + void SendConfigFile(HttpSession& session); + + // Deal with incoming G-Codes + char gcodeBuffer[gcodeBufferLength]; + uint16_t gcodeReadIndex, gcodeWriteIndex; // head and tail indices into gcodeBuffer + uint32_t seq; // sequence number for G-Code replies + + void LoadGcodeBuffer(const char* gc); + void ProcessGcode(const char* gc); + void StoreGcodeData(const char* data, uint16_t len); + void SendGCodeReply(HttpSession& session); + uint16_t GetGCodeBufferSpace() const; + + HttpSession *StartSession(uint32_t ip); // start a new session for this requester + HttpSession *FindSession(uint32_t ip); // find an existing session for this requester + void DeleteSession(uint32_t ip); // delete a session + + // Response from GCodes class + OutputStack *gcodeReply; + + enum HttpState + { + doingFilename, // receiving the filename (second word in the command line) + doingFilenameEsc1, // received '%' in the filename (e.g. we are being asked for a filename with spaces in it) + doingFilenameEsc2, // received '%' and one hex digit in the filename + doingQualifierKey, // receiving a key name in the HTTP request + doingQualifierValue, // receiving a key value in the HTTP request + doingQualifierValueEsc1, // received '%' in the qualifier + doingQualifierValueEsc2, // received '%' and one hex digit in the qualifier + }; + HttpState state; + + struct KeyValueIndices + { + const char* key; + const char* value; + }; + + uint32_t uploadBuffers[2][uploadBufLength/4]; // two 8K buffers for uploading files + uint32_t uploadIp; // session that owns the upload buffer + Platform *platform; + Network *network; + float longWait; + bool webserverActive; + + char clientMessage[webMessageLength + 3]; // holds the command, qualifier, and headers + size_t clientPointer; // current index into clientMessage + char decodeChar; + + bool processingDeferredRequest; // it's no good idea to parse 128kB of text in one go... + + KeyValueIndices qualifiers[maxQualKeys + 1]; // offsets into clientQualifier of the key/value pairs, the +1 is needed so that values can contain nulls + size_t numQualKeys; // number of qualifier keys we have found, <= maxQualKeys + + HttpSession sessions[maxHttpSessions]; + size_t numSessions, clientsServed; +}; + +inline uint16_t Webserver::GetGCodeBufferSpace() const +{ + return (gcodeReadIndex - gcodeWriteIndex - 1u) % gcodeBufferLength; +} + +#endif diff --git a/src/DuetNG/WifiFirmwareUploader.cpp b/src/DuetNG/WifiFirmwareUploader.cpp new file mode 100644 index 00000000..8bc740d8 --- /dev/null +++ b/src/DuetNG/WifiFirmwareUploader.cpp @@ -0,0 +1,743 @@ +/* + * EspFirmwareUpload.cpp + * + * Created on: 15 Apr 2016 + * Author: David + */ + +#include "WifiFirmwareUploader.h" +#include "Core.h" +#include "RepRapFirmware.h" + +// ESP8266 command codes +const uint8_t ESP_FLASH_BEGIN = 0x02; +const uint8_t ESP_FLASH_DATA = 0x03; +const uint8_t ESP_FLASH_END = 0x04; +const uint8_t ESP_MEM_BEGIN = 0x05; +const uint8_t ESP_MEM_END = 0x06; +const uint8_t ESP_MEM_DATA = 0x07; +const uint8_t ESP_SYNC = 0x08; +const uint8_t ESP_WRITE_REG = 0x09; +const uint8_t ESP_READ_REG = 0x0a; + +// MAC address storage locations +const uint32_t ESP_OTP_MAC0 = 0x3ff00050; +const uint32_t ESP_OTP_MAC1 = 0x3ff00054; +const uint32_t ESP_OTP_MAC2 = 0x3ff00058; +const uint32_t ESP_OTP_MAC3 = 0x3ff0005c; + +const size_t EspFlashBlockSize = 0x0400; // 1K byte blocks + +const uint8_t ESP_IMAGE_MAGIC = 0xe9; +const uint8_t ESP_CHECKSUM_MAGIC = 0xef; + +const uint32_t ESP_ERASE_CHIP_ADDR = 0x40004984; // &SPIEraseChip +const uint32_t ESP_SEND_PACKET_ADDR = 0x40003c80; // &send_packet +const uint32_t ESP_SPI_READ_ADDR = 0x40004b1c; // &SPIRead +const uint32_t ESP_UNKNOWN_ADDR = 0x40001121; // not used +const uint32_t ESP_USER_DATA_RAM_ADDR = 0x3ffe8000; // &user data ram +const uint32_t ESP_IRAM_ADDR = 0x40100000; // instruction RAM +const uint32_t ESP_FLASH_ADDR = 0x40200000; // address of start of Flash +const uint32_t ESP_FLASH_READ_STUB_BEGIN = IRAM_ADDR + 0x18; + +// Messages corresponding to result codes, should make sense when followed by " error" +const char *resultMessages[] = +{ + "no", + "timeout", + "comm write", + "connect", + "bad reply", + "file read", + "empty file", + "response header", + "slip frame", + "slip state", + "slip data" +}; + +// A note on baud rates. +// The ESP8266 supports 921600, 460800, 230400, 115200, 74880 and some lower baud rates. +// 921600b is not reliable because even though it sometimes succeeds in connecting, we get a bad response during uploading after a few blocks. +// Probably our UART ISR cannot receive bytes fast enough, perhaps because of the latency of the system tick ISR. +// 460800b doesn't always manage to connect, but if it does then uploading appears to be reliable. +// 230400b always manages to connect. +static const uint32_t uploadBaudRates[] = { 460800, 230400, 115200, 74880 }; + +WifiFirmwareUploader::WifiFirmwareUploader(UARTClass& port) + : uploadPort(port), uploadFile(nullptr), state(UploadState::idle) +{ +} + +bool WifiFirmwareUploader::IsReady() const +{ + return state == UploadState::idle; +} + +void WifiFirmwareUploader::MessageF(const char *fmt, ...) +{ + va_list vargs; + va_start(vargs, fmt); + reprap.GetPlatform()->MessageF(FIRMWARE_UPDATE_MESSAGE, fmt, vargs); + va_end(vargs); +} + +void WifiFirmwareUploader::flushInput() +{ + while (uploadPort.available() != 0) + { + (void)uploadPort.read(); + } +} + +// Extract 1-4 bytes of a value in little-endian order from a buffer beginning at a specified offset +uint32_t WifiFirmwareUploader::getData(unsigned byteCnt, const uint8_t *buf, int ofst) +{ + uint32_t val = 0; + + if (buf && byteCnt) + { + unsigned int shiftCnt = 0; + if (byteCnt > 4) + byteCnt = 4; + do + { + val |= (uint32_t)buf[ofst++] << shiftCnt; + shiftCnt += 8; + } while (--byteCnt); + } + return(val); +} + +// Put 1-4 bytes of a value in little-endian order into a buffer beginning at a specified offset. +void WifiFirmwareUploader::putData(uint32_t val, unsigned byteCnt, uint8_t *buf, int ofst) +{ + if (buf && byteCnt) + { + if (byteCnt > 4) + { + byteCnt = 4; + } + do + { + buf[ofst++] = (uint8_t)(val & 0xff); + val >>= 8; + } while (--byteCnt); + } +} + +// Read a byte optionally performing SLIP decoding. The return values are: +// +// 2 - an escaped byte was read successfully +// 1 - a non-escaped byte was read successfully +// 0 - no data was available +// -1 - the value 0xc0 was encountered (shouldn't happen) +// -2 - a SLIP escape byte was found but the following byte wasn't available +// -3 - a SLIP escape byte was followed by an invalid byte +int WifiFirmwareUploader::ReadByte(uint8_t& data, bool slipDecode) +{ + if (uploadPort.available() == 0) + { + return(0); + } + + // at least one byte is available + data = uploadPort.read(); + if (!slipDecode) + { + return(1); + } + + if (data == 0xc0) + { + // this shouldn't happen + return(-1); + } + + // if not the SLIP escape, we're done + if (data != 0xdb) + { + return(1); + } + + // SLIP escape, check availability of subsequent byte + if (uploadPort.available() == 0) + { + return(-2); + } + + // process the escaped byte + data = uploadPort.read(); + if (data == 0xdc) + { + data = 0xc0; + return(2); + } + + if (data == 0xdd) + { + data = 0xdb; + return(2); + } + // invalid + return(-3); +} + +// When we write a sync packet, there must be no gaps between most of the characters. +// So use this function, which does a block write to the UART buffer in the latest CoreNG. +void WifiFirmwareUploader::writePacketRaw(const uint8_t *buf, size_t len) +{ + uploadPort.write(buf, len); +} + +// Write a byte to the serial port optionally SLIP encoding. Return the number of bytes actually written. +inline void WifiFirmwareUploader::WriteByteRaw(uint8_t b) +{ + uploadPort.write(b); +} + +// Write a byte to the serial port optionally SLIP encoding. Return the number of bytes actually written. +inline void WifiFirmwareUploader::WriteByteSlip(uint8_t b) +{ + if (b == 0xC0) + { + WriteByteRaw(0xDB); + WriteByteRaw(0xDC); + } + else if (b == 0xDB) + { + WriteByteRaw(0xDB); + WriteByteRaw(0xDD); + } + else + { + uploadPort.write(b); + } +} + +// Wait for a data packet to be returned. If the body of the packet is +// non-zero length, return an allocated buffer indirectly containing the +// data and return the data length. Note that if the pointer for returning +// the data buffer is NULL, the response is expected to be two bytes of zero. +// +// If an error occurs, return a negative value. Otherwise, return the number +// of bytes in the response (or zero if the response was not the standard "two bytes of zero"). +WifiFirmwareUploader::EspUploadResult WifiFirmwareUploader::readPacket(uint8_t op, uint32_t *valp, size_t& bodyLen, uint32_t msTimeout) +{ + enum class PacketState + { + begin = 0, + header, + body, + end, + done + }; + + const size_t headerLength = 8; + + uint32_t startTime = millis(); + uint8_t hdr[headerLength]; + uint16_t hdrIdx = 0; + bodyLen = 0; + uint16_t bodyIdx = 0; + uint8_t respBuf[2]; + + // wait for the response + uint16_t needBytes = 1; + PacketState state = PacketState::begin; + while (state != PacketState::done) + { + uint8_t c; + EspUploadResult stat; + + if (millis() - startTime > msTimeout) + { + return(EspUploadResult::timeout); + } + + if (uploadPort.available() < needBytes) + { + // insufficient data available + // preferably, return to Spin() here + continue; + } + + // sufficient bytes have been received for the current state, process them + switch(state) + { + case PacketState::begin: // expecting frame start + case PacketState::end: // expecting frame end + c = uploadPort.read(); + if (c != 0xc0) + { + return EspUploadResult::slipFrame; + } + if (state == PacketState::begin) + { + state = PacketState::header; + needBytes = 2; + } + else + { + state = PacketState::done; + } + break; + + case PacketState::header: // reading an 8-byte header + case PacketState::body: // reading the response body + { + int rslt; + // retrieve a byte with SLIP decoding + rslt = ReadByte(c, true); + if (rslt != 1 && rslt != 2) + { + // some error occurred + stat = (rslt == 0 || rslt == -2) ? EspUploadResult::slipData : EspUploadResult::slipFrame; + return stat; + } + else if (state == PacketState::header) + { + //store the header byte + hdr[hdrIdx++] = c; + if (hdrIdx >= headerLength) + { + // get the body length, prepare a buffer for it + bodyLen = (uint16_t)getData(2, hdr, 2); + + // extract the value, if requested + if (valp != nullptr) + { + *valp = getData(4, hdr, 4); + } + + if (bodyLen != 0) + { + state = PacketState::body; + } + else + { + needBytes = 1; + state = PacketState::end; + } + } + } + else + { + // Store the response body byte, check for completion + if (bodyIdx < ARRAY_SIZE(respBuf)) + { + respBuf[bodyIdx] = c; + } + ++bodyIdx; + if (bodyIdx >= bodyLen) + { + needBytes = 1; + state = PacketState::end; + } + } + } + break; + + default: // this shouldn't happen + return EspUploadResult::slipState; + } + } + + // Extract elements from the header + const uint8_t resp = (uint8_t)getData(1, hdr, 0); + const uint8_t opRet = (uint8_t)getData(1, hdr, 1); + // Sync packets often provoke a response with a zero opcode instead of ESP_SYNC + if (resp != 0x01 || opRet != op) + { +//debugPrintf("resp %02x %02x\n", resp, opRet); + return EspUploadResult::respHeader; + } + + return EspUploadResult::success; +} + +// Send a block of data performing SLIP encoding of the content. +inline void WifiFirmwareUploader::writePacket(const uint8_t *data, size_t len) +{ + while (len != 0) + { + WriteByteSlip(*data++); + --len; + } +} + +// Send a packet to the serial port while performing SLIP framing. The packet data comprises a header and an optional data block. +// A SLIP packet begins and ends with 0xc0. The data encapsulated has the bytes +// 0xc0 and 0xdb replaced by the two-byte sequences {0xdb, 0xdc} and {0xdb, 0xdd} respectively. +void WifiFirmwareUploader::writePacket(const uint8_t *hdr, size_t hdrLen, const uint8_t *data, size_t dataLen) +{ + WriteByteRaw(0xc0); // send the packet start character + writePacket(hdr, hdrLen); // send the header + writePacket(data, dataLen); // send the data block + WriteByteRaw(0xc0); // send the packet end character +} + +// Send a packet to the serial port while performing SLIP framing. The packet data comprises a header and an optional data block. +// This is like writePacket except that it does a fast block write for both the header and the main data with no SLIP encoding. Used to send sync commands. +void WifiFirmwareUploader::writePacketRaw(const uint8_t *hdr, size_t hdrLen, const uint8_t *data, size_t dataLen) +{ + WriteByteRaw(0xc0); // send the packet start character + writePacketRaw(hdr, hdrLen); // send the header + writePacketRaw(data, dataLen); // send the data block in raw mode + WriteByteRaw(0xc0); // send the packet end character +} + +// Send a command to the attached device together with the supplied data, if any. +// The data is supplied via a list of one or more segments. +void WifiFirmwareUploader::sendCommand(uint8_t op, uint32_t checkVal, const uint8_t *data, size_t dataLen) +{ + // populate the header + uint8_t hdr[8]; + putData(0, 1, hdr, 0); + putData(op, 1, hdr, 1); + putData(dataLen, 2, hdr, 2); + putData(checkVal, 4, hdr, 4); + + // send the packet + flushInput(); + if (op == ESP_SYNC) + { + writePacketRaw(hdr, sizeof(hdr), data, dataLen); + } + else + { + writePacket(hdr, sizeof(hdr), data, dataLen); + } +} + +// Send a command to the attached device together with the supplied data, if any, and get the response +WifiFirmwareUploader::EspUploadResult WifiFirmwareUploader::doCommand(uint8_t op, const uint8_t *data, size_t dataLen, uint32_t checkVal, uint32_t *valp, uint32_t msTimeout) +{ + sendCommand(op, checkVal, data, dataLen); + size_t bodyLen; + EspUploadResult stat = readPacket(op, valp, bodyLen, msTimeout); + if (stat == EspUploadResult::success && bodyLen != 2) + { + stat = EspUploadResult::badReply; + } + + return stat; +} + +// Send a synchronising packet to the serial port in an attempt to induce +// the ESP8266 to auto-baud lock on the baud rate. +WifiFirmwareUploader::EspUploadResult WifiFirmwareUploader::Sync(uint16_t timeout) +{ + uint8_t buf[36]; + + // compose the data for the sync attempt + memset(buf, 0x55, sizeof(buf)); + buf[0] = 0x07; + buf[1] = 0x07; + buf[2] = 0x12; + buf[3] = 0x20; + + EspUploadResult stat = doCommand(ESP_SYNC, buf, sizeof(buf), 0, nullptr, timeout); + + // If we got a response other than sync, discard it and wait for a sync response. This happens at higher baud rates. + for (int i = 0; i < 10 && stat == EspUploadResult::respHeader; ++i) + { + size_t bodyLen; + stat = readPacket(ESP_SYNC, nullptr, bodyLen, timeout); + } + + if (stat == EspUploadResult::success) + { + // Read and discard additional replies + for (;;) + { + size_t bodyLen; + EspUploadResult rc = readPacket(ESP_SYNC, nullptr, bodyLen, defaultTimeout); + if (rc != EspUploadResult::success || bodyLen != 2) + { + break; + } + } + } +//DEBUG +// else debugPrintf("stat=%d\n", (int)stat); + return stat; +} + +// Send a command to the device to begin the Flash process. +WifiFirmwareUploader::EspUploadResult WifiFirmwareUploader::flashBegin(uint32_t addr, uint32_t size) +{ + // determine the number of blocks represented by the size + uint32_t blkCnt; + blkCnt = (size + EspFlashBlockSize - 1) / EspFlashBlockSize; + + // ensure that the address is on a block boundary + addr &= ~(EspFlashBlockSize - 1); + + // begin the Flash process + uint8_t buf[16]; + putData(size, 4, buf, 0); + putData(blkCnt, 4, buf, 4); + putData(EspFlashBlockSize, 4, buf, 8); + putData(addr, 4, buf, 12); + + uint32_t timeout = (size != 0) ? eraseTimeout : defaultTimeout; + return doCommand(ESP_FLASH_BEGIN, buf, sizeof(buf), 0, nullptr, timeout); +} + +// Send a command to the device to terminate the Flash process +WifiFirmwareUploader::EspUploadResult WifiFirmwareUploader::flashFinish(bool reboot) +{ + uint8_t buf[4]; + putData(reboot ? 0 : 1, 4, buf, 0); + return doCommand(ESP_FLASH_END, buf, sizeof(buf), 0, nullptr, defaultTimeout); +} + +// Compute the checksum of a block of data +uint16_t WifiFirmwareUploader::checksum(const uint8_t *data, uint16_t dataLen, uint16_t cksum) +{ + if (data != NULL) + { + while (dataLen--) + { + cksum ^= (uint16_t)*data++; + } + } + return(cksum); +} + +WifiFirmwareUploader::EspUploadResult WifiFirmwareUploader::flashWriteBlock(uint16_t flashParmVal, uint16_t flashParmMask) +{ + const uint32_t blkSize = EspFlashBlockSize; + + // Allocate a data buffer for the combined header and block data + const uint16_t hdrOfst = 0; + const uint16_t dataOfst = 16; + const uint16_t blkBufSize = dataOfst + blkSize; + uint8_t blkBuf[blkBufSize]; + + // Prepare the header for the block + putData(blkSize, 4, blkBuf, hdrOfst + 0); + putData(uploadBlockNumber, 4, blkBuf, hdrOfst + 4); + putData(0, 4, blkBuf, hdrOfst + 8); + putData(0, 4, blkBuf, hdrOfst + 12); + + // Get the data for the block + size_t cnt = uploadFile->Read((char *)blkBuf + dataOfst, blkSize); + if (cnt != EspFlashBlockSize) + { + if (uploadFile->Position() == fileSize) + { + // partial last block, fill the remainder + memset(blkBuf + dataOfst + cnt, 0xff, blkSize - cnt); + } + else + { + return EspUploadResult::fileRead; + } + } + + // Patch the flash parameters into the first block if it is loaded at address 0 + if (uploadBlockNumber == 0 && uploadAddress == 0 && blkBuf[dataOfst] == ESP_IMAGE_MAGIC && flashParmMask != 0) + { + // update the Flash parameters + uint32_t flashParm = getData(2, blkBuf + dataOfst + 2, 0) & ~(uint32_t)flashParmMask; + putData(flashParm | flashParmVal, 2, blkBuf + dataOfst + 2, 0); + } + + // Calculate the block checksum + uint16_t cksum = checksum(blkBuf + dataOfst, blkSize, ESP_CHECKSUM_MAGIC); + EspUploadResult stat; + for (int i = 0; i < 3; i++) + { + if ((stat = doCommand(ESP_FLASH_DATA, blkBuf, blkBufSize, cksum, nullptr, blockWriteTimeout)) == EspUploadResult::success) + { + break; + } + } + + return stat; +} + +void WifiFirmwareUploader::Spin() +{ + switch (state) + { + case UploadState::resetting: + if (connectAttemptNumber == ARRAY_SIZE(uploadBaudRates) * retriesPerBaudRate) + { + // Time to give up + Network::ResetWiFi(); + uploadResult = EspUploadResult::connect; + state = UploadState::done; + } + else + { + // Reset the serial port at the new baud rate. Also reset the ESP8266. + const uint32_t baud = uploadBaudRates[connectAttemptNumber/retriesPerBaudRate]; + if (connectAttemptNumber % retriesPerBaudRate == 0) + { + // First attempt at this baud rate + MessageF("Trying to connect at %u baud: ", baud); + } + uploadPort.begin(baud); + uploadPort.setInterruptPriority(1); // we are going to move data at seriously high speeds + Network::ResetWiFiForUpload(); + lastAttemptTime = lastResetTime = millis(); + state = UploadState::connecting; + } + break; + + case UploadState::connecting: + if (millis() - lastAttemptTime >= connectAttemptInterval && millis() - lastResetTime >= resetDelay) + { + // Attempt to establish a connection to the ESP8266. + EspUploadResult res = Sync(syncTimeout); + lastAttemptTime = millis(); + if (res == EspUploadResult::success) + { + // Successful connection +// MessageF(" success on attempt %d\n", (connectAttemptNumber % retriesPerBaudRate) + 1); + MessageF(" success\n"); + state = UploadState::erasing; + } + else + { + // This attempt failed + ++connectAttemptNumber; + if (connectAttemptNumber % retriesPerReset == 0) + { + if (connectAttemptNumber % retriesPerBaudRate == 0) + { + MessageF(" failed\n"); + } + state = UploadState::resetting; // try a reset and a lower baud rate + } + } + } + break; + + case UploadState::erasing: + if (millis() - lastAttemptTime >= blockWriteInterval) + { + const uint32_t sectorsPerBlock = 16; + const uint32_t sectorSize = 4096; + const uint32_t numSectors = (fileSize + sectorSize - 1)/sectorSize; + const uint32_t startSector = uploadAddress/sectorSize; + uint32_t headSectors = sectorsPerBlock - (startSector % sectorsPerBlock); + + if (numSectors < headSectors) + { + headSectors = numSectors; + } + const uint32_t eraseSize = (numSectors < 2 * headSectors) + ? (numSectors + 1) / 2 * sectorSize + : (numSectors - headSectors) * sectorSize; + + MessageF("Erasing %u bytes...\n", fileSize); + uploadResult = flashBegin(uploadAddress, eraseSize); + if (uploadResult == EspUploadResult::success) + { + MessageF("Uploading file...\n"); + uploadBlockNumber = 0; + uploadNextPercentToReport = percentToReportIncrement; + lastAttemptTime = millis(); + state = UploadState::uploading; + } + else + { + MessageF("Erase failed\n"); + state = UploadState::done; + } + } + break; + + case UploadState::uploading: + // The ESP needs several milliseconds to recover from one packet before it will accept another + if (millis() - lastAttemptTime >= blockWriteInterval) + { + const uint32_t blkCnt = (fileSize + EspFlashBlockSize - 1) / EspFlashBlockSize; + if (uploadBlockNumber < blkCnt) + { + uploadResult = flashWriteBlock(0, 0); + lastAttemptTime = millis(); + if (uploadResult != EspUploadResult::success) + { + MessageF("Flash block upload failed\n"); + state = UploadState::done; + } + const unsigned int percentComplete = (100 * uploadBlockNumber)/blkCnt; + ++uploadBlockNumber; + if (percentComplete >= uploadNextPercentToReport) + { + MessageF("%u%% complete\n", percentComplete); + uploadNextPercentToReport += percentToReportIncrement; + } + } + else + { + state = UploadState::done; + } + } + break; + + case UploadState::done: + uploadFile->Close(); + uploadPort.end(); // disable the port, it has a high interrupt priority + if (uploadResult == EspUploadResult::success) + { + reprap.GetPlatform()->Message(FIRMWARE_UPDATE_MESSAGE, "Upload successful\n"); + if (restartOnCompletion) + { + reprap.GetNetwork()->Start(); + } + else + { + reprap.GetNetwork()->ResetWiFi(); + } + } + else + { + reprap.GetPlatform()->MessageF(FIRMWARE_UPDATE_MESSAGE, "Error: Installation failed due to %s error\n", resultMessages[(size_t)uploadResult]); + // Not safe to restart the network + reprap.GetNetwork()->ResetWiFi(); + } + state = UploadState::idle; + break; + + default: + break; + } +} + +// Try to upload the given file at the given address +void WifiFirmwareUploader::SendUpdateFile(const char *file, const char *dir, uint32_t address) +{ + Platform *platform = reprap.GetPlatform(); + uploadFile = platform->GetFileStore(dir, file, false); + if (uploadFile == nullptr) + { + platform->MessageF(FIRMWARE_UPDATE_MESSAGE, "Failed to open file %s\n", file); + return; + } + + fileSize = uploadFile->Length(); + if (fileSize == 0) + { + uploadFile->Close(); + platform->MessageF(FIRMWARE_UPDATE_MESSAGE, "Upload file is empty %s\n", file); + return; + } + + // Stop the network + Network *network = reprap.GetNetwork(); + restartOnCompletion = network->IsEnabled(); + network->Stop(); + + // Set up the state so that subsequent calls to Spin() will attempt the upload + uploadAddress = address; + connectAttemptNumber = 0; + state = UploadState::resetting; +} + +// End diff --git a/src/DuetNG/WifiFirmwareUploader.h b/src/DuetNG/WifiFirmwareUploader.h new file mode 100644 index 00000000..33f387f3 --- /dev/null +++ b/src/DuetNG/WifiFirmwareUploader.h @@ -0,0 +1,96 @@ +/* + * EspFirmwareUpload.h + * + * Created on: 15 Apr 2016 + * Author: David + */ + +#ifndef SRC_DUETNG_WIFIFIRMWAREUPLOADER_H_ +#define SRC_DUETNG_WIFIFIRMWAREUPLOADER_H_ + +#include "Core.h" +#include "FileStore.h" + +class WifiFirmwareUploader +{ +public: + WifiFirmwareUploader(UARTClass& port); + bool IsReady() const; + void SendUpdateFile(const char *file, const char *dir, uint32_t address); + void Spin(); + + static const uint32_t FirmwareAddress = 0x00000000; + static const uint32_t WebFilesAddress = 0x00100000; + +private: + static const uint32_t defaultTimeout = 500; // default timeout in milliseconds + static const uint32_t syncTimeout = 1000; + static const unsigned int retriesPerBaudRate = 9; + static const unsigned int retriesPerReset = 3; + static const uint32_t connectAttemptInterval = 50; + static const uint32_t resetDelay = 500; + static const uint32_t blockWriteInterval = 15; // 15ms is long enough, 10ms is mostly too short + static const uint32_t blockWriteTimeout = 200; + static const uint32_t eraseTimeout = 15000; // increased from 12 to 15 seconds because Roland's board was timing out + static const unsigned int percentToReportIncrement = 5; // how often we report % complete + + // Return codes - this list must be kept in step with the corresponding messages + enum class EspUploadResult + { + success = 0, + timeout, + connect, + badReply, + fileRead, + emptyFile, + respHeader, + slipFrame, + slipState, + slipData, + }; + + enum class UploadState + { + idle, + resetting, + connecting, + erasing, + uploading, + done + }; + + void MessageF(const char *fmt, ...); + uint32_t getData(unsigned byteCnt, const uint8_t *buf, int ofst); + void putData(uint32_t val, unsigned byteCnt, uint8_t *buf, int ofst); + int ReadByte(uint8_t& data, bool slipDecode); + void WriteByteRaw(uint8_t b); + void WriteByteSlip(uint8_t b); + void flushInput(); + EspUploadResult readPacket(uint8_t op, uint32_t *valp, size_t& bodyLen, uint32_t msTimeout); + void writePacket(const uint8_t *data, size_t len); + void writePacketRaw(const uint8_t *buf, size_t len); + void writePacket(const uint8_t *hdr, size_t hdrLen, const uint8_t *data, size_t dataLen); + void writePacketRaw(const uint8_t *hdr, size_t hdrLen, const uint8_t *data, size_t dataLen); + void sendCommand(uint8_t op, uint32_t checkVal, const uint8_t *data, size_t dataLen); + EspUploadResult doCommand(uint8_t op, const uint8_t *data, size_t dataLen, uint32_t checkVal, uint32_t *valp, uint32_t msTimeout); + EspUploadResult Sync(uint16_t timeout); + EspUploadResult flashBegin(uint32_t addr, uint32_t size); + EspUploadResult flashFinish(bool reboot); + static uint16_t checksum(const uint8_t *data, uint16_t dataLen, uint16_t cksum); + EspUploadResult flashWriteBlock(uint16_t flashParmVal, uint16_t flashParmMask); + + UARTClass& uploadPort; + FileStore *uploadFile; + FilePosition fileSize; + uint32_t uploadAddress; + uint32_t uploadBlockNumber; + unsigned int uploadNextPercentToReport; + unsigned int connectAttemptNumber; + uint32_t lastAttemptTime; + uint32_t lastResetTime; + UploadState state; + EspUploadResult uploadResult; + bool restartOnCompletion; +}; + +#endif /* SRC_DUETNG_WIFIFIRMWAREUPLOADER_H_ */ diff --git a/src/ExternalDrivers.cpp b/src/ExternalDrivers.cpp index 04634fa0..0fb01a0d 100644 --- a/src/ExternalDrivers.cpp +++ b/src/ExternalDrivers.cpp @@ -5,7 +5,6 @@ * Author: David */ -//#include "Platform.h" // for typedefs uint8_t etc. #include "RepRapFirmware.h" #ifdef EXTERNAL_DRIVERS @@ -41,29 +40,45 @@ const size_t NumExternalDrivers = DRIVES - FIRST_EXTERNAL_DRIVE; // 5V_USB 5 +3.3V 3 (+3.3V) #ifdef DUET_NG -# if 1 + +const Pin DriverSelectPins[NumExternalDrivers] = {78, 41, 42, 49, 57, 87, 88, 89, 90}; + +# ifdef PROTOTYPE_1 + // Pin assignments for the first prototype, using USART0 SPI const Pin DriversMosiPin = 27; // PB1 const Pin DriversMisoPin = 26; // PB0 const Pin DriversSclkPin = 30; // PB13 # define USART_EXT_DRV USART0 # define ID_USART_EXT_DRV ID_USART0 + # else + +# include "sam/drivers/tc/tc.h" + // Pin assignments for the second prototype, using USART1 SPI +const Pin DriversClockPin = 15; // PB15/TIOA1 const Pin DriversMosiPin = 22; // PA13 const Pin DriversMisoPin = 21; // PA22 const Pin DriversSclkPin = 23; // PA23 # define USART_EXT_DRV USART1 # define ID_USART_EXT_DRV ID_USART1 +# define TMC_CLOCK_TC TC0 +# define TMC_CLOCK_CHAN 1 +# define TMC_CLOCK_ID ID_TC1 // this is channel 1 on TC0 # endif -const Pin DriverSelectPins[NumExternalDrivers] = {87, 88, 89, 90}; + #else + +// Duet 0.6 or 0.8.5 + +const Pin DriverSelectPins[NumExternalDrivers] = {37, X8, 50, 47 /*, X13*/ }; const Pin DriversMosiPin = 16; // PA13 const Pin DriversMisoPin = 17; // PA12 const Pin DriversSclkPin = 54; // PA16 -const Pin DriverSelectPins[NumExternalDrivers] = {37, X8, 50, 47 /*, X13*/ }; # define USART_EXT_DRV USART1 # define ID_USART_EXT_DRV ID_USART1 + #endif const uint32_t DriversSpiClockFrequency = 1000000; // 1MHz SPI clock for now @@ -171,6 +186,7 @@ const uint32_t defaultSgscConfReg = // Driver configuration register const uint32_t defaultDrvConfReg = TMC_REG_DRVCONF + | TMC_DRVCONF_VSENSE // use high sensitivity range | 0; // Driver control register @@ -275,25 +291,40 @@ void TmcDriverState::SetMicrostepping(uint32_t shift, bool interpolate) void TmcDriverState::SetCurrent(float current) { - // I am assuming that the current sense resistor is 0.1 ohms as on the evaluation board. - // This gives us a range of 95mA to 3.05A in 95mA steps when VSENSE is high (but max allowed RMS current is 2A), - // or 52mA to 1.65A in 52mA steps when VSENSE is low. +#if defined(DUET_NG) && !defined(PROTOTYPE_1) + + // The current sense resistor is 0.051 ohms. + // This gives us a range of 101mA to 3.236A in 101mA steps in the high sensitivity range (VSENSE = 1) + drvConfReg |= TMC_DRVCONF_VSENSE; // this should always be set, but send it again just in case + SpiSendWord(pin, drvConfReg); + + const uint32_t iCurrent = (current > 2000.0) ? 2000 : (current < 100) ? 100 : (uint32_t)current; + const uint32_t csBits = (uint32_t)((32 * iCurrent - 1600)/3236); // formula checked by simulation on a spreadsheet + sgcsConfReg &= ~TMC_SGCSCONF_CS_MASK; + sgcsConfReg |= TMC_SGCSCONF_CS(csBits); + SpiSendWord(pin, sgcsConfReg); + +#else + + // The current sense resistor is 0.1 ohms on the evaluation board. + // This gives us a range of 95mA to 3.05A in 95mA steps when VSENSE is low (but max allowed RMS current is 2A), + // or 52mA to 1.65A in 52mA steps when VSENSE is high. if (current > 1650.0) { - // Need VSENSE = 1, but set up the current first to avoid temporarily exceeding the 2A rating + // Need VSENSE = 0, but set up the current first to avoid temporarily exceeding the 2A rating const uint32_t iCurrent = (current > 2000.0) ? 2000 : (uint32_t)current; const uint32_t csBits = (uint32_t)((32 * iCurrent - 1500)/3050); // formula checked by simulation on a spreadsheet sgcsConfReg &= ~TMC_SGCSCONF_CS_MASK; sgcsConfReg |= TMC_SGCSCONF_CS(csBits); SpiSendWord(pin, sgcsConfReg); - drvConfReg |= TMC_DRVCONF_VSENSE; + drvConfReg &= ~TMC_DRVCONF_VSENSE; SpiSendWord(pin, drvConfReg); } else { - // Use VSENSE = 0 - drvConfReg &= ~TMC_DRVCONF_VSENSE; + // Use VSENSE = 1 + drvConfReg |= TMC_DRVCONF_VSENSE; SpiSendWord(pin, drvConfReg); const uint32_t iCurrent = (current < 50) ? 50 : (uint32_t)current; @@ -302,6 +333,8 @@ void TmcDriverState::SetCurrent(float current) sgcsConfReg |= TMC_SGCSCONF_CS(csBits); SpiSendWord(pin, sgcsConfReg); } + + #endif } void TmcDriverState::Enable(bool en) @@ -347,9 +380,24 @@ namespace ExternalDrivers pio_configure(pin1.pPort, PIO_PERIPH_A, pin1.ulPin, PIO_DEFAULT); #endif - // Enable the clock to UART1 + // Enable the clock to the USART pmc_enable_periph_clk(ID_USART_EXT_DRV); +#if defined(DUET_NG) && !defined(PROTOTYPE_1) + // Set up the 15MHz clock to the TMC drivers on TIOA1 + pmc_enable_periph_clk(TMC_CLOCK_ID); + ConfigurePin(GetPinDescription(DriversClockPin)); // set up TIOA1 to be an output + tc_init(TMC_CLOCK_TC, TMC_CLOCK_CHAN, + TC_CMR_TCCLKS_TIMER_CLOCK1 | // clock is MCLK/2 (fastest available) + TC_CMR_BURST_NONE | // clock is not gated + TC_CMR_WAVE | // Waveform mode + TC_CMR_WAVSEL_UP_RC | // Counter runs up and reset when equals to RC + TC_CMR_EEVT_XC0 | // Set external events from XC0 (this sets up TIOB as output) + TC_CMR_ACPC_TOGGLE); // toggle TIOA output + tc_write_rc(TMC_CLOCK_TC, TMC_CLOCK_CHAN, 2); // divisor = 2, gives us a 15MHz clock with a master clock of 120MHz. + tc_start(TMC_CLOCK_TC, TMC_CLOCK_CHAN); +#endif + // Set up the CS pins and set them all high // When this becomes the standard code, we must set up the STEP and DIR pins here too. for (size_t drive = 0; drive < NumExternalDrivers; ++drive) diff --git a/src/GCodeBuffer.cpp b/src/GCodeBuffer.cpp index 6ebedb31..c8480e27 100644 --- a/src/GCodeBuffer.cpp +++ b/src/GCodeBuffer.cpp @@ -184,7 +184,6 @@ bool GCodeBuffer::Seen(char c) } // Get a float after a G Code letter found by a call to Seen() - float GCodeBuffer::GetFValue() { if (readPointer < 0) @@ -199,7 +198,6 @@ float GCodeBuffer::GetFValue() } // Get a :-separated list of floats after a key letter - const void GCodeBuffer::GetFloatArray(float a[], size_t& returnedLength) { if(readPointer < 0) @@ -214,7 +212,7 @@ const void GCodeBuffer::GetFloatArray(float a[], size_t& returnedLength) bool inList = true; while(inList) { - if(length >= returnedLength) // Array limit has been set in here + if (length >= returnedLength) // array limit has been set in here { platform->MessageF(GENERIC_MESSAGE, "Error: GCodes: Attempt to read a GCode float array that is too long: %s\n", gcodeBuffer); readPointer = -1; @@ -223,12 +221,11 @@ const void GCodeBuffer::GetFloatArray(float a[], size_t& returnedLength) } a[length] = (float)strtod(&gcodeBuffer[readPointer + 1], 0); length++; - readPointer++; - while(gcodeBuffer[readPointer] && (gcodeBuffer[readPointer] != ' ') && (gcodeBuffer[readPointer] != LIST_SEPARATOR)) + do { readPointer++; - } - if(gcodeBuffer[readPointer] != LIST_SEPARATOR) + } while(gcodeBuffer[readPointer] && (gcodeBuffer[readPointer] != ' ') && (gcodeBuffer[readPointer] != LIST_SEPARATOR)); + if (gcodeBuffer[readPointer] != LIST_SEPARATOR) { inList = false; } @@ -252,10 +249,9 @@ const void GCodeBuffer::GetFloatArray(float a[], size_t& returnedLength) } // Get a :-separated list of longs after a key letter - const void GCodeBuffer::GetLongArray(long l[], size_t& returnedLength) { - if(readPointer < 0) + if (readPointer < 0) { platform->Message(GENERIC_MESSAGE, "Error: GCodes: Attempt to read a GCode long array before a search.\n"); readPointer = -1; @@ -266,7 +262,7 @@ const void GCodeBuffer::GetLongArray(long l[], size_t& returnedLength) bool inList = true; while(inList) { - if(length >= returnedLength) // Array limit has been set in here + if (length >= returnedLength) // Array limit has been set in here { platform->MessageF(GENERIC_MESSAGE, "Error: GCodes: Attempt to read a GCode long array that is too long: %s\n", gcodeBuffer); readPointer = -1; @@ -275,12 +271,11 @@ const void GCodeBuffer::GetLongArray(long l[], size_t& returnedLength) } l[length] = strtol(&gcodeBuffer[readPointer + 1], 0, 0); length++; - readPointer++; - while(gcodeBuffer[readPointer] && (gcodeBuffer[readPointer] != ' ') && (gcodeBuffer[readPointer] != LIST_SEPARATOR)) + do { readPointer++; - } - if(gcodeBuffer[readPointer] != LIST_SEPARATOR) + } while(gcodeBuffer[readPointer] != 0 && (gcodeBuffer[readPointer] != ' ') && (gcodeBuffer[readPointer] != LIST_SEPARATOR)); + if (gcodeBuffer[readPointer] != LIST_SEPARATOR) { inList = false; } diff --git a/src/GCodes.cpp b/src/GCodes.cpp index fb2ae899..908f8ead 100644 --- a/src/GCodes.cpp +++ b/src/GCodes.cpp @@ -56,7 +56,6 @@ void GCodes::Exit() void GCodes::Init() { Reset(); - firmwareUpdateModuleMap = 0; distanceScale = 1.0; rawExtruderTotal = 0.0; for (size_t extruder = 0; extruder < DRIVES - AXES; extruder++) @@ -122,7 +121,7 @@ void GCodes::Reset() feedRate = pausedMoveBuffer[DRIVES] = DEFAULT_FEEDRATE/minutesToSeconds; ClearMove(); - for (size_t i =0; i < MaxTriggers; ++i) + for (size_t i = 0; i < MaxTriggers; ++i) { triggers[i].Init(); } @@ -139,6 +138,7 @@ void GCodes::Reset() isPaused = false; filePos = moveBuffer.filePos = noFilePosition; lastEndstopStates = platform->GetAllEndstopStates(); + firmwareUpdateModuleMap = 0; } float GCodes::FractionOfFilePrinted() const @@ -397,7 +397,7 @@ void GCodes::Spin() if (FirmwareUpdater::IsReady()) { bool updating = false; - for (unsigned int module = 0; module < NumFirmwareUpdateModules; ++module) + for (unsigned int module = 1; module < NumFirmwareUpdateModules; ++module) { if ((firmwareUpdateModuleMap & (1 << module)) != 0) { @@ -626,7 +626,10 @@ bool GCodes::CheckTriggers() unsigned int lowestTriggerPending = MaxTriggers; for (unsigned int triggerNumber = 0; triggerNumber < MaxTriggers; ++triggerNumber) { - if ((triggers[triggerNumber].rising & risen) != 0 || (triggers[triggerNumber].falling & fallen) != 0) + const Trigger& ct = triggers[triggerNumber]; + if ( ((ct.rising & risen) != 0 || (ct.falling & fallen) != 0) + && (ct.condition == 0 || (ct.condition == 1 && reprap.GetPrintMonitor()->IsPrinting())) + ) { triggersPending |= (1 << triggerNumber); } @@ -740,7 +743,6 @@ void GCodes::Diagnostics(MessageType mtype) platform->Message(mtype, "GCodes Diagnostics:\n"); platform->MessageF(mtype, "Move available? %s\n", moveAvailable ? "yes" : "no"); platform->MessageF(mtype, "Stack pointer: %u of %u\n", stackPointer, StackSize); - fileMacroGCode->Diagnostics(mtype); httpGCode->Diagnostics(mtype); telnetGCode->Diagnostics(mtype); @@ -1642,9 +1644,10 @@ void GCodes::GetCurrentCoordinates(StringRef& s) const s.catf("E%u:%.1f ", i - AXES, liveCoordinates[i]); } - // Print the stepper motor positions as Marlin does, as an aid to debugging + // Print the axis stepper motor positions as Marlin does, as an aid to debugging. + // Don't bother with the extruder endpoints, they are zero after any non-extruding move. s.cat(" Count"); - for (size_t i = 0; i < DRIVES; ++i) + for (size_t i = 0; i < AXES; ++i) { s.catf(" %d", reprap.GetMove()->GetEndPoint(i)); } @@ -2769,7 +2772,7 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) bool error = false; int code = gb->GetIValue(); - if (simulationMode != 0 && (code < 20 || code > 37) && code != 82 && code != 83 && code != 111 && code != 105 && code != 122 && code != 408 && code != 999) + if (simulationMode != 0 && (code < 20 || code > 37) && code != 0 && code != 1 && code != 82 && code != 83 && code != 111 && code != 105 && code != 122 && code != 408 && code != 999) { return true; // we don't yet simulate most M codes } @@ -3409,6 +3412,10 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) hh |= (1 << (unsigned int)hnum); } } + if (hh != 0) + { + platform->SetFanValue(fanNum, 1.0); // default the fan speed to full for safety + } platform->SetHeatersMonitored(fanNum, hh); } @@ -3510,7 +3517,7 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) DoEmergencyStop(); break; - case 114: // Deprecated + case 114: GetCurrentCoordinates(reply); break; @@ -3585,11 +3592,8 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) case 117: // Display message { - const char *msg = gb->GetUnprecedentedString(); - if (msg != nullptr) - { - reprap.SetMessage(msg); - } + const char *msg = gb->GetUnprecedentedString(true); + reprap.SetMessage((msg == nullptr) ? "" : msg); } break; @@ -4112,7 +4116,11 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) { seen = true; int microsteps = gb->GetIValue(); - if (!ChangeMicrostepping(axis, microsteps, interp)) + if (ChangeMicrostepping(axis, microsteps, interp)) + { + axisIsHomed[axis] = false; + } + else { platform->MessageF(GENERIC_MESSAGE, "Drive %c does not support %dx microstepping%s\n", axisLetters[axis], microsteps, (interp) ? " with interpolation" : ""); @@ -5018,63 +5026,81 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) triggersPending |= (1 << triggerNumber); } } - else if (gb->Seen('S')) + else { - int sval = gb->GetIValue(); - TriggerMask triggerMask = 0; - for (size_t axis = 0; axis < AXES; ++axis) + bool seen = false; + if (gb->Seen('C')) { - if (gb->Seen(axisLetters[axis])) - { - triggerMask |= (1u << axis); - } + seen = true; + triggers[triggerNumber].condition = gb->GetIValue(); + } + else if (triggers[triggerNumber].IsUnused()) + { + triggers[triggerNumber].condition = 0; // this is a new trigger, so set no condition } - if (gb->Seen(extrudeLetter)) + if (gb->Seen('S')) { - long eStops[DRIVES - AXES]; - size_t numEntries = DRIVES - AXES; - gb->GetLongArray(eStops, numEntries); - for (size_t i = 0; i < numEntries; ++i) + seen = true; + int sval = gb->GetIValue(); + TriggerMask triggerMask = 0; + for (size_t axis = 0; axis < AXES; ++axis) { - if (eStops[i] >= 0 && (unsigned long)eStops[i] < DRIVES - AXES) + if (gb->Seen(axisLetters[axis])) { - triggerMask |= (1u << (eStops[i] + AXES)); + triggerMask |= (1u << axis); } } - } - switch(sval) - { - case -1: - if (triggerMask == 0) + if (gb->Seen(extrudeLetter)) { - triggers[triggerNumber].rising = triggers[triggerNumber].falling = 0; + long eStops[DRIVES - AXES]; + size_t numEntries = DRIVES - AXES; + gb->GetLongArray(eStops, numEntries); + for (size_t i = 0; i < numEntries; ++i) + { + if (eStops[i] >= 0 && (unsigned long)eStops[i] < DRIVES - AXES) + { + triggerMask |= (1u << (eStops[i] + AXES)); + } + } } - else + switch(sval) { - triggers[triggerNumber].rising &= (~triggerMask); - triggers[triggerNumber].falling &= (~triggerMask); - } - break; + case -1: + if (triggerMask == 0) + { + triggers[triggerNumber].rising = triggers[triggerNumber].falling = 0; + } + else + { + triggers[triggerNumber].rising &= (~triggerMask); + triggers[triggerNumber].falling &= (~triggerMask); + } + break; - case 0: - triggers[triggerNumber].falling |= triggerMask; - break; + case 0: + triggers[triggerNumber].falling |= triggerMask; + break; - case 1: - triggers[triggerNumber].rising |= triggerMask; - break; + case 1: + triggers[triggerNumber].rising |= triggerMask; + break; - default: - platform->Message(GENERIC_MESSAGE, "Bad S parameter in M581 command\n"); + default: + platform->Message(GENERIC_MESSAGE, "Bad S parameter in M581 command\n"); + } + } + if (!seen) + { + reply.printf("Trigger %u fires on a rising edge on ", triggerNumber); + ListTriggers(reply, triggers[triggerNumber].rising); + reply.cat(" or a falling edge on "); + ListTriggers(reply, triggers[triggerNumber].falling); + reply.cat(" endstop inputs"); + if (triggers[triggerNumber].condition == 1) + { + reply.cat(" when printing from SD card"); + } } - } - else - { - reply.printf("Trigger %u fires on a rising edge on ", triggerNumber); - ListTriggers(reply, triggers[triggerNumber].rising); - reply.cat(" or a falling edge on "); - ListTriggers(reply, triggers[triggerNumber].falling); - reply.cat(" endstop inputs"); } } else @@ -5301,27 +5327,21 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) break; case 997: // Perform firmware update + if (!AllMovesAreFinishedAndMoveBufferIsLoaded()) + { + return false; + } + reprap.GetHeat()->SwitchOffAll(); // turn all heaters off because the main loop may get suspended + DisableDrives(); // all motors off + if (firmwareUpdateModuleMap == 0) // have we worked out which modules to update? { // Find out which modules we have been asked to update - long modulesToUpdate[3]; - size_t numUpdateModules; if (gb->Seen('S')) { - numUpdateModules = ARRAY_SIZE(modulesToUpdate); + long modulesToUpdate[3]; + size_t numUpdateModules = ARRAY_SIZE(modulesToUpdate); gb->GetLongArray(modulesToUpdate, numUpdateModules); - } - else - { - numUpdateModules = 0; - } - - if (numUpdateModules == 0) - { - firmwareUpdateModuleMap = (1 << 0); // no modules specified, so update module 0 to match old behaviour - } - else - { for (size_t i = 0; i < numUpdateModules; ++i) { long t = modulesToUpdate[i]; @@ -5331,27 +5351,37 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) firmwareUpdateModuleMap = 0; break; } - firmwareUpdateModuleMap |= (1 << t); + firmwareUpdateModuleMap |= (1 << (unsigned int)t); } } + else + { + firmwareUpdateModuleMap = (1 << 0); // no modules specified, so update module 0 to match old behaviour + } + + if (firmwareUpdateModuleMap == 0) + { + break; // nothing to update + } // Check prerequisites of all modules to be updated, if any are not met then don't update any of them #ifdef DUET_NG if (!FirmwareUpdater::CheckFirmwareUpdatePrerequisites(firmwareUpdateModuleMap)) { + firmwareUpdateModuleMap = 0; break; } #endif if ((firmwareUpdateModuleMap & 1) != 0 && !platform->CheckFirmwareUpdatePrerequisites()) { + firmwareUpdateModuleMap = 0; break; } } // If we get here then we have the module map, and all prerequisites are satisfied - reprap.GetHeat()->SwitchOffAll(); // turn all heaters off because the main loop may get suspended isFlashing = true; // this tells the web interface and PanelDue that we are about to flash firmware - if (!DoDwellTime(1.0)) // wait a second so all HTTP clients are notified + if (!DoDwellTime(1.0)) // wait a second so all HTTP clients and PanelDue are notified { return false; } @@ -5370,7 +5400,6 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) if (val != 0) { reply.printf("Checksum error on line %d", val); - //resend = true; // FIXME? } } break; diff --git a/src/GCodes.h b/src/GCodes.h index c7f3c5fd..9a89fc56 100644 --- a/src/GCodes.h +++ b/src/GCodes.h @@ -78,10 +78,17 @@ struct Trigger { TriggerMask rising; TriggerMask falling; + uint8_t condition; void Init() { rising = falling = 0; + condition = 0; + } + + bool IsUnused() const + { + return rising == 0 && falling == 0; } }; @@ -257,7 +264,6 @@ private: uint32_t auxSeq; // Sequence number for AUX devices float simulationTime; // Accumulated simulation time uint8_t simulationMode; // 0 = not simulating, 1 = simulating, >1 are simulation modes for debugging - bool isFlashing; // Is a new firmware binary going to be flashed? FilePosition filePos; // The position we got up to in the file being printed // Firmware retraction settings @@ -273,6 +279,7 @@ private: // Firmware update uint8_t firmwareUpdateModuleMap; // Bitmap of firmware modules to be updated + bool isFlashing; // Is a new firmware binary going to be flashed? }; //***************************************************************************************************** diff --git a/src/Heat.cpp b/src/Heat.cpp index f7de96c1..b4060b59 100644 --- a/src/Heat.cpp +++ b/src/Heat.cpp @@ -71,12 +71,12 @@ void Heat::Spin() void Heat::Diagnostics(MessageType mtype) { - platform->Message(mtype, "Heat Diagnostics:\n"); + platform->MessageF(mtype, "Heat Diagnostics:\nBed heater = %d, chamber heater = %d\n", bedHeater, chamberHeater); for (size_t heater=0; heater < HEATERS; heater++) { if (pids[heater]->Active()) { - platform->MessageF(mtype, "Heater %d: I-accumulator = %.1f\n", heater, pids[heater]->GetAccumulator()); + platform->MessageF(mtype, "Heater %d is on, I-accum = %.1f\n", heater, pids[heater]->GetAccumulator()); } } } @@ -231,8 +231,7 @@ void PID::Init() averagePWM = 0.0; // Time the sensor was last sampled. During startup, we use the current - // time as the initial value so as to not trigger an immediate warning from - // the Tick ISR. + // time as the initial value so as to not trigger an immediate warning from the Tick ISR. lastSampleTime = millis(); } @@ -240,7 +239,7 @@ void PID::SwitchOn() { if (reprap.Debug(Module::moduleHeat)) { - platform->MessageF(GENERIC_MESSAGE, "Heater %d switched on.\n", heater); + platform->MessageF(GENERIC_MESSAGE, "Heater %d %s\n", heater, (temperatureFault) ? "not switched on due to temperature fault" : "switched on"); } switchedOff = temperatureFault; } @@ -474,6 +473,10 @@ void PID::SwitchOff() active = false; switchedOff = true; heatingUp = false; + if (reprap.Debug(Module::moduleHeat)) + { + platform->MessageF(GENERIC_MESSAGE, "Heater %d switched off", heater); + } } float PID::GetAveragePWM() const diff --git a/src/Platform.cpp b/src/Platform.cpp index 437b4ff8..d5083261 100644 --- a/src/Platform.cpp +++ b/src/Platform.cpp @@ -1,6 +1,6 @@ /**************************************************************************************************** - RepRapFirmware - Platform: RepRapPro Ormerod with Arduino Due controller + RepRapFirmware - Platform: RepRapPro Ormerod with Duet controller Platform contains all the code and definitions to deal with machine-dependent things such as control pins, bed area, number of extruders, tolerable accelerations and speeds and so on. @@ -25,7 +25,7 @@ #include "sam/drivers/tc/tc.h" #include "sam/drivers/hsmci/hsmci.h" -#ifdef EXTERNAL_DRIVERS +#if defined(DUET_NG) || defined (EXTERNAL_DRIVERS) # include "ExternalDrivers.h" #endif @@ -185,8 +185,10 @@ void Platform::Init() fileStructureInitialised = true; +#if !defined(DUET_NG) || defined(PROTOTYPE_1) mcpDuet.begin(); // only call begin once in the entire execution, this begins the I2C comms on that channel for all objects mcpExpansion.setMCP4461Address(0x2E); // not required for mcpDuet, as this uses the default address +#endif // Directories @@ -209,11 +211,15 @@ void Platform::Init() ARRAY_INIT(accelerations, ACCELERATIONS); ARRAY_INIT(driveStepsPerUnit, DRIVE_STEPS_PER_UNIT); ARRAY_INIT(instantDvs, INSTANT_DVS); + +#if !defined(DUET_NG) || defined(PROTOTYPE_1) ARRAY_INIT(potWipes, POT_WIPES); senseResistor = SENSE_RESISTOR; maxStepperDigipotVoltage = MAX_STEPPER_DIGIPOT_VOLTAGE; stepperDacVoltageRange = STEPPER_DAC_VOLTAGE_RANGE; stepperDacVoltageOffset = STEPPER_DAC_VOLTAGE_OFFSET; +#endif + maxAverageAcceleration = 10000.0; // high enough to have no effect until it is changed // Z PROBE @@ -264,14 +270,17 @@ void Platform::Init() { pinMode(directionPins[drive], OUTPUT); } -#ifdef EXTERNAL_DRIVERS + +#if !defined(DUET_NG) || defined(PROTOTYPE_1) +# ifdef EXTERNAL_DRIVERS if (drive < FIRST_EXTERNAL_DRIVE && enablePins[drive] >= 0) -#else +# else if (enablePins[drive] >= 0) -#endif +# endif { pinMode(enablePins[drive], OUTPUT); } +#endif if (endStopPins[drive] >= 0) { pinMode(endStopPins[drive], INPUT_PULLUP); @@ -928,6 +937,19 @@ void Platform::Beep(int freq, int ms) MessageF(AUX_MESSAGE, "{\"beep_freq\":%d,\"beep_length\":%d}\n", freq, ms); } +// Send a short message to the aux channel. There is no flow control on this port, so it can't block for long. +void Platform::SendMessage(const char* msg) +{ + OutputBuffer *buf; + if (OutputBuffer::Allocate(buf)) + { + buf->copy("{\"message\":"); + buf->EncodeString(msg, strlen(msg), false, true); + buf->cat("}\n"); + Message(AUX_MESSAGE, buf); + } +} + // Note: the use of floating point time will cause the resolution to degrade over time. // For example, 1ms time resolution will only be available for about half an hour from startup. // Personally, I (dc42) would rather just maintain and provide the time in milliseconds in a uint32_t. @@ -1316,13 +1338,15 @@ void Platform::Diagnostics(MessageType mtype) } MessageF(mtype, "Free file entries: %u\n", numFreeFiles); - // Show the longest write time - MessageF(mtype, "Longest block write time: %.1fms\n", FileStore::GetAndClearLongestWriteTime()); - // Show the HSMCI speed - MessageF(mtype, "SD card speed: %.1fMHz\n", (float)hsmci_get_speed()/1000000.0); + MessageF(mtype, "SD card interface speed: %.1fMBytes/sec\n", (float)hsmci_get_speed()/1000000.0); + + // Show the longest SD card write time + MessageF(mtype, "SD card longest block write time: %.1fms\n", FileStore::GetAndClearLongestWriteTime()); // Debug +//MessageF(mtype, "TC_FMR = %08x, PWM_FPE = %08x, PWM_FSR = %08x\n", TC2->TC_FMR, PWM->PWM_FPE, PWM->PWM_FSR); +//MessageF(mtype, "PWM2 period %08x, duty %08x\n", PWM->PWM_CH_NUM[2].PWM_CPRD, PWM->PWM_CH_NUM[2].PWM_CDTY); //MessageF(mtype, "Shortest/longest times read %.1f/%.1f write %.1f/%.1f ms, %u/%u\n", // (float)shortestReadWaitTime/1000, (float)longestReadWaitTime/1000, (float)shortestWriteWaitTime/1000, (float)longestWriteWaitTime/1000, // maxRead, maxWrite); @@ -1660,21 +1684,25 @@ void Platform::EnableDrive(size_t drive) { UpdateMotorCurrent(driver); // the current may have been reduced by the idle timeout -#ifdef EXTERNAL_DRIVERS +#if defined(DUET_NG) && !defined(PROTOTYPE_1) + ExternalDrivers::EnableDrive(driver, true); +#else +# ifdef EXTERNAL_DRIVERS if (driver >= FIRST_EXTERNAL_DRIVE) { ExternalDrivers::EnableDrive(driver - FIRST_EXTERNAL_DRIVE, true); } else { -#endif +# endif const int pin = enablePins[driver]; if (pin >= 0) { digitalWrite(pin, enableValues[driver]); } -#ifdef EXTERNAL_DRIVERS +# ifdef EXTERNAL_DRIVERS } +# endif #endif } } @@ -1685,23 +1713,27 @@ void Platform::DisableDrive(size_t drive) { if (drive < DRIVES) { +#if defined(DUET_NG) && !defined(PROTOTYPE_1) + ExternalDrivers::EnableDrive(driverNumbers[drive], false); +#else const size_t driver = driverNumbers[drive]; -#ifdef EXTERNAL_DRIVERS +# ifdef EXTERNAL_DRIVERS if (driver >= FIRST_EXTERNAL_DRIVE) { ExternalDrivers::EnableDrive(driver - FIRST_EXTERNAL_DRIVE, false); } else { -#endif +# endif const int pin = enablePins[driver]; if (pin >= 0) { digitalWrite(pin, !enableValues[driver]); } driveState[drive] = DriveStatus::disabled; -#ifdef EXTERNAL_DRIVERS +# ifdef EXTERNAL_DRIVERS } +# endif #endif } } @@ -1741,14 +1773,19 @@ void Platform::UpdateMotorCurrent(size_t drive) current *= idleCurrentFactor; } const size_t driver = driverNumbers[drive]; -#ifdef EXTERNAL_DRIVERS + +#if defined(DUET_NG) && !defined(PROTOTYPE_1) + ExternalDrivers::SetCurrent(driver, current); +#else + +# ifdef EXTERNAL_DRIVERS if (driver >= FIRST_EXTERNAL_DRIVE) { ExternalDrivers::SetCurrent(driver - FIRST_EXTERNAL_DRIVE, current); } else { -#endif +# endif unsigned short pot = (unsigned short)((0.256*current*8.0*senseResistor + maxStepperDigipotVoltage/2)/maxStepperDigipotVoltage); if (driver < 4) { @@ -1757,37 +1794,39 @@ void Platform::UpdateMotorCurrent(size_t drive) } else { -#ifndef DUET_NG +# ifndef DUET_NG if (board == BoardType::Duet_085) { -#endif +# endif // Extruder 0 is on DAC channel 0 if (driver == 4) { float dacVoltage = max<float>(current * 0.008*senseResistor + stepperDacVoltageOffset, 0.0); // the voltage we want from the DAC relative to its minimum uint32_t dac = (uint32_t)((256 * dacVoltage + 0.5 * stepperDacVoltageRange)/stepperDacVoltageRange); -#ifdef DUET_NG +# ifdef DUET_NG AnalogWrite(DAC1, dac); -#else +# else AnalogWrite(DAC0, dac); -#endif +# endif } else { mcpExpansion.setNonVolatileWiper(potWipes[driver-1], pot); mcpExpansion.setVolatileWiper(potWipes[driver-1], pot); } -#ifndef DUET_NG +# ifndef DUET_NG } else if (driver < 8) // on a Duet 0.6 we have a maximum of 8 drives { mcpExpansion.setNonVolatileWiper(potWipes[driver], pot); mcpExpansion.setVolatileWiper(potWipes[driver], pot); } -#endif +# endif } -#ifdef EXTERNAL_DRIVERS +# ifdef EXTERNAL_DRIVERS } +# endif + #endif } } @@ -1815,7 +1854,10 @@ bool Platform::SetMicrostepping(size_t drive, int microsteps, int mode) { if (drive < DRIVES) { -#ifdef EXTERNAL_DRIVERS +#if defined(DUET_NG) && !defined(PROTOTYPE_1) + return ExternalDrivers::SetMicrostepping(driverNumbers[drive], microsteps, mode); +#else +# ifdef EXTERNAL_DRIVERS const size_t driver = driverNumbers[drive]; if (driver >= FIRST_EXTERNAL_DRIVE) { @@ -1823,12 +1865,14 @@ bool Platform::SetMicrostepping(size_t drive, int microsteps, int mode) } else { -#endif +# endif // On-board drivers only support x16 microstepping. // We ignore the interpolation on/off parameter so that e.g. M350 I1 E16:128 won't give an error if E1 supports interpolation but E0 doesn't. return microsteps == 16; -#ifdef EXTERNAL_DRIVERS +# ifdef EXTERNAL_DRIVERS } +# endif + #endif } return false; @@ -1836,7 +1880,13 @@ bool Platform::SetMicrostepping(size_t drive, int microsteps, int mode) unsigned int Platform::GetMicrostepping(size_t drive, bool& interpolation) const { -#ifdef EXTERNAL_DRIVERS +#if defined(DUET_NG) && !defined(PROTOTYPE_1) + if (drive < DRIVES) + { + return ExternalDrivers::GetMicrostepping(driverNumbers[drive], interpolation); + } +# else +# ifdef EXTERNAL_DRIVERS if (drive < DRIVES) { const size_t driver = driverNumbers[drive]; @@ -1845,8 +1895,8 @@ unsigned int Platform::GetMicrostepping(size_t drive, bool& interpolation) const return ExternalDrivers::GetMicrostepping(driver - FIRST_EXTERNAL_DRIVE, interpolation); } } +# endif #endif - // On-board drivers only support x16 microstepping without interpolation interpolation = false; return 16; @@ -1937,10 +1987,11 @@ void Platform::InitFans() for (size_t i = 0; i < NUM_FANS; ++i) { fans[i].Init(COOLING_FAN_PINS[i], - !HEAT_ON -#ifndef DUET_NG - // The cooling fan 0 output pin gets inverted if HEAT_ON == 0 on a Duet 0.4, 0.6 or 0.7 - && (board == BoardType::Duet_06 || board == BoardType::Duet_07) +#ifdef DUET_NG + false +#else + // The cooling fan output pin gets inverted if HEAT_ON == 0 on a Duet 0.6 or 0.7 + !HEAT_ON && (board == BoardType::Duet_06 || board == BoardType::Duet_07) #endif ); } @@ -1949,6 +2000,7 @@ void Platform::InitFans() { // Set fan 1 to be thermostatic by default, monitoring all heaters except the default bed heater fans[1].SetHeatersMonitored(0xFFFF & ~(1 << BED_HEATER)); + fans[1].SetValue(1.0); // set it full on } coolingFanRpmPin = COOLING_FAN_RPM_PIN; @@ -2060,7 +2112,9 @@ void Platform::Fan::Check() { if (heatersMonitored != 0) { - val = (reprap.GetPlatform()->AnyHeaterHot(heatersMonitored, triggerTemperature)) ? 1.0 : 0.0; + val = (reprap.GetPlatform()->AnyHeaterHot(heatersMonitored, triggerTemperature)) + ? max<float>(0.5, val) // make sure that thermostatic fans always run at 50% speed or more + : 0.0; Refresh(); } } @@ -2198,18 +2252,8 @@ void Platform::Message(MessageType type, const char *message) break; case FIRMWARE_UPDATE_MESSAGE: - Message(HOST_MESSAGE, message); - // Send an alert message to the aux port - { - OutputBuffer *buf; - if (OutputBuffer::Allocate(buf, false)) - { - buf->cat("{\"alert\":"); - buf->EncodeString(message, strlen(message), true, true); - buf->cat("}\n"); - Message(AUX_MESSAGE, buf); - } - } + Message(HOST_MESSAGE, message); // send message to USB + SendMessage(message); // send message to aux break; case GENERIC_MESSAGE: @@ -2287,7 +2331,7 @@ void Platform::Message(const MessageType type, OutputBuffer *buffer) break; case FIRMWARE_UPDATE_MESSAGE: - // We don't generate any of these with an OutputBuffer argument, but if we get one, just send it to USB + // We don't generate any of these with an OutputBuffer argument, but if do we get one, just send it to USB Message(HOST_MESSAGE, buffer); break; @@ -2416,7 +2460,11 @@ void Platform::SetBoardType(BoardType bt) if (bt == BoardType::Auto) { #ifdef DUET_NG - board = BoardType::DuetNG_08; +# ifdef PROTOTYPE_1 + board = BoardType::DuetNG_06; +# else + board = BoardType::DuetNG_10; +# endif #else // Determine whether this is a Duet 0.6 or a Duet 0.8.5 board. // If it is a 0.85 board then DAC0 (AKA digital pin 67) is connected to ground via a diode and a 2.15K resistor. @@ -2445,7 +2493,11 @@ const char* Platform::GetElectronicsString() const switch (board) { #ifdef DUET_NG - case BoardType::DuetNG_08: return "DuetNG 0.6"; +# ifdef PROTOTYPE_1 + case BoardType::DuetNG_06: return "DuetNG 0.6"; +# else + case BoardType::DuetNG_10: return "DuetNG 1.0"; +# endif #else case BoardType::Duet_06: return "Duet 0.6"; case BoardType::Duet_07: return "Duet 0.7"; @@ -2459,18 +2511,26 @@ const char* Platform::GetElectronicsString() const // Set the specified pin to the specified output level. Return true if success, false if not allowed. bool Platform::SetPin(int pin, int level) { - if (pin >= 0 && (unsigned int)pin < NUM_PINS_ALLOWED && (level == 0 || level == 1)) + if (pin >= 0 && (unsigned int)pin < NUM_PINS_ALLOWED && (level >= 0 || level <= 255)) { const size_t index = (unsigned int)pin/8; const uint8_t mask = 1 << ((unsigned int)pin & 7); if ((pinAccessAllowed[index] & mask) != 0) { +#ifdef DUET_NG //TODO temporary + if (level == 1) + { + level = 255; + } + AnalogWrite(pin, level, 1000); +#else if ((pinInitialised[index] & mask) == 0) { pinMode(pin, OUTPUT); pinInitialised[index] |= mask; } digitalWrite(pin, level); +#endif return true; } } diff --git a/src/Platform.h b/src/Platform.h index 67a40182..e8107bcb 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -49,7 +49,11 @@ Licence: GPL #include "Core.h" #include "OutputMemory.h" #include "ff.h" -#include "MCP4461.h" + +#if !defined(DUET_NG) || defined(PROTOTYPE_1) +# include "MCP4461.h" +#endif + #include "MassStorage.h" #include "FileStore.h" #include "MessageType.h" @@ -178,7 +182,11 @@ enum class BoardType : uint8_t { Auto = 0, #ifdef DUET_NG - DuetNG_08 = 1 +# ifdef PROTOTYPE_1 + DuetNG_06 = 1 +# else + DuetNG_10 = 1 +# endif #else Duet_06 = 1, Duet_07 = 2, @@ -591,6 +599,7 @@ public: // AUX device void Beep(int freq, int ms); + void SendMessage(const char* msg); // Hotend configuration float GetFilamentWidth() const; @@ -719,12 +728,14 @@ private: // Digipots +#if !defined(DUET_NG) || defined(PROTOTYPE_1) MCP4461 mcpDuet; MCP4461 mcpExpansion; Pin potWipes[8]; // we have only 8 digipots, on the Duet 0.8.5 we use the DAC for the 9th float senseResistor; float maxStepperDigipotVoltage; float stepperDacVoltageRange, stepperDacVoltageOffset; +#endif // Z probe diff --git a/src/Reprap.cpp b/src/Reprap.cpp index afbf8202..9bfd555a 100644 --- a/src/Reprap.cpp +++ b/src/Reprap.cpp @@ -562,12 +562,11 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source) int toolNumber = (currentTool == nullptr) ? -1 : currentTool->Number(); response->catf("]},\"currentTool\":%d", toolNumber); - /* Output - only reported once */ + // Output - only reported once { bool sendBeep = (beepDuration != 0 && beepFrequency != 0); bool sendMessage = (message[0] != 0); - bool sourceRight = (gCodes->HaveAux() && source == ResponseSource::AUX) || (!gCodes->HaveAux() && source == ResponseSource::HTTP); - if ((sendBeep || message[0] != 0) && sourceRight) + if (sendBeep || sendMessage) { response->cat(",\"output\":{"); @@ -579,7 +578,6 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source) { response->cat(","); } - beepFrequency = beepDuration = 0; } @@ -600,17 +598,12 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source) response->catf(",\"params\":{\"atxPower\":%d", platform->AtxPower() ? 1 : 0); // Cooling fan value - response->cat(",\"fanPercent\":["); + response->cat(",\"fanPercent\":"); + ch = '['; for(size_t i = 0; i < NUM_FANS; i++) { - if (i == NUM_FANS - 1) - { - response->catf("%.2f", platform->GetFanValue(i) * 100.0); - } - else - { - response->catf("%.2f,", platform->GetFanValue(i) * 100.0); - } + response->catf("%c%.2f", ch, platform->GetFanValue(i) * 100.0); + ch = ','; } // Speed and Extrusion factors @@ -1114,11 +1107,17 @@ OutputBuffer *RepRap::GetLegacyStatusResponse(uint8_t type, int seq) break; } - // Send the fan0 settings (for PanelDue firmware 1.13) - response->catf(",\"fanPercent\":[%.02f,%.02f]", platform->GetFanValue(0) * 100.0, platform->GetFanValue(1) * 100.0); + // Send the fan settings, for PanelDue firmware 1.13 and later + response->catf(",\"fanPercent\":"); + ch = '['; + for (size_t i = 0; i < NUM_FANS; ++i) + { + response->catf("%c%.02f", ch, platform->GetFanValue(i)); + ch = ','; + } - // Send fan RPM value - response->catf(",\"fanRPM\":%u", static_cast<unsigned int>(platform->GetFanRPM())); + // Send fan RPM value (we only support one) + response->catf("],\"fanRPM\":%u", static_cast<unsigned int>(platform->GetFanRPM())); // Send the home state. To keep the messages short, we send 1 for homed and 0 for not homed, instead of true and false. response->catf(",\"homed\":[%d,%d,%d]", @@ -1132,14 +1131,10 @@ OutputBuffer *RepRap::GetLegacyStatusResponse(uint8_t type, int seq) response->catf(",\"fraction_printed\":%.4f", max<float>(0.0, gCodes->FractionOfFilePrinted())); } - response->cat(",\"message\":"); - response->EncodeString(message, ARRAY_SIZE(message), false); + // Short messages are now pushed directly to PanelDue, so don't include them here as well + // We no longer send the amount of http buffer space here because the web interface doesn't use these formns of status response - if (type < 2) - { - response->catf(",\"buff\":%u", webserver->GetGCodeBufferSpace(WebSource::HTTP)); // send the amount of buffer space available for gcodes - } - else if (type == 2) + if (type == 2) { if (printMonitor->IsPrinting()) { @@ -1251,25 +1246,29 @@ OutputBuffer *RepRap::GetFilesResponse(const char *dir, bool flagsDirs) return response; } +// Send a beep. We send it to both PanelDue and the web interface. void RepRap::Beep(int freq, int ms) { + beepFrequency = freq; + beepDuration = ms; + if (gCodes->HaveAux()) { // If there is an LCD device present, make it beep platform->Beep(freq, ms); } - else - { - // Otherwise queue it until the webserver can process it - beepFrequency = freq; - beepDuration = ms; - } } +// Send a short message. We send it to both PanelDue and the web interface. void RepRap::SetMessage(const char *msg) { strncpy(message, msg, MESSAGE_LENGTH); message[MESSAGE_LENGTH] = 0; + + if (gCodes->HaveAux()) + { + platform->SendMessage(msg); + } } // Get the status character for the new-style status response diff --git a/src/TemperatureError.cpp b/src/TemperatureError.cpp index 182d0755..115d2841 100644 --- a/src/TemperatureError.cpp +++ b/src/TemperatureError.cpp @@ -30,15 +30,20 @@ const char* TemperatureErrorString(TemperatureError err) // and so the heater should just be shut off immediately. bool IsPermanentError(TemperatureError err) { +#if 1 + return false; +#else switch (err) { case TemperatureError::success: case TemperatureError::busBusy: case TemperatureError::ioError: + case TemperatureError::hardwareError: // need this one because it comes up sometimes (Jimustanguitar on seemecnc forum) return false; default: return true; } +#endif } // End |