diff --git a/Firmware b/Firmware deleted file mode 160000 index 40e9c8a..0000000 --- a/Firmware +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 40e9c8a774ce4707db48dc1ce6462cbe0044825f diff --git a/Firmware_V2/.cproject b/Firmware_V2/.cproject new file mode 100644 index 0000000..b5e5732 --- /dev/null +++ b/Firmware_V2/.cproject @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Firmware_V2/.gitignore b/Firmware_V2/.gitignore new file mode 100644 index 0000000..652f297 --- /dev/null +++ b/Firmware_V2/.gitignore @@ -0,0 +1 @@ +Release/ \ No newline at end of file diff --git a/Firmware_V2/.project b/Firmware_V2/.project new file mode 100644 index 0000000..338a4ca --- /dev/null +++ b/Firmware_V2/.project @@ -0,0 +1,40 @@ + + + DIYThermocam + + + + + + io.sloeber.core.inoToCpp + + + + + org.eclipse.cdt.managedbuilder.core.genmakebuilder + clean,full,incremental, + + + + + org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder + full,incremental, + + + + + + org.eclipse.cdt.core.cnature + org.eclipse.cdt.core.ccnature + org.eclipse.cdt.managedbuilder.core.managedBuildNature + org.eclipse.cdt.managedbuilder.core.ScannerConfigNature + io.sloeber.arduinonature + + + + core/core + 2 + C:/Program Files (x86)/Arduino/hardware/teensy/avr/cores/teensy3 + + + diff --git a/Firmware_V2/.settings/language.settings.xml b/Firmware_V2/.settings/language.settings.xml new file mode 100644 index 0000000..934b42e --- /dev/null +++ b/Firmware_V2/.settings/language.settings.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Firmware_V2/.settings/org.eclipse.cdt.codan.core.prefs b/Firmware_V2/.settings/org.eclipse.cdt.codan.core.prefs new file mode 100644 index 0000000..6907ef9 --- /dev/null +++ b/Firmware_V2/.settings/org.eclipse.cdt.codan.core.prefs @@ -0,0 +1,72 @@ +eclipse.preferences.version=1 +org.eclipse.cdt.codan.checkers.errnoreturn=Warning +org.eclipse.cdt.codan.checkers.errnoreturn.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"No return\\")",implicit\=>false} +org.eclipse.cdt.codan.checkers.errreturnvalue=Error +org.eclipse.cdt.codan.checkers.errreturnvalue.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused return value\\")"} +org.eclipse.cdt.codan.checkers.nocommentinside=-Error +org.eclipse.cdt.codan.checkers.nocommentinside.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Nesting comments\\")"} +org.eclipse.cdt.codan.checkers.nolinecomment=-Error +org.eclipse.cdt.codan.checkers.nolinecomment.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Line comments\\")"} +org.eclipse.cdt.codan.checkers.noreturn=Error +org.eclipse.cdt.codan.checkers.noreturn.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"No return value\\")",implicit\=>false} +org.eclipse.cdt.codan.internal.checkers.AbstractClassCreation=-Error +org.eclipse.cdt.codan.internal.checkers.AbstractClassCreation.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Abstract class cannot be instantiated\\")"} +org.eclipse.cdt.codan.internal.checkers.AmbiguousProblem=-Error +org.eclipse.cdt.codan.internal.checkers.AmbiguousProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Ambiguous problem\\")"} +org.eclipse.cdt.codan.internal.checkers.AssignmentInConditionProblem=Warning +org.eclipse.cdt.codan.internal.checkers.AssignmentInConditionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Assignment in condition\\")"} +org.eclipse.cdt.codan.internal.checkers.AssignmentToItselfProblem=Error +org.eclipse.cdt.codan.internal.checkers.AssignmentToItselfProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Assignment to itself\\")"} +org.eclipse.cdt.codan.internal.checkers.CaseBreakProblem=Warning +org.eclipse.cdt.codan.internal.checkers.CaseBreakProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"No break at end of case\\")",no_break_comment\=>"no break",last_case_param\=>false,empty_case_param\=>false,enable_fallthrough_quickfix_param\=>false} +org.eclipse.cdt.codan.internal.checkers.CatchByReference=Warning +org.eclipse.cdt.codan.internal.checkers.CatchByReference.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Catching by reference is recommended\\")",unknown\=>false,exceptions\=>()} +org.eclipse.cdt.codan.internal.checkers.CircularReferenceProblem=-Error +org.eclipse.cdt.codan.internal.checkers.CircularReferenceProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Circular inheritance\\")"} +org.eclipse.cdt.codan.internal.checkers.ClassMembersInitialization=Warning +org.eclipse.cdt.codan.internal.checkers.ClassMembersInitialization.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Class members should be properly initialized\\")",skip\=>true} +org.eclipse.cdt.codan.internal.checkers.FieldResolutionProblem=-Error +org.eclipse.cdt.codan.internal.checkers.FieldResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Field cannot be resolved\\")"} +org.eclipse.cdt.codan.internal.checkers.FunctionResolutionProblem=-Error +org.eclipse.cdt.codan.internal.checkers.FunctionResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Function cannot be resolved\\")"} +org.eclipse.cdt.codan.internal.checkers.InvalidArguments=-Error +org.eclipse.cdt.codan.internal.checkers.InvalidArguments.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid arguments\\")"} +org.eclipse.cdt.codan.internal.checkers.InvalidTemplateArgumentsProblem=-Error +org.eclipse.cdt.codan.internal.checkers.InvalidTemplateArgumentsProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid template argument\\")"} +org.eclipse.cdt.codan.internal.checkers.LabelStatementNotFoundProblem=-Error +org.eclipse.cdt.codan.internal.checkers.LabelStatementNotFoundProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Label statement not found\\")"} +org.eclipse.cdt.codan.internal.checkers.MemberDeclarationNotFoundProblem=-Error +org.eclipse.cdt.codan.internal.checkers.MemberDeclarationNotFoundProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Member declaration not found\\")"} +org.eclipse.cdt.codan.internal.checkers.MethodResolutionProblem=-Error +org.eclipse.cdt.codan.internal.checkers.MethodResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Method cannot be resolved\\")"} +org.eclipse.cdt.codan.internal.checkers.NamingConventionFunctionChecker=-Info +org.eclipse.cdt.codan.internal.checkers.NamingConventionFunctionChecker.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Name convention for function\\")",pattern\=>"^[a-z]",macro\=>true,exceptions\=>()} +org.eclipse.cdt.codan.internal.checkers.NonVirtualDestructorProblem=Warning +org.eclipse.cdt.codan.internal.checkers.NonVirtualDestructorProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Class has a virtual method and non-virtual destructor\\")"} +org.eclipse.cdt.codan.internal.checkers.OverloadProblem=-Error +org.eclipse.cdt.codan.internal.checkers.OverloadProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid overload\\")"} +org.eclipse.cdt.codan.internal.checkers.RedeclarationProblem=-Error +org.eclipse.cdt.codan.internal.checkers.RedeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid redeclaration\\")"} +org.eclipse.cdt.codan.internal.checkers.RedefinitionProblem=-Error +org.eclipse.cdt.codan.internal.checkers.RedefinitionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid redefinition\\")"} +org.eclipse.cdt.codan.internal.checkers.ReturnStyleProblem=-Warning +org.eclipse.cdt.codan.internal.checkers.ReturnStyleProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Return with parenthesis\\")"} +org.eclipse.cdt.codan.internal.checkers.ScanfFormatStringSecurityProblem=-Warning +org.eclipse.cdt.codan.internal.checkers.ScanfFormatStringSecurityProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Format String Vulnerability\\")"} +org.eclipse.cdt.codan.internal.checkers.StatementHasNoEffectProblem=Warning +org.eclipse.cdt.codan.internal.checkers.StatementHasNoEffectProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Statement has no effect\\")",macro\=>true,exceptions\=>()} +org.eclipse.cdt.codan.internal.checkers.SuggestedParenthesisProblem=Warning +org.eclipse.cdt.codan.internal.checkers.SuggestedParenthesisProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Suggested parenthesis around expression\\")",paramNot\=>false} +org.eclipse.cdt.codan.internal.checkers.SuspiciousSemicolonProblem=Warning +org.eclipse.cdt.codan.internal.checkers.SuspiciousSemicolonProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Suspicious semicolon\\")",else\=>false,afterelse\=>false} +org.eclipse.cdt.codan.internal.checkers.TypeResolutionProblem=-Error +org.eclipse.cdt.codan.internal.checkers.TypeResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Type cannot be resolved\\")"} +org.eclipse.cdt.codan.internal.checkers.UnusedFunctionDeclarationProblem=Warning +org.eclipse.cdt.codan.internal.checkers.UnusedFunctionDeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused function declaration\\")",macro\=>true} +org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem=Warning +org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused static function\\")",macro\=>true} +org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem=Warning +org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused variable declaration in file scope\\")",macro\=>true,exceptions\=>("@(\#)","$Id")} +org.eclipse.cdt.codan.internal.checkers.VariableResolutionProblem=-Error +org.eclipse.cdt.codan.internal.checkers.VariableResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Symbol is not resolved\\")"} +useParentScope=false diff --git a/Firmware_V2/.settings/org.eclipse.cdt.core.prefs b/Firmware_V2/.settings/org.eclipse.cdt.core.prefs new file mode 100644 index 0000000..360e2bf --- /dev/null +++ b/Firmware_V2/.settings/org.eclipse.cdt.core.prefs @@ -0,0 +1,456 @@ +eclipse.preferences.version=1 +environment/project/io.sloeber.core.toolChain.release.787846084/A.ALT_SIZE_COMMAND/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.ALT_SIZE_COMMAND/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.ALT_SIZE_COMMAND/value="${A.COMPILER.PATH}${A.COMPILER.SIZE.CMD}" --format\=avr --mcu\=${A.BUILD.MCU} "${A.BUILD.PATH}/${A.BUILD.PROJECT_NAME}.elf" +environment/project/io.sloeber.core.toolChain.release.787846084/A.ARCHIVE_FILE/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.ARCHIVE_FILE/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.ARCHIVE_FILE/value=arduino.ar +environment/project/io.sloeber.core.toolChain.release.787846084/A.ARCHIVE_FILE_PATH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.ARCHIVE_FILE_PATH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.ARCHIVE_FILE_PATH/value=${A.BUILD.PATH}/${A.ARCHIVE_FILE} +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.ARCH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.ARCH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.ARCH/value=AVR +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.BOARD/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.BOARD/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.BOARD/value=TEENSY36 +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.COMMAND.AR/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.COMMAND.AR/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.COMMAND.AR/value=arm-none-eabi-gcc-ar +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.COMMAND.G++/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.COMMAND.G++/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.COMMAND.G++/value=arm-none-eabi-g++ +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.COMMAND.GCC/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.COMMAND.GCC/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.COMMAND.GCC/value=arm-none-eabi-gcc +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.COMMAND.OBJCOPY/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.COMMAND.OBJCOPY/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.COMMAND.OBJCOPY/value=arm-none-eabi-objcopy +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.COMMAND.OBJDUMP/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.COMMAND.OBJDUMP/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.COMMAND.OBJDUMP/value=arm-none-eabi-objdump +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.COMMAND.SIZE/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.COMMAND.SIZE/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.COMMAND.SIZE/value=arm-none-eabi-size +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.CORE.PATH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.CORE.PATH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.CORE.PATH/value=${A.RUNTIME.PLATFORM.PATH}/cores/${A.BUILD.CORE} +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.CORE/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.CORE/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.CORE/value=teensy3 +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FCPU/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FCPU/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FCPU/value=240000000 +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.C/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.C/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.C/value= +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.COMMON/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.COMMON/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.COMMON/value=-g -Wall -ffunction-sections -fdata-sections -nostdlib +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.CPP/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.CPP/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.CPP/value=-fno-exceptions -felide-constructors -std\=gnu++14 -Wno-error\=narrowing -fno-rtti +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.CPU/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.CPU/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.CPU/value=-mthumb -mcpu\=cortex-m4 -mfloat-abi\=hard -mfpu\=fpv4-sp-d16 -fsingle-precision-constant +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.DEFS/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.DEFS/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.DEFS/value=-D__MK66FX1M0__ -DTEENSYDUINO\=143 +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.DEP/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.DEP/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.DEP/value=-MMD +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.LD/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.LD/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.LD/value=-Wl,--gc-sections,--relax,--defsym\=__rtc_localtime\=${A.EXTRA.TIME.LOCAL} "-T${A.BUILD.CORE.PATH}/mk66fx1m0.ld" -lstdc++ +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.LDSPECS/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.LDSPECS/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.LDSPECS/value=-fuse-linker-plugin +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.LIBS/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.LIBS/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.LIBS/value=-larm_cortexM4lf_math -lm +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.OPTIMIZE/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.OPTIMIZE/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.OPTIMIZE/value=-O3 -flto -fno-fat-lto-objects +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.S/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.S/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.FLAGS.S/value=-x assembler-with-cpp +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.KEYLAYOUT/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.KEYLAYOUT/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.KEYLAYOUT/value=US_INTERNATIONAL +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.MCU/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.MCU/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.MCU/value=mk66fx1m0 +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.PATH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.PATH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.PATH/value=${ProjDirPath}/${ConfigName} +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.PROJECT_NAME/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.PROJECT_NAME/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.PROJECT_NAME/value=${ProjName} +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.SYSTEM.PATH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.SYSTEM.PATH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.SYSTEM.PATH/value=${A.RUNTIME.PLATFORM.PATH}/system +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.TOOLCHAIN/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.TOOLCHAIN/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.TOOLCHAIN/value=arm/bin/ +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.USBTYPE/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.USBTYPE/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.USBTYPE/value=USB_SERIAL +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.VARIANT.PATH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.VARIANT.PATH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.VARIANT.PATH/value= +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.WARN_DATA_PERCENTAGE/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.WARN_DATA_PERCENTAGE/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.BUILD.WARN_DATA_PERCENTAGE/value=99 +environment/project/io.sloeber.core.toolChain.release.787846084/A.COMPILER.ELF2HEX.FLAGS/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.COMPILER.ELF2HEX.FLAGS/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.COMPILER.ELF2HEX.FLAGS/value=-O ihex -R .eeprom +environment/project/io.sloeber.core.toolChain.release.787846084/A.COMPILER.OBJCOPY.EEP.FLAGS/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.COMPILER.OBJCOPY.EEP.FLAGS/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.COMPILER.OBJCOPY.EEP.FLAGS/value=-O ihex -j .eeprom --set-section-flags\=.eeprom\=alloc,load --no-change-warnings --change-section-lma .eeprom\=0 +environment/project/io.sloeber.core.toolChain.release.787846084/A.COMPILER.PATH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.COMPILER.PATH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.COMPILER.PATH/value=${A.RUNTIME.HARDWARE.PATH}/../tools/ +environment/project/io.sloeber.core.toolChain.release.787846084/A.COMPILER.WARNING_FLAGS.ALL/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.COMPILER.WARNING_FLAGS.ALL/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.COMPILER.WARNING_FLAGS.ALL/value=-Wall -Wextra +environment/project/io.sloeber.core.toolChain.release.787846084/A.COMPILER.WARNING_FLAGS/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.COMPILER.WARNING_FLAGS/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.COMPILER.WARNING_FLAGS/value=${A.COMPILER.WARNING_FLAGS.ALL} +environment/project/io.sloeber.core.toolChain.release.787846084/A.ESP8266.NETWORK.UPLOAD.TOOL/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.ESP8266.NETWORK.UPLOAD.TOOL/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.ESP8266.NETWORK.UPLOAD.TOOL/value=esp8266OTA +environment/project/io.sloeber.core.toolChain.release.787846084/A.EXTRA.TIME.DTS/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.EXTRA.TIME.DTS/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.EXTRA.TIME.DTS/value=3600 +environment/project/io.sloeber.core.toolChain.release.787846084/A.EXTRA.TIME.LOCAL/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.EXTRA.TIME.LOCAL/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.EXTRA.TIME.LOCAL/value=1536406810 +environment/project/io.sloeber.core.toolChain.release.787846084/A.EXTRA.TIME.UTC/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.EXTRA.TIME.UTC/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.EXTRA.TIME.UTC/value=1536399610 +environment/project/io.sloeber.core.toolChain.release.787846084/A.EXTRA.TIME.ZONE/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.EXTRA.TIME.ZONE/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.EXTRA.TIME.ZONE/value=3600 +environment/project/io.sloeber.core.toolChain.release.787846084/A.JANTJE.CORE.REFERENCED.PLATFORM/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.JANTJE.CORE.REFERENCED.PLATFORM/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.JANTJE.CORE.REFERENCED.PLATFORM/value=${JANTJE.SELECTED.PLATFORM} +environment/project/io.sloeber.core.toolChain.release.787846084/A.JANTJE.USED.BOARDS_FILE/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.JANTJE.USED.BOARDS_FILE/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.JANTJE.USED.BOARDS_FILE/value=${JANTJE.BOARDS_FILE} +environment/project/io.sloeber.core.toolChain.release.787846084/A.JANTJE.VARIANT.REFERENCED.PLATFORM/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.JANTJE.VARIANT.REFERENCED.PLATFORM/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.JANTJE.VARIANT.REFERENCED.PLATFORM/value=${JANTJE.SELECTED.PLATFORM} +environment/project/io.sloeber.core.toolChain.release.787846084/A.NAME/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.NAME/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.NAME/value=Teensy 3.6 +environment/project/io.sloeber.core.toolChain.release.787846084/A.PACKAGES/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.PACKAGES/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.PACKAGES/value=${eclipse_home}/arduinoPlugin/packages +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.AR.PATTERN.1/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.AR.PATTERN.1/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.AR.PATTERN.1/value="C\:/Program Files (x86)/Arduino/hardware/teensy/../tools/arm/bin/arm-none-eabi-gcc-ar" rcs +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.AR.PATTERN.2/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.AR.PATTERN.2/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.AR.PATTERN.2/value=\ +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.AR.PATTERN.3/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.AR.PATTERN.3/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.AR.PATTERN.3/value= +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.AR.PATTERN/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.AR.PATTERN/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.AR.PATTERN/value="${A.COMPILER.PATH}${A.BUILD.TOOLCHAIN}${A.BUILD.COMMAND.AR}" rcs "${A.ARCHIVE_FILE_PATH}" "${A.OBJECT_FILE}" +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.COMBINE.PATTERN.1/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.COMBINE.PATTERN.1/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.COMBINE.PATTERN.1/value="C\:/Program Files (x86)/Arduino/hardware/teensy/../tools/arm/bin/arm-none-eabi-gcc" -O3 -flto -fno-fat-lto-objects -Wl,--gc-sections,--relax,--defsym\=__rtc_localtime\=1536406810 "-TC\:/Program Files (x86)/Arduino/hardware/teensy/avr/cores/teensy3/mk66fx1m0.ld" -lstdc++ -fuse-linker-plugin -mthumb -mcpu\=cortex-m4 -mfloat-abi\=hard -mfpu\=fpv4-sp-d16 -fsingle-precision-constant -o "C\:\\Sandbox\\DIY-Thermocam\\Firmware/Release/DIYThermocam.elf" +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.COMBINE.PATTERN.2/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.COMBINE.PATTERN.2/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.COMBINE.PATTERN.2/value=\ +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.COMBINE.PATTERN.3/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.COMBINE.PATTERN.3/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.COMBINE.PATTERN.3/value=\ "-LC\:\\Sandbox\\DIY-Thermocam\\Firmware/Release" -larm_cortexM4lf_math -lm +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.COMBINE.PATTERN/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.COMBINE.PATTERN/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.COMBINE.PATTERN/value="${A.COMPILER.PATH}${A.BUILD.TOOLCHAIN}${A.BUILD.COMMAND.GCC}" ${A.BUILD.FLAGS.OPTIMIZE} ${A.BUILD.FLAGS.LD} ${A.BUILD.FLAGS.LDSPECS} ${A.BUILD.FLAGS.CPU} -o "${A.BUILD.PATH}/${A.BUILD.PROJECT_NAME}.elf" ${A.OBJECT_FILES} "${A.BUILD.PATH}/${A.ARCHIVE_FILE}" "-L${A.BUILD.PATH}" ${A.BUILD.FLAGS.LIBS} +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.O.PATTERN.1/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.O.PATTERN.1/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.O.PATTERN.1/value="C\:/Program Files (x86)/Arduino/hardware/teensy/../tools/arm/bin/arm-none-eabi-gcc" -c -O3 -flto -fno-fat-lto-objects -g -Wall -ffunction-sections -fdata-sections -nostdlib -mthumb -mcpu\=cortex-m4 -mfloat-abi\=hard -mfpu\=fpv4-sp-d16 -fsingle-precision-constant -D__MK66FX1M0__ -DTEENSYDUINO\=143 -DARDUINO\=10802 -DF_CPU\=240000000 -DUSB_SERIAL -DLAYOUT_US_INTERNATIONAL +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.O.PATTERN.2/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.O.PATTERN.2/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.O.PATTERN.2/value=\ -o +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.O.PATTERN.3/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.O.PATTERN.3/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.O.PATTERN.3/value= +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.O.PATTERN/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.O.PATTERN/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.C.O.PATTERN/value="${A.COMPILER.PATH}${A.BUILD.TOOLCHAIN}${A.BUILD.COMMAND.GCC}" -c ${A.BUILD.FLAGS.OPTIMIZE} ${A.BUILD.FLAGS.COMMON} ${A.BUILD.FLAGS.DEP} ${A.BUILD.FLAGS.C} ${A.BUILD.FLAGS.CPU} ${A.BUILD.FLAGS.DEFS} -DARDUINO\=${A.RUNTIME.IDE.VERSION} -DF_CPU\=${A.BUILD.FCPU} -D${A.BUILD.USBTYPE} -DLAYOUT_${A.BUILD.KEYLAYOUT} ${A.INCLUDES} "${A.SOURCE_FILE}" -o "${A.OBJECT_FILE}" +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.CPP.O.PATTERN.1/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.CPP.O.PATTERN.1/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.CPP.O.PATTERN.1/value="C\:/Program Files (x86)/Arduino/hardware/teensy/../tools/arm/bin/arm-none-eabi-g++" -c -O3 -flto -fno-fat-lto-objects -g -Wall -ffunction-sections -fdata-sections -nostdlib -fno-exceptions -felide-constructors -std\=gnu++14 -Wno-error\=narrowing -fno-rtti -mthumb -mcpu\=cortex-m4 -mfloat-abi\=hard -mfpu\=fpv4-sp-d16 -fsingle-precision-constant -D__MK66FX1M0__ -DTEENSYDUINO\=143 -DARDUINO\=10802 -DF_CPU\=240000000 -DUSB_SERIAL -DLAYOUT_US_INTERNATIONAL "-IC\:\\Sandbox\\DIY-Thermocam\\Firmware/Release/pch" +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.CPP.O.PATTERN.2/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.CPP.O.PATTERN.2/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.CPP.O.PATTERN.2/value=\ -o +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.CPP.O.PATTERN.3/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.CPP.O.PATTERN.3/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.CPP.O.PATTERN.3/value= +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.CPP.O.PATTERN/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.CPP.O.PATTERN/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.CPP.O.PATTERN/value="${A.COMPILER.PATH}${A.BUILD.TOOLCHAIN}${A.BUILD.COMMAND.G++}" -c ${A.BUILD.FLAGS.OPTIMIZE} ${A.BUILD.FLAGS.COMMON} ${A.BUILD.FLAGS.DEP} ${A.BUILD.FLAGS.CPP} ${A.BUILD.FLAGS.CPU} ${A.BUILD.FLAGS.DEFS} -DARDUINO\=${A.RUNTIME.IDE.VERSION} -DF_CPU\=${A.BUILD.FCPU} -D${A.BUILD.USBTYPE} -DLAYOUT_${A.BUILD.KEYLAYOUT} "-I${A.BUILD.PATH}/pch" ${A.INCLUDES} "${A.SOURCE_FILE}" -o "${A.OBJECT_FILE}" +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.ELFPATCH.PATTERN/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.ELFPATCH.PATTERN/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.ELFPATCH.PATTERN/value="${A.COMPILER.PATH}/hardware/tools/${A.BUILD.ELFPATCH}" -mmcu\=${A.BUILD.MCU} "${A.BUILD.PATH}/${A.BUILD.PROJECT_NAME}.elf" "${A.SKETCH_PATH}/disk" +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.HOOKS.POSTBUILD.1.PATTERN/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.HOOKS.POSTBUILD.1.PATTERN/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.HOOKS.POSTBUILD.1.PATTERN/value="${A.COMPILER.PATH}stdout_redirect" "${A.BUILD.PATH}/${A.BUILD.PROJECT_NAME}.lst" "${A.COMPILER.PATH}${A.BUILD.TOOLCHAIN}${A.BUILD.COMMAND.OBJDUMP}" -d -S -C "${A.BUILD.PATH}/${A.BUILD.PROJECT_NAME}.elf" +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.HOOKS.POSTBUILD.2.PATTERN/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.HOOKS.POSTBUILD.2.PATTERN/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.HOOKS.POSTBUILD.2.PATTERN/value="${A.COMPILER.PATH}stdout_redirect" "${A.BUILD.PATH}/${A.BUILD.PROJECT_NAME}.sym" "${A.COMPILER.PATH}${A.BUILD.TOOLCHAIN}${A.BUILD.COMMAND.OBJDUMP}" -t -C "${A.BUILD.PATH}/${A.BUILD.PROJECT_NAME}.elf" +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.HOOKS.POSTBUILD.3.PATTERN/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.HOOKS.POSTBUILD.3.PATTERN/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.HOOKS.POSTBUILD.3.PATTERN/value="${A.COMPILER.PATH}teensy_post_compile" "-file\=${A.BUILD.PROJECT_NAME}" "-path\=${A.BUILD.PATH}" "-tools\=${A.COMPILER.PATH}" "-board\=${A.BUILD.BOARD}" +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.HOOKS.SKETCH.PREBUILD.1.PATTERN/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.HOOKS.SKETCH.PREBUILD.1.PATTERN/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.HOOKS.SKETCH.PREBUILD.1.PATTERN/value="${A.COMPILER.PATH}precompile_helper" "${A.RUNTIME.PLATFORM.PATH}/cores/${A.BUILD.CORE}" "${A.BUILD.PATH}" "${A.COMPILER.PATH}${A.BUILD.TOOLCHAIN}${A.BUILD.COMMAND.G++}" -x c++-header ${A.BUILD.FLAGS.OPTIMIZE} ${A.BUILD.FLAGS.COMMON} ${A.BUILD.FLAGS.DEP} ${A.BUILD.FLAGS.CPP} ${A.BUILD.FLAGS.CPU} ${A.BUILD.FLAGS.DEFS} -DARDUINO\=${A.RUNTIME.IDE.VERSION} -DF_CPU\=${A.BUILD.FCPU} -D${A.BUILD.USBTYPE} -DLAYOUT_${A.BUILD.KEYLAYOUT} "-I${A.RUNTIME.PLATFORM.PATH}/cores/${A.BUILD.CORE}" "${A.BUILD.PATH}/pch/Arduino.h" -o "${A.BUILD.PATH}/pch/Arduino.h.gch" +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OBJCOPY.EEP.PATTERN.1/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OBJCOPY.EEP.PATTERN.1/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OBJCOPY.EEP.PATTERN.1/value="C\:/Program Files (x86)/Arduino/hardware/teensy/../tools/arm/bin/arm-none-eabi-objcopy" -O ihex -j .eeprom --set-section-flags\=.eeprom\=alloc,load --no-change-warnings --change-section-lma .eeprom\=0 "C\:\\Sandbox\\DIY-Thermocam\\Firmware/Release/DIYThermocam.elf" "C\:\\Sandbox\\DIY-Thermocam\\Firmware/Release/DIYThermocam.eep" +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OBJCOPY.EEP.PATTERN/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OBJCOPY.EEP.PATTERN/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OBJCOPY.EEP.PATTERN/value="${A.COMPILER.PATH}${A.BUILD.TOOLCHAIN}${A.BUILD.COMMAND.OBJCOPY}" ${A.COMPILER.OBJCOPY.EEP.FLAGS} "${A.BUILD.PATH}/${A.BUILD.PROJECT_NAME}.elf" "${A.BUILD.PATH}/${A.BUILD.PROJECT_NAME}.eep" +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OBJCOPY.HEX.PATTERN.1/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OBJCOPY.HEX.PATTERN.1/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OBJCOPY.HEX.PATTERN.1/value="C\:/Program Files (x86)/Arduino/hardware/teensy/../tools/arm/bin/arm-none-eabi-objcopy" -O ihex -R .eeprom "C\:\\Sandbox\\DIY-Thermocam\\Firmware/Release/DIYThermocam.elf" "C\:\\Sandbox\\DIY-Thermocam\\Firmware/Release/DIYThermocam.hex" +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OBJCOPY.HEX.PATTERN/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OBJCOPY.HEX.PATTERN/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OBJCOPY.HEX.PATTERN/value="${A.COMPILER.PATH}${A.BUILD.TOOLCHAIN}${A.BUILD.COMMAND.OBJCOPY}" ${A.COMPILER.ELF2HEX.FLAGS} "${A.BUILD.PATH}/${A.BUILD.PROJECT_NAME}.elf" "${A.BUILD.PATH}/${A.BUILD.PROJECT_NAME}.hex" +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OUTPUT.SAVE_FILE/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OUTPUT.SAVE_FILE/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OUTPUT.SAVE_FILE/value=${A.BUILD.PROJECT_NAME}.${A.BUILD.BOARD}.hex +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OUTPUT.SAVE_FILE2/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OUTPUT.SAVE_FILE2/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OUTPUT.SAVE_FILE2/value=${A.BUILD.PROJECT_NAME}.elf +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OUTPUT.TMP_FILE/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OUTPUT.TMP_FILE/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OUTPUT.TMP_FILE/value=${A.BUILD.PROJECT_NAME}.hex +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OUTPUT.TMP_FILE2/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OUTPUT.TMP_FILE2/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.OUTPUT.TMP_FILE2/value=${A.BUILD.PROJECT_NAME}.elf +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.PREPROC.INCLUDES/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.PREPROC.INCLUDES/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.PREPROC.INCLUDES/value="${A.COMPILER.PATH}${A.BUILD.TOOLCHAIN}${A.BUILD.COMMAND.G++}" -M -MG -MP -x c++ -w ${A.BUILD.FLAGS.CPP} ${A.BUILD.FLAGS.CPU} ${A.BUILD.FLAGS.DEFS} -DARDUINO\=${A.RUNTIME.IDE.VERSION} -DF_CPU\=${A.BUILD.FCPU} -D${A.BUILD.USBTYPE} -DLAYOUT_${A.BUILD.KEYLAYOUT} ${A.INCLUDES} "${A.SOURCE_FILE}" +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.PREPROC.MACROS/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.PREPROC.MACROS/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.PREPROC.MACROS/value="${A.COMPILER.PATH}${A.BUILD.TOOLCHAIN}${A.BUILD.COMMAND.G++}" -E -CC -x c++ -w ${A.COMPILER.CPP.FLAGS} ${A.BUILD.FLAGS.COMMON} ${A.BUILD.FLAGS.CPP} ${A.BUILD.FLAGS.CPU} ${A.BUILD.FLAGS.DEFS} -DARDUINO\=${A.RUNTIME.IDE.VERSION} -DF_CPU\=${A.BUILD.FCPU} -D${A.BUILD.USBTYPE} -DLAYOUT_${A.BUILD.KEYLAYOUT} ${A.INCLUDES} "${A.SOURCE_FILE}" -o "${A.PREPROCESSED_FILE_PATH}" +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.S.O.PATTERN.1/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.S.O.PATTERN.1/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.S.O.PATTERN.1/value="C\:/Program Files (x86)/Arduino/hardware/teensy/../tools/arm/bin/arm-none-eabi-gcc" -c -O3 -flto -fno-fat-lto-objects -g -Wall -ffunction-sections -fdata-sections -nostdlib -x assembler-with-cpp -mthumb -mcpu\=cortex-m4 -mfloat-abi\=hard -mfpu\=fpv4-sp-d16 -fsingle-precision-constant -D__MK66FX1M0__ -DTEENSYDUINO\=143 -DARDUINO\=10802 -DF_CPU\=240000000 -DUSB_SERIAL -DLAYOUT_US_INTERNATIONAL +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.S.O.PATTERN.2/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.S.O.PATTERN.2/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.S.O.PATTERN.2/value=\ -o +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.S.O.PATTERN.3/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.S.O.PATTERN.3/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.S.O.PATTERN.3/value= +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.S.O.PATTERN/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.S.O.PATTERN/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.S.O.PATTERN/value="${A.COMPILER.PATH}${A.BUILD.TOOLCHAIN}${A.BUILD.COMMAND.GCC}" -c ${A.BUILD.FLAGS.OPTIMIZE} ${A.BUILD.FLAGS.COMMON} ${A.BUILD.FLAGS.DEP} ${A.BUILD.FLAGS.S} ${A.BUILD.FLAGS.CPU} ${A.BUILD.FLAGS.DEFS} -DARDUINO\=${A.RUNTIME.IDE.VERSION} -DF_CPU\=${A.BUILD.FCPU} -D${A.BUILD.USBTYPE} -DLAYOUT_${A.BUILD.KEYLAYOUT} ${A.INCLUDES} "${A.SOURCE_FILE}" -o "${A.OBJECT_FILE}" +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.SIZE.PATTERN.1/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.SIZE.PATTERN.1/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.SIZE.PATTERN.1/value="C\:/Program Files (x86)/Arduino/hardware/teensy/../tools/arm/bin/arm-none-eabi-size" -A "C\:\\Sandbox\\DIY-Thermocam\\Firmware/Release/DIYThermocam.elf" +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.SIZE.PATTERN/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.SIZE.PATTERN/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.SIZE.PATTERN/value="${A.COMPILER.PATH}${A.BUILD.TOOLCHAIN}${A.BUILD.COMMAND.SIZE}" -A "${A.BUILD.PATH}/${A.BUILD.PROJECT_NAME}.elf" +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.SIZE.REGEX.DATA/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.SIZE.REGEX.DATA/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.SIZE.REGEX.DATA/value=^(?\:\\.usbdescriptortable|\\.dmabuffers|\\.usbbuffers|\\.data|\\.bss|\\.noinit)\\s+([0-9]+).* +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.SIZE.REGEX.EEPROM/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.SIZE.REGEX.EEPROM/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.SIZE.REGEX.EEPROM/value=^(?\:\\.eeprom)\\s+([0-9]+).* +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.SIZE.REGEX/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.SIZE.REGEX/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RECIPE.SIZE.REGEX/value=^(?\:\\.text|\\.data|\\.bootloader)\\s+([0-9]+).* +environment/project/io.sloeber.core.toolChain.release.787846084/A.REWRITING/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.REWRITING/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.REWRITING/value=disabled +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.HARDWARE.PATH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.HARDWARE.PATH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.HARDWARE.PATH/value=C\:/Program Files (x86)/Arduino/hardware/teensy +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.IDE.VERSION/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.IDE.VERSION/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.IDE.VERSION/value=10802 +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.PLATFORM.PATH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.PLATFORM.PATH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.PLATFORM.PATH/value=C\:/Program Files (x86)/Arduino/hardware/teensy/avr +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.REFERENCED.PLATFORM.PATH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.REFERENCED.PLATFORM.PATH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.REFERENCED.PLATFORM.PATH/value=C\:/Program Files (x86)/Arduino/hardware/teensy/avr +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.ARDUINOOTA-1.1.1.PATH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.ARDUINOOTA-1.1.1.PATH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.ARDUINOOTA-1.1.1.PATH/value=${eclipse_home}/arduinoPlugin/packages/arduino/tools/arduinoOTA/1.1.1 +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.ARDUINOOTA.PATH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.ARDUINOOTA.PATH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.ARDUINOOTA.PATH/value=${eclipse_home}/arduinoPlugin/packages/arduino/tools/arduinoOTA/1.1.1 +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.ARDUINOOTA1.1.1.PATH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.ARDUINOOTA1.1.1.PATH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.ARDUINOOTA1.1.1.PATH/value=${eclipse_home}/arduinoPlugin/packages/arduino/tools/arduinoOTA/1.1.1 +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.AVR-GCC-4.9.2-ATMEL3.5.4-ARDUINO2.PATH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.AVR-GCC-4.9.2-ATMEL3.5.4-ARDUINO2.PATH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.AVR-GCC-4.9.2-ATMEL3.5.4-ARDUINO2.PATH/value=${eclipse_home}/arduinoPlugin/packages/arduino/tools/avr-gcc/4.9.2-atmel3.5.4-arduino2 +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.AVR-GCC.PATH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.AVR-GCC.PATH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.AVR-GCC.PATH/value=${eclipse_home}/arduinoPlugin/packages/arduino/tools/avr-gcc/4.9.2-atmel3.5.4-arduino2 +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.AVR-GCC4.9.2-ATMEL3.5.4-ARDUINO2.PATH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.AVR-GCC4.9.2-ATMEL3.5.4-ARDUINO2.PATH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.AVR-GCC4.9.2-ATMEL3.5.4-ARDUINO2.PATH/value=${eclipse_home}/arduinoPlugin/packages/arduino/tools/avr-gcc/4.9.2-atmel3.5.4-arduino2 +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.AVRDUDE-6.3.0-ARDUINO9.PATH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.AVRDUDE-6.3.0-ARDUINO9.PATH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.AVRDUDE-6.3.0-ARDUINO9.PATH/value=${eclipse_home}/arduinoPlugin/packages/arduino/tools/avrdude/6.3.0-arduino9 +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.AVRDUDE.PATH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.AVRDUDE.PATH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.AVRDUDE.PATH/value=${eclipse_home}/arduinoPlugin/packages/arduino/tools/avrdude/6.3.0-arduino9 +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.AVRDUDE6.3.0-ARDUINO9.PATH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.AVRDUDE6.3.0-ARDUINO9.PATH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.RUNTIME.TOOLS.AVRDUDE6.3.0-ARDUINO9.PATH/value=${eclipse_home}/arduinoPlugin/packages/arduino/tools/avrdude/6.3.0-arduino9 +environment/project/io.sloeber.core.toolChain.release.787846084/A.SERIAL.PORT.FILE/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.SERIAL.PORT.FILE/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.SERIAL.PORT.FILE/value=${A.SERIAL.PORT} +environment/project/io.sloeber.core.toolChain.release.787846084/A.SERIAL.PORT/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.SERIAL.PORT/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.SERIAL.PORT/value=${JANTJE.COM_PORT} +environment/project/io.sloeber.core.toolChain.release.787846084/A.SERIAL.RESTART_CMD/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.SERIAL.RESTART_CMD/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.SERIAL.RESTART_CMD/value=false +environment/project/io.sloeber.core.toolChain.release.787846084/A.SOFTWARE/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.SOFTWARE/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.SOFTWARE/value=ARDUINO +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.ARDUINO-PREPROCESSOR.CMD.PATH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.ARDUINO-PREPROCESSOR.CMD.PATH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.ARDUINO-PREPROCESSOR.CMD.PATH/value=${A.TOOLS.ARDUINO-PREPROCESSOR.PATH}/arduino-preprocessor +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.ARDUINO-PREPROCESSOR.PATH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.ARDUINO-PREPROCESSOR.PATH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.ARDUINO-PREPROCESSOR.PATH/value=${A.TOOLS.ARDUINO-PREPROCESSOR.RUNTIME.TOOLS.ARDUINO-PREPROCESSOR.PATH} +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.ARDUINO-PREPROCESSOR.PATTERN/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.ARDUINO-PREPROCESSOR.PATTERN/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.ARDUINO-PREPROCESSOR.PATTERN/value="${A.TOOLS.ARDUINO-PREPROCESSOR.CMD.PATH}" "${A.TOOLS.ARDUINO-PREPROCESSOR.SOURCE_FILE}" "${A.TOOLS.ARDUINO-PREPROCESSOR.CODECOMPLETE}" -- -std\=gnu++14 +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.AVRDUDE_REMOTE.UPLOAD.VERBOSE/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.AVRDUDE_REMOTE.UPLOAD.VERBOSE/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.AVRDUDE_REMOTE.UPLOAD.VERBOSE/value=-v +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.ESP8266OTA.UPLOAD.PATTERN/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.ESP8266OTA.UPLOAD.PATTERN/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.ESP8266OTA.UPLOAD.PATTERN/value=${A.TOOLS.ESP8266OTA.TOOLS.ESPTOOL.UPLOAD.NETWORK_PATTERN} +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.ESP8266OTA/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.ESP8266OTA/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.ESP8266OTA/value=${A.TOOLS.ESP8266OTA.TOOLS.ESPTOOL.NETWORK_CMD} +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.ESPTOOL.NETWORK.PASSWORD/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.ESPTOOL.NETWORK.PASSWORD/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.ESPTOOL.NETWORK.PASSWORD/value=${A.TOOLS.ESPTOOL.NETWORK.AUTH} +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.TEENSYLOADER.CMD.PATH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.TEENSYLOADER.CMD.PATH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.TEENSYLOADER.CMD.PATH/value=${A.RUNTIME.HARDWARE.PATH}/../tools +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.TEENSYLOADER.UPLOAD.PARAMS.QUIET/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.TEENSYLOADER.UPLOAD.PARAMS.QUIET/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.TEENSYLOADER.UPLOAD.PARAMS.QUIET/value= +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.TEENSYLOADER.UPLOAD.PARAMS.VERBOSE/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.TEENSYLOADER.UPLOAD.PARAMS.VERBOSE/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.TEENSYLOADER.UPLOAD.PARAMS.VERBOSE/value=-verbose +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.TEENSYLOADER.UPLOAD.PATTERN/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.TEENSYLOADER.UPLOAD.PATTERN/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.TOOLS.TEENSYLOADER.UPLOAD.PATTERN/value="${A.TOOLS.TEENSYLOADER.CMD.PATH}/teensy_post_compile" "-file\=${A.BUILD.PROJECT_NAME}" "-path\=${A.BUILD.PATH}" "-tools\=${A.TOOLS.TEENSYLOADER.CMD.PATH}" "-board\=${A.BUILD.BOARD}" -reboot "-port\=${A.SERIAL.PORT}" "-portlabel\=${A.TOOLS.TEENSYLOADER.SERIAL.PORT.LABEL}" "-portprotocol\=${A.TOOLS.TEENSYLOADER.SERIAL.PORT.PROTOCOL}" +environment/project/io.sloeber.core.toolChain.release.787846084/A.UPLOAD.ALTID/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.UPLOAD.ALTID/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.UPLOAD.ALTID/value=no_altID +environment/project/io.sloeber.core.toolChain.release.787846084/A.UPLOAD.MAXIMUM_DATA_SIZE/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.UPLOAD.MAXIMUM_DATA_SIZE/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.UPLOAD.MAXIMUM_DATA_SIZE/value=262144 +environment/project/io.sloeber.core.toolChain.release.787846084/A.UPLOAD.MAXIMUM_SIZE/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.UPLOAD.MAXIMUM_SIZE/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.UPLOAD.MAXIMUM_SIZE/value=1048576 +environment/project/io.sloeber.core.toolChain.release.787846084/A.UPLOAD.PROTOCOL/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.UPLOAD.PROTOCOL/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.UPLOAD.PROTOCOL/value=halfkay +environment/project/io.sloeber.core.toolChain.release.787846084/A.UPLOAD.SPEED/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.UPLOAD.SPEED/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.UPLOAD.SPEED/value=19200 +environment/project/io.sloeber.core.toolChain.release.787846084/A.UPLOAD.TOOL/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.UPLOAD.TOOL/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.UPLOAD.TOOL/value=teensyloader +environment/project/io.sloeber.core.toolChain.release.787846084/A.UPLOAD.USBID/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.UPLOAD.USBID/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.UPLOAD.USBID/value=no_altID +environment/project/io.sloeber.core.toolChain.release.787846084/A.VERSION/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/A.VERSION/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/A.VERSION/value=1.8.5 +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.ARCHITECTURE_ID/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.ARCHITECTURE_ID/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.ARCHITECTURE_ID/value=avr +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.BOARDS_FILE/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.BOARDS_FILE/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.BOARDS_FILE/value=C\:\\Program Files (x86)\\Arduino\\hardware\\teensy\\avr\\boards.txt +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.BOARD_ID/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.BOARD_ID/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.BOARD_ID/value=teensy36 +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.BOARD_NAME/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.BOARD_NAME/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.BOARD_NAME/value=Teensy 3.6 +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.COM_PORT/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.COM_PORT/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.COM_PORT/value=COM10 +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.ECLIPSE_LOCATION/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.ECLIPSE_LOCATION/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.ECLIPSE_LOCATION/value=C\:\\Dev\\sloeber\\ +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.EXTRA.ALL/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.EXTRA.ALL/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.EXTRA.ALL/value= +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.EXTRA.ARCHIVE/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.EXTRA.ARCHIVE/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.EXTRA.ARCHIVE/value= +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.EXTRA.ASSEMBLY/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.EXTRA.ASSEMBLY/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.EXTRA.ASSEMBLY/value= +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.EXTRA.C.COMPILE/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.EXTRA.C.COMPILE/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.EXTRA.C.COMPILE/value= +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.EXTRA.COMPILE/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.EXTRA.COMPILE/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.EXTRA.COMPILE/value= +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.EXTRA.CPP.COMPILE/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.EXTRA.CPP.COMPILE/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.EXTRA.CPP.COMPILE/value= +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.EXTRA.LINK/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.EXTRA.LINK/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.EXTRA.LINK/value= +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.MAKE_LOCATION/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.MAKE_LOCATION/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.MAKE_LOCATION/value=${eclipse_home}/arduinoPlugin/tools/make/ +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.MENU/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.MENU/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.MENU/value=keys\=usint\nopt\=o3lto\nspeed\=240\nusb\=serial +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.OBJCOPY/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.OBJCOPY/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.OBJCOPY/value=${A.RECIPE.OBJCOPY.EEP.PATTERN}\n\t${A.RECIPE.OBJCOPY.HEX.PATTERN} +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.OS_NAME/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.OS_NAME/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.OS_NAME/value=win32 +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.PACKAGE_ID/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.PACKAGE_ID/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.PACKAGE_ID/value=teensy +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.PROJECT_NAME/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.PROJECT_NAME/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.PROJECT_NAME/value=DIYThermocam +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.SELECTED.PLATFORM/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.SELECTED.PLATFORM/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.SELECTED.PLATFORM/value=C\:/Program Files (x86)/Arduino/hardware/teensy/avr +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.SIZE.SWITCH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.SIZE.SWITCH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.SIZE.SWITCH/value=${A.RECIPE.SIZE.PATTERN} +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.UPLOAD/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.UPLOAD/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.UPLOAD/value=Default +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.WARNING_LEVEL/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.WARNING_LEVEL/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.WARNING_LEVEL/value=true +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.WORKSPACE_LOCATION/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.WORKSPACE_LOCATION/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/JANTJE.WORKSPACE_LOCATION/value=C\:\\Users\\Max\\Documents\\sloeber-workspace +environment/project/io.sloeber.core.toolChain.release.787846084/PATH/delimiter=; +environment/project/io.sloeber.core.toolChain.release.787846084/PATH/operation=replace +environment/project/io.sloeber.core.toolChain.release.787846084/PATH/value=${A.COMPILER.PATH}${PathDelimiter}${A.BUILD.GENERIC.PATH}${PathDelimiter}${SystemRoot}\\system32${PathDelimiter}${SystemRoot}${PathDelimiter}${SystemRoot}\\system32\\Wbem${PathDelimiter}${sloeber_path_extension} +environment/project/io.sloeber.core.toolChain.release.787846084/append=true +environment/project/io.sloeber.core.toolChain.release.787846084/appendContributed=true diff --git a/Firmware_V2/.settings/org.eclipse.core.resources.prefs b/Firmware_V2/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..bc49b4f --- /dev/null +++ b/Firmware_V2/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding//src/hardware/camera/ov2640regs.cpp=UTF-8 diff --git a/Firmware_V2/LICENSE b/Firmware_V2/LICENSE new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/Firmware_V2/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Firmware_V2/README.MD b/Firmware_V2/README.MD new file mode 100644 index 0000000..bf2567a --- /dev/null +++ b/Firmware_V2/README.MD @@ -0,0 +1,33 @@ +# DIY-Thermocam V2 Firmware # + +GNU General Public License v3.0, copyright by Max Ritter + +**The following hardware is supported:** + +- **DIY-Thermocam V2** (2017) - Lepton2.0 / Lepton2.5 / Lepton3.0 / Lepton3.5 + +---------- + +**THIS GUIDE IS ONLY REQUIRED, IF YOU WANT TO MAKE CHANGES TO THE FIRMWARE ON YOUR OWN!** + +If you just want to flash the newest version of the firmware to your device, follow the Firmware Update Guide in the Document section and use one of the pre-compiled versions (*.hex) from **[here](https://github.com/maxritter/diythermocam_firmware/releases)**. + +---------- + +This guide should work on **all common operating systems** (**Windows, Linux & Mac OSX**). + +**Download** and **install** the following programs: + +1. **[Arduino 1.8.6+](https://www.arduino.cc/en/Main/Software)** +2. **[Teensyduino 1.43+](https://www.pjrc.com/teensy/td_download.html)** +3. **[Sloeber Eclipse](https://github.com/Sloeber/arduino-eclipse-plugin/releases/tag/4_2)** + +Then **clone this repo** to a path without empty spaces (f. ex. "C:\Sandbox\DIYThermocam_Firmware\") with `git clone https://github.com/maxritter/diythermocam_firmware.git`. + +Now **start Sloeber Eclipse**. + +Go to Windows -> Preferences -> Arduino and **set the Private Library Path** to the folder of your Arduino library installation (f. ex. "C:\Program Files (x86)\Arduino\libraries"). Also **set the Private Hardware Path** to the path of your Arduino hardware installation (f. ex. "C:\Program Files (x86)\Arduino\hardware"). + +**Import the project** with File -> Import -> General -> Existing Projects into Workspace, select the root directory of this repo and click Finish. + +Now you should be able to build the project with Arduino -> Verify and upload it to the Thermocam with Arduino -> Upload Sketch. Have fun! diff --git a/Firmware_V2/core/.keep b/Firmware_V2/core/.keep new file mode 100644 index 0000000..e69de29 diff --git a/Firmware_V2/src/general/colorschemes.cpp b/Firmware_V2/src/general/colorschemes.cpp new file mode 100644 index 0000000..07319c6 --- /dev/null +++ b/Firmware_V2/src/general/colorschemes.cpp @@ -0,0 +1,132 @@ +/* + * + * COLOR SCHEMES - Contains 19 different color schemes to display the thermal image + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################################# INCLUDES ##################################*/ + +#include +#include + +/*############################# PUBLIC VARIABLES ##############################*/ + +const byte colorMap_arctic[] = { 15, 16, 146, 15, 16, 146, 15, 15, 153, 15, 15, 153, 15, 15, 159, 15, 15, 159, 16, 15, 167, 16, 15, 167, 15, 15, 175, 15, 15, 175, 16, 15, 182, 16, 15, 182, 16, 16, 190, 16, 16, 190, 14, 15, 197, 14, 15, 197, 15, 15, 205, 15, 15, 205, 15, 15, 211, 15, 15, 211, 16, 15, 219, 16, 15, 219, 16, 15, 227, 16, 15, 227, 16, 18, 239, 16, 18, 239, 16, 25, 240, 16, 25, 240, 15, 34, 239, 15, 34, 239, 15, 44, 238, 15, 44, 238, 14, 54, 239, 14, 54, 239, 14, 63, 239, 14, 63, 239, 14, 74, 238, 14, 74, 238, 17, 82, 238, 17, 82, 238, 19, 92, 237, 19, 92, 237, 22, 102, 239, 22, 102, 239, 24, 111, 238, 24, 111, 238, 27, 120, 237, 27, 120, 237, 28, 131, 237, 28, 131, 237, 32, 140, 237, 32, 140, 237, 34, 150, 237, 34, 150, 237, 36, 160, 236, 36, 160, 236, 39, 168, 237, 39, 168, 237, 42, 179, 237, 42, 179, 237, 44, 188, 236, 44, 188, 236, 46, 197, 236, 46, 197, 236, 49, 208, 236, 49, 208, 236, 52, 217, 235, 52, 217, 235, 54, 227, 232, 54, 227, 232, 57, 227, 230, 57, 227, 230, 58, 226, 227, 58, 226, 227, 62, 224, 225, 62, 224, 225, 64, 222, 222, 64, 222, 222, 66, 220, 220, 66, 220, 220, 67, 215, 215, 67, 215, 215, 69, 209, 210, 69, 209, 210, 73, 205, 204, 73, 205, 204, 76, 198, 199, 76, 198, 199, 79, 193, 192, 79, 193, 192, 81, 187, 187, 81, 187, 187, 83, 181, 180, 83, 181, 180, 87, 175, 175, 87, 175, 175, 88, 170, 170, 88, 170, 170, 88, 164, 165, 88, 164, 165, 90, 158, 159, 90, 158, 159, 90, 152, 153, 90, 152, 153, 90, 146, 145, 90, 146, 145, 92, 140, 140, 92, 140, 140, 92, 134, 134, 92, 134, 134, 95, 129, 129, 95, 129, 129, 95, 123, 123, 95, 123, 123, 96, 117, 116, 96, 117, 116, 97, 111, 110, 97, 111, 110, 99, 105, 105, 99, 105, 105, 102, 102, 102, 102, 102, 102, 107, 101, 97, 107, 101, 97, 112, 101, 95, 112, 101, 95, 117, 101, 90, 117, 101, 90, 123, 102, 87, 123, 102, 87, 129, 101, 84, 129, 101, 84, 134, 101, 80, 134, 101, 80, 138, 102, 76, 138, 102, 76, 143, 101, 73, 143, 101, 73, 148, 101, 69, 148, 101, 69, 153, 101, 66, 153, 101, 66, 159, 102, 63, 159, 102, 63, 165, 102, 59, 165, 102, 59, 170, 101, 56, 170, 101, 56, 175, 101, 52, 175, 101, 52, 180, 101, 48, 180, 101, 48, 185, 100, 45, 185, 100, 45, 191, 100, 41, 191, 100, 41, 197, 101, 37, 197, 101, 37, 201, 101, 35, 201, 101, 35, 206, 101, 31, 206, 101, 31, 211, 101, 26, 211, 101, 26, 216, 101, 24, 216, 101, 24, 221, 101, 19, 221, 101, 19, 228, 101, 18, 228, 101, 18, 233, 101, 14, 233, 101, 14, 237, 101, 13, 237, 101, 13, 236, 105, 13, 236, 105, 13, 236, 112, 12, 236, 112, 12, 236, 120, 13, 236, 120, 13, 237, 123, 13, 237, 123, 13, 237, 130, 12, 237, 130, 12, 237, 137, 13, 237, 137, 13, 237, 142, 12, 237, 142, 12, 237, 149, 12, 237, 149, 12, 236, 156, 13, 236, 156, 13, 236, 160, 11, 236, 160, 11, 235, 167, 12, 235, 167, 12, 235, 173, 12, 235, 173, 12, 235, 179, 12, 235, 179, 12, 235, 185, 12, 235, 185, 12, 236, 191, 13, 236, 191, 13, 236, 196, 11, 236, 196, 11, 235, 202, 12, 235, 202, 12, 236, 204, 27, 236, 204, 27, 235, 207, 34, 235, 207, 34, 236, 208, 50, 236, 208, 50, 235, 211, 65, 235, 211, 65, 235, 212, 71, 235, 212, 71, 235, 214, 87, 235, 214, 87, 235, 216, 100, 235, 216, 100, 235, 216, 108, 235, 216, 108, 236, 220, 123, 236, 220, 123, 235, 221, 138, 235, 221, 138, 235, 221, 146, 235, 221, 146, 235, 225, 160, 235, 225, 160, 235, 225, 175, 235, 225, 175, 236, 227, 182, 236, 227, 182, 235, 229, 191, 235, 229, 191, 235, 230, 194, 235, 230, 194 }; + +const byte colorMap_blackHot[] = { 235, 235, 235, 234, 234, 234, 233, 233, 233, 232, 232, 232, 231, 231, 231, 230, 230, 230, 229, 229, 229, 228, 228, 228, 227, 227, 227, 226, 226, 226, 225, 225, 225, 224, 224, 224, 223, 223, 223, 222, 222, 222, 221, 221, 221, 220, 220, 220, 219, 219, 219, 218, 218, 218, 217, 217, 217, 216, 216, 216, 215, 215, 215, 214, 214, 214, 213, 213, 213, 212, 212, 212, 211, 211, 211, 210, 210, 210, 209, 209, 209, 209, 209, 209, 208, 208, 208, 207, 207, 207, 206, 206, 206, 205, 205, 205, 204, 204, 204, 203, 203, 203, 202, 202, 202, 201, 201, 201, 200, 200, 200, 199, 199, 199, 198, 198, 198, 197, 197, 197, 196, 196, 196, 195, 195, 195, 194, 194, 194, 193, 193, 193, 192, 192, 192, 191, 191, 191, 190, 190, 190, 189, 189, 189, 188, 188, 188, 187, 187, 187, 186, 186, 186, 185, 185, 185, 184, 184, 184, 183, 183, 183, 182, 182, 182, 181, 181, 181, 180, 180, 180, 179, 179, 179, 178, 178, 178, 177, 177, 177, 176, 176, 176, 175, 175, 175, 174, 174, 174, 173, 173, 173, 172, 172, 172, 171, 171, 171, 170, 170, 170, 169, 169, 169, 168, 168, 168, 167, 167, 167, 166, 166, 166, 165, 165, 165, 164, 164, 164, 163, 163, 163, 162, 162, 162, 161, 161, 161, 160, 160, 160, 159, 159, 159, 158, 158, 158, 157, 157, 157, 156, 156, 156, 155, 155, 155, 154, 154, 154, 154, 154, 154, 153, 153, 153, 152, 152, 152, 151, 151, 151, 150, 150, 150, 149, 149, 149, 148, 148, 148, 147, 147, 147, 146, 146, 146, 145, 145, 145, 144, 144, 144, 143, 143, 143, 142, 142, 142, 141, 141, 141, 140, 140, 140, 139, 139, 139, 138, 138, 138, 137, 137, 137, 136, 136, 136, 135, 135, 135, 134, 134, 134, 133, 133, 133, 132, 132, 132, 131, 131, 131, 130, 130, 130, 129, 129, 129, 128, 128, 128, 127, 127, 127, 126, 126, 126, 125, 125, 125, 124, 124, 124, 123, 123, 123, 122, 122, 122, 121, 121, 121, 120, 120, 120, 119, 119, 119, 118, 118, 118, 117, 117, 117, 116, 116, 116, 115, 115, 115, 114, 114, 114, 113, 113, 113, 112, 112, 112, 111, 111, 111, 110, 110, 110, 109, 109, 109, 108, 108, 108, 107, 107, 107, 106, 106, 106, 105, 105, 105, 104, 104, 104, 103, 103, 103, 102, 102, 102, 101, 101, 101, 100, 100, 100, 99, 99, 99, 99, 99, 99, 98, 98, 98, 97, 97, 97, 96, 96, 96, 95, 95, 95, 94, 94, 94, 93, 93, 93, 92, 92, 92, 91, 91, 91, 90, 90, 90, 89, 89, 89, 88, 88, 88, 87, 87, 87, 86, 86, 86, 85, 85, 85, 84, 84, 84, 83, 83, 83, 82, 82, 82, 81, 81, 81, 80, 80, 80, 79, 79, 79, 78, 78, 78, 77, 77, 77, 76, 76, 76, 75, 75, 75, 74, 74, 74, 73, 73, 73, 72, 72, 72, 71, 71, 71, 70, 70, 70, 69, 69, 69, 68, 68, 68, 67, 67, 67, 66, 66, 66, 65, 65, 65, 64, 64, 64, 63, 63, 63, 62, 62, 62, 61, 61, 61, 60, 60, 60, 59, 59, 59, 58, 58, 58, 57, 57, 57, 56, 56, 56, 55, 55, 55, 54, 54, 54, 53, 53, 53, 52, 52, 52, 51, 51, 51, 50, 50, 50, 49, 49, 49, 48, 48, 48, 47, 47, 47, 46, 46, 46, 45, 45, 45, 44, 44, 44, 44, 44, 44, 43, 43, 43, 42, 42, 42, 41, 41, 41, 40, 40, 40, 39, 39, 39, 38, 38, 38, 37, 37, 37, 36, 36, 36, 35, 35, 35, 34, 34, 34, 33, 33, 33, 32, 32, 32, 31, 31, 31, 30, 30, 30, 29, 29, 29, 28, 28, 28, 27, 27, 27, 26, 26, 26, 25, 25, 25, 24, 24, 24, 23, 23, 23, 22, 22, 22, 21, 21, 21, 20, 20, 20, 19, 19, 19, 18, 18, 18, 17, 17, 17, 16, 16, 16 }; + +const byte colorMap_blueRed[] = { 19, 64, 206, 18, 65, 209, 18, 67, 210, 19, 69, 212, 18, 71, 215, 19, 73, 217, 18, 75, 218, 18, 77, 219, 19, 79, 223, 19, 82, 225, 19, 84, 226, 18, 85, 227, 19, 88, 229, 21, 90, 229, 22, 93, 231, 21, 95, 230, 22, 98, 232, 22, 101, 232, 22, 103, 232, 23, 106, 234, 23, 109, 234, 24, 112, 236, 23, 114, 235, 25, 116, 235, 25, 119, 237, 27, 122, 238, 26, 124, 237, 27, 125, 236, 27, 127, 235, 27, 130, 236, 29, 133, 234, 30, 136, 234, 31, 139, 233, 32, 141, 232, 33, 145, 231, 33, 147, 231, 33, 150, 231, 34, 154, 229, 36, 156, 228, 36, 158, 227, 36, 162, 225, 38, 165, 224, 40, 167, 222, 41, 171, 221, 43, 174, 217, 45, 176, 216, 44, 178, 215, 46, 181, 212, 48, 184, 210, 49, 188, 209, 51, 190, 207, 53, 193, 206, 54, 195, 201, 56, 198, 200, 58, 200, 198, 59, 202, 196, 61, 205, 192, 63, 206, 190, 64, 208, 187, 68, 210, 184, 69, 212, 180, 71, 215, 178, 73, 216, 176, 74, 217, 173, 78, 220, 170, 79, 222, 165, 82, 224, 164, 83, 225, 161, 85, 227, 157, 88, 228, 154, 92, 228, 152, 94, 229, 149, 96, 230, 147, 99, 231, 144, 102, 230, 141, 104, 231, 136, 106, 232, 134, 109, 233, 131, 113, 234, 129, 114, 234, 125, 116, 235, 123, 119, 236, 120, 122, 235, 117, 126, 233, 113, 128, 234, 112, 131, 233, 109, 134, 232, 107, 137, 231, 103, 139, 230, 102, 143, 231, 99, 146, 230, 96, 149, 229, 94, 152, 228, 90, 154, 227, 88, 156, 227, 87, 157, 226, 85, 161, 224, 83, 164, 223, 81, 166, 221, 79, 169, 220, 77, 172, 217, 74, 175, 216, 74, 178, 213, 70, 181, 212, 70, 183, 210, 67, 186, 207, 64, 189, 206, 62, 192, 205, 60, 194, 201, 58, 197, 200, 57, 200, 197, 54, 202, 195, 53, 205, 191, 52, 207, 189, 51, 208, 187, 48, 209, 183, 48, 212, 180, 45, 214, 178, 44, 216, 175, 43, 218, 173, 42, 220, 169, 40, 222, 166, 39, 224, 164, 38, 225, 162, 35, 228, 157, 35, 227, 153, 34, 228, 149, 32, 230, 147, 33, 232, 144, 32, 231, 141, 31, 232, 138, 29, 232, 136, 28, 233, 132, 28, 235, 130, 27, 234, 127, 27, 235, 123, 25, 237, 120, 26, 235, 118, 24, 234, 115, 24, 235, 113, 24, 234, 110, 24, 234, 108, 22, 232, 105, 22, 231, 102, 22, 231, 100, 21, 231, 97, 20, 230, 94, 20, 228, 92, 20, 228, 89, 18, 228, 87, 19, 227, 84, 18, 224, 82, 18, 223, 81, 19, 222, 78, 19, 219, 76, 19, 217, 74, 17, 215, 72, 18, 214, 71, 17, 211, 69, 17, 210, 66, 17, 207, 64, 16, 206, 63, 17, 205, 62, 18, 203, 60, 16, 202, 58, 17, 198, 57, 17, 196, 54, 16, 194, 54, 17, 190, 53, 15, 188, 50, 15, 185, 50, 16, 183, 48, 15, 179, 46, 15, 177, 46, 16, 175, 43, 16, 171, 42, 15, 169, 41, 14, 166, 40, 14, 164, 37, 14, 160, 38, 15, 157, 37, 16, 154, 35, 15, 152, 35, 16, 149, 34, 15, 147, 34, 16, 142, 33, 16, 140, 33, 15, 139, 31, 15, 134, 30, 15, 131, 30, 14, 128, 28, 14, 126, 28, 15 }; + +const byte colorMap_coldest[] = { 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239,15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 45, 45, 45, 46, 46, 46, 47, 47, 47, 48, 48, 48, 49, 49, 49, 50, 50, 50, 51, 51, 51, 52, 52, 52, 53, 53, 53, 54, 54, 54, 55, 55, 55, 56, 56, 56, 57, 57, 57, 58, 58, 58, 59, 59, 59, 60, 60, 60, 61, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 65, 65, 65, 66, 66, 66, 67, 67, 67, 68, 68, 68, 69, 69, 69, 70, 70, 70, 71, 71, 71, 72, 72, 72, 73, 73, 73, 74, 74, 74, 75, 75, 75, 76, 76, 76, 77, 77, 77, 78, 78, 78, 79, 79, 79, 80, 80, 80, 81, 81, 81, 82, 82, 82, 83, 83, 83, 84, 84, 84, 85, 85, 85, 86, 86, 86, 87, 87, 87, 88, 88, 88, 89, 89, 89, 90, 90, 90, 91, 91, 91, 92, 92, 92, 93, 93, 93, 94, 94, 94, 95, 95, 95, 96, 96, 96, 97, 97, 97, 98, 98, 98, 99, 99, 99, 99, 99, 99, 100, 100, 100, 101, 101, 101, 102, 102, 102, 103, 103, 103, 104, 104, 104, 105, 105, 105, 106, 106, 106, 107, 107, 107, 108, 108, 108, 109, 109, 109, 110, 110, 110, 111, 111, 111, 112, 112, 112, 113, 113, 113, 114, 114, 114, 115, 115, 115, 116, 116, 116, 117, 117, 117, 118, 118, 118, 119, 119, 119, 120, 120, 120, 121, 121, 121, 122, 122, 122, 123, 123, 123, 124, 124, 124, 125, 125, 125, 126, 126, 126, 127, 127, 127, 128, 128, 128, 129, 129, 129, 130, 130, 130, 131, 131, 131, 132, 132, 132, 133, 133, 133, 134, 134, 134, 135, 135, 135, 136, 136, 136, 137, 137, 137, 138, 138, 138, 139, 139, 139, 140, 140, 140, 141, 141, 141, 142, 142, 142, 143, 143, 143, 144, 144, 144, 145, 145, 145, 146, 146, 146, 147, 147, 147, 148, 148, 148, 149, 149, 149, 150, 150, 150, 151, 151, 151, 152, 152, 152, 153, 153, 153, 154, 154, 154, 154, 154, 154, 155, 155, 155, 156, 156, 156, 157, 157, 157, 158, 158, 158, 159, 159, 159, 160, 160, 160, 161, 161, 161, 162, 162, 162, 163, 163, 163, 164, 164, 164, 165, 165, 165, 166, 166, 166, 167, 167, 167, 168, 168, 168, 169, 169, 169, 170, 170, 170, 171, 171, 171, 172, 172, 172, 173, 173, 173, 174, 174, 174, 175, 175, 175, 176, 176, 176, 177, 177, 177, 178, 178, 178, 179, 179, 179, 180, 180, 180, 181, 181, 181, 182, 182, 182, 183, 183, 183, 184, 184, 184, 185, 185, 185, 186, 186, 186, 187, 187, 187, 188, 188, 188, 189, 189, 189, 190, 190, 190, 191, 191, 191, 192, 192, 192, 193, 193, 193, 194, 194, 194, 195, 195, 195, 196, 196, 196, 197, 197, 197, 198, 198, 198, 199, 199, 199, 200, 200, 200, 201, 201, 201, 202, 202, 202, 203, 203, 203, 204, 204, 204, 205, 205, 205, 206, 206, 206, 207, 207, 207, 208, 208, 208, 209, 209, 209, 209, 209, 209, 210, 210, 210, 211, 211, 211, 212, 212, 212, 213, 213, 213, 214, 214, 214, 215, 215, 215, 216, 216, 216, 217, 217, 217, 218, 218, 218, 219, 219, 219, 220, 220, 220, 221, 221, 221, 222, 222, 222, 223, 223, 223, 224, 224, 224, 225, 225, 225, 226, 226, 226, 227, 227, 227, 228, 228, 228, 229, 229, 229, 230, 230, 230, 231, 231, 231, 232, 232, 232, 233, 233, 233, 234, 234, 234, 235, 235, 235 }; + +const byte colorMap_contrast[] = { 16, 16, 16, 23, 16, 22, 30, 15, 30, 37, 16, 37, 46, 15, 45, 53, 15, 52, 60, 15, 60, 67, 15, 67, 75, 15, 75, 82, 15, 81, 89, 15, 90, 98, 14, 96, 105, 14, 105, 112, 14, 111, 120, 15, 121, 127, 15, 127, 135, 15, 135, 143, 14, 142, 150, 14, 150, 158, 14, 157, 165, 14, 165, 172, 14, 172, 179, 14, 180, 186, 14, 187, 195, 14, 195, 202, 14, 201, 209, 14, 210, 217, 14, 216, 209, 15, 214, 202, 14, 211, 194, 15, 209, 187, 14, 206, 179, 15, 204, 172, 14, 201, 165, 15, 199, 157, 14, 196, 150, 15, 194, 141, 14, 191, 135, 15, 189, 126, 14, 186, 120, 15, 184, 112, 14, 181, 105, 15, 179, 97, 14, 176, 91, 15, 174, 82, 14, 171, 74, 15, 169, 67, 14, 166, 60, 15, 164, 52, 14, 161, 45, 15, 159, 38, 14, 156, 30, 15, 154, 23, 14, 151, 15, 15, 149, 16, 23, 152, 14, 30, 155, 15, 38, 156, 15, 44, 158, 14, 53, 162, 14, 59, 164, 15, 67, 165, 13, 74, 168, 14, 82, 171, 15, 89, 174, 13, 96, 176, 14, 104, 178, 14, 111, 180, 13, 119, 183, 13, 125, 185, 14, 133, 187, 12, 140, 189, 13, 148, 192, 14, 155, 195, 12, 162, 198, 13, 170, 199, 13, 177, 201, 12, 185, 205, 12, 191, 207, 13, 199, 208, 11, 206, 211, 12, 214, 214, 12, 208, 206, 12, 205, 200, 12, 199, 192, 12, 194, 185, 12, 190, 176, 12, 185, 169, 14, 181, 162, 12, 176, 155, 13, 170, 147, 14, 166, 141, 13, 161, 133, 13, 156, 125, 14, 151, 119, 13, 147, 110, 14, 142, 103, 14, 137, 96, 13, 132, 88, 14, 128, 81, 14, 122, 74, 13, 118, 66, 14, 113, 60, 15, 108, 52, 14, 104, 44, 15, 99, 36, 15, 93, 29, 15, 90, 22, 15, 84, 15, 23, 89, 14, 28, 93, 13, 36, 97, 15, 42, 103, 14, 49, 106, 13, 55, 112, 13, 63, 116, 12, 69, 120, 13, 76, 125, 12, 84, 129, 12, 90, 134, 11, 97, 138, 10, 104, 143, 12, 111, 147, 11, 117, 152, 10, 124, 156, 9, 130, 161, 10, 138, 165, 10, 144, 170, 9, 151, 174, 9, 159, 179, 8, 165, 183, 9, 172, 187, 8, 179, 193, 8, 186, 196, 7, 192, 202, 6, 200, 206, 8, 205, 210, 7, 213, 215, 6, 209, 208, 7, 207, 201, 7, 204, 194, 7, 200, 187, 7, 196, 180, 8, 194, 173, 8, 191, 166, 8, 187, 159, 9, 183, 153, 9, 181, 145, 9, 178, 139, 10, 174, 132, 10, 172, 124, 10, 168, 118, 11, 166, 112, 10, 162, 105, 10, 160, 98, 11, 156, 91, 11, 153, 84, 11, 150, 77, 12, 147, 70, 12, 143, 63, 12, 140, 57, 13, 137, 49, 13, 134, 43, 13, 130, 36, 14, 127, 29, 14, 124, 22, 14, 121, 15, 15, 124, 16, 17, 128, 17, 19, 130, 20, 19, 133, 21, 21, 135, 21, 22, 139, 23, 24, 141, 25, 24, 144, 26, 26, 148, 28, 28, 151, 29, 30, 153, 31, 30, 156, 32, 32, 160, 34, 34, 163, 35, 36, 164, 36, 36, 168, 38, 38, 171, 39, 40, 174, 40, 42, 176, 43, 42, 180, 44, 44, 183, 45, 46, 187, 46, 48, 189, 49, 48, 191, 49, 49, 194, 50, 51, 198, 52, 53, 200, 54, 53, 203, 55, 55, 204, 60, 61, 205, 67, 68, 206, 73, 72, 207, 79, 79, 208, 84, 84, 209, 91, 91, 210, 96, 97, 211, 103, 104, 212, 109, 108, 213, 115, 114, 214, 120, 120, 215, 127, 127, 216, 132, 133, 217, 139, 139, 218, 145, 143, 219, 151, 150, 220, 156, 156, 221, 163, 163, 222, 168, 169, 223, 175, 175, 224, 181, 179, 225, 187, 186, 226, 192, 192, 227, 199, 199, 228, 204, 204, 229, 211, 211, 230, 217, 215, 231, 223, 222, 232, 228, 228 }; + +const byte colorMap_doubleRainbow[] = { 18, 15, 18, 25, 17, 26, 34, 18, 32, 43, 19, 39, 52, 21, 48, 60, 23, 55, 69, 25, 62, 77, 26, 70, 86, 28, 75, 95, 30, 84, 103, 31, 91, 112, 34, 98, 120, 35, 106, 129, 36, 111, 138, 39, 120, 146, 40, 128, 155, 42, 136, 150, 44, 140, 145, 47, 146, 139, 51, 151, 134, 54, 157, 130, 57, 161, 124, 60, 168, 119, 63, 172, 115, 66, 179, 109, 70, 183, 104, 73, 189, 99, 76, 194, 93, 80, 200, 89, 83, 205, 84, 86, 211, 78, 90, 216, 73, 92, 222, 69, 96, 227, 63, 99, 233, 59, 103, 238, 57, 104, 230, 54, 107, 221, 50, 109, 213, 50, 113, 206, 46, 115, 196, 45, 117, 189, 42, 120, 180, 39, 123, 171, 38, 125, 164, 35, 127, 154, 32, 130, 147, 30, 133, 138, 28, 135, 129, 25, 138, 122, 24, 140, 113, 21, 144, 104, 20, 146, 97, 16, 148, 87, 14, 152, 81, 27, 153, 75, 41, 157, 70, 54, 160, 64, 69, 164, 60, 84, 166, 54, 98, 170, 49, 110, 173, 44, 123, 176, 38, 138, 180, 34, 151, 182, 28, 166, 186, 23, 179, 189, 18, 194, 193, 13, 194, 189, 13, 194, 186, 13, 193, 183, 14, 194, 180, 15, 194, 177, 15, 194, 174, 14, 193, 171, 14, 194, 169, 15, 193, 165, 15, 194, 161, 16, 194, 160, 15, 194, 156, 17, 194, 154, 18, 195, 150, 17, 194, 147, 17, 195, 145, 18, 195, 142, 18, 195, 138, 19, 194, 135, 19, 195, 133, 20, 195, 129, 20, 195, 126, 19, 195, 124, 22, 193, 118, 21, 191, 114, 24, 189, 109, 24, 188, 104, 27, 186, 100, 27, 185, 95, 29, 183, 91, 30, 181, 86, 32, 180, 82, 33, 178, 77, 35, 177, 73, 36, 176, 67, 38, 173, 63, 39, 172, 59, 41, 172, 54, 41, 169, 50, 44, 169, 45, 45, 170, 53, 60, 171, 61, 74, 174, 68, 90, 174, 76, 103, 177, 83, 119, 179, 92, 133, 181, 99, 149, 182, 107, 162, 185, 114, 178, 186, 123, 192, 187, 131, 208, 190, 139, 222, 193, 146, 238, 194, 149, 236, 195, 153, 238, 197, 158, 237, 199, 160, 237, 200, 165, 237, 203, 168, 236, 193, 169, 235, 185, 168, 232, 176, 168, 229, 166, 168, 228, 157, 168, 225, 149, 168, 222, 140, 170, 220, 131, 169, 218, 121, 170, 215, 113, 170, 212, 103, 170, 211, 94, 170, 208, 86, 171, 206, 76, 171, 203, 68, 171, 202, 59, 171, 199, 51, 170, 196, 41, 171, 195, 33, 172, 193, 23, 172, 190, 14, 172, 187, 18, 173, 181, 24, 174, 174, 30, 175, 166, 33, 176, 160, 39, 177, 152, 45, 178, 145, 49, 179, 139, 54, 180, 131, 60, 181, 126, 64, 182, 118, 69, 183, 110, 74, 183, 104, 79, 185, 97, 84, 186, 91, 88, 187, 83, 93, 187, 76, 99, 189, 71, 103, 190, 63, 107, 191, 57, 113, 192, 50, 118, 193, 42, 122, 194, 36, 127, 195, 28, 133, 195, 20, 139, 196, 15, 143, 199, 14, 148, 200, 15, 151, 202, 13, 155, 204, 13, 161, 206, 15, 164, 208, 13, 169, 209, 13, 173, 211, 14, 178, 213, 13, 182, 214, 13, 187, 216, 14, 192, 217, 13, 196, 219, 13, 201, 221, 13, 205, 223, 13, 210, 224, 13, 213, 226, 11, 217, 228, 13, 222, 229, 13, 226, 231, 11, 231, 233, 13, 236, 234, 13, 236, 229, 13, 236, 224, 16, 237, 219, 17, 236, 214, 20, 236, 209, 22, 236, 203, 22, 236, 198, 25, 237, 193, 28, 236, 188, 30, 236, 183, 31, 236, 177, 33, 236, 172, 34, 236, 167, 36, 236, 162, 39, 238, 156, 42, 237, 151, 42, 237, 146, 45, 237, 140, 47, 238, 136, 48, 238, 131, 51, 237, 125, 53, 235, 118, 52, 235, 113, 52, 234, 105, 52, 233, 99, 52, 232, 93, 52, 231, 86, 53, 229, 80, 52, 230, 73, 52, 228, 67, 53, 227, 61, 53, 226, 54, 52, 225, 48, 52, 227, 56, 61, 227, 64, 69, 227, 73, 77, 227, 80, 86, 227, 88, 93, 229, 96, 101, 228, 105, 109, 230, 113, 118, 230, 121, 126, 231, 130, 136, 231, 138, 142, 230, 146, 150, 231, 154, 158, 233, 162, 166, 233, 170, 175, 232, 175, 178, 232, 179, 183, 233, 184, 189, 233, 189, 194, 233, 195, 198, 233, 199, 202, 235, 204, 206, 235, 208, 213, 234, 213, 216, 235, 218, 222, 235, 222, 227, 234, 227, 230, 235, 232, 235 }; + +const byte colorMap_grayRed[] = { 218, 186, 175, 216, 186, 174, 214, 186, 173, 213, 185, 172, 212, 184, 171, 209, 183, 170, 206, 182, 170, 205, 181, 169, 202, 180, 168, 202, 180, 168, 199, 179, 168, 197, 178, 167, 194, 178, 166, 193, 177, 166, 191, 177, 165, 186, 176, 165, 185, 175, 164, 182, 173, 162, 180, 174, 162, 177, 172, 162, 174, 172, 161, 172, 170, 159, 170, 170, 160, 168, 169, 159, 165, 169, 158, 162, 167, 157, 160, 168, 157, 157, 167, 155, 156, 166, 154, 153, 165, 155, 149, 164, 155, 146, 164, 154, 143, 163, 152, 140, 162, 153, 137, 161, 151, 136, 160, 150, 134, 159, 149, 131, 159, 150, 128, 157, 148, 126, 158, 148, 124, 156, 147, 122, 156, 146, 120, 155, 147, 117, 155, 146, 115, 154, 145, 110, 152, 144, 109, 152, 144, 106, 151, 142, 105, 150, 141, 101, 149, 141, 100, 149, 141, 99, 148, 140, 96, 148, 139, 93, 146, 138, 92, 147, 138, 91, 146, 137, 90, 145, 138, 86, 143, 136, 85, 142, 135, 83, 142, 134, 80, 142, 133, 77, 140, 133, 76, 139, 132, 75, 138, 131, 74, 137, 130, 72, 137, 129, 71, 136, 130, 69, 137, 128, 68, 136, 127, 67, 134, 128, 66, 133, 127, 65, 134, 127, 64, 133, 126, 63, 132, 125, 62, 131, 124, 61, 130, 123, 60, 129, 122, 59, 128, 121, 59, 128, 121, 58, 127, 120, 58, 125, 119, 58, 125, 119, 57, 124, 118, 58, 123, 117, 58, 123, 117, 58, 123, 117, 57, 122, 116, 56, 121, 115, 56, 121, 115, 57, 120, 115, 58, 117, 111, 58, 117, 111, 59, 116, 111, 59, 116, 111, 60, 114, 110, 60, 115, 108, 61, 114, 108, 61, 112, 107, 61, 112, 107, 63, 112, 107, 63, 112, 107, 63, 110, 104, 65, 109, 104, 66, 109, 104, 67, 108, 104, 69, 106, 102, 72, 107, 101, 72, 105, 100, 73, 104, 100, 75, 103, 100, 78, 102, 98, 77, 101, 97, 79, 102, 98, 82, 101, 96, 83, 100, 96, 85, 99, 96, 86, 99, 95, 89, 98, 93, 90, 97, 93, 92, 95, 92, 96, 94, 91, 97, 94, 91, 100, 93, 89, 103, 93, 90, 104, 93, 88, 107, 91, 88, 107, 90, 86, 111, 89, 87, 112, 89, 87, 114, 88, 85, 117, 87, 83, 120, 87, 84, 122, 86, 84, 125, 85, 82, 126, 84, 82, 130, 82, 81, 134, 82, 80, 135, 82, 78, 138, 81, 78, 140, 80, 78, 143, 80, 77, 145, 79, 77, 148, 78, 75, 150, 77, 75, 153, 77, 75, 154, 77, 73, 157, 75, 73, 159, 73, 72, 163, 73, 71, 164, 72, 71, 168, 70, 69, 171, 71, 69, 173, 70, 69, 176, 68, 67, 178, 68, 67, 180, 67, 65, 182, 66, 65, 184, 66, 66, 187, 65, 64, 188, 64, 64, 191, 63, 63, 193, 63, 63, 194, 62, 61, 197, 61, 61, 198, 60, 61, 202, 59, 57, 204, 58, 57, 205, 57, 57, 208, 58, 56, 209, 57, 56, 212, 56, 56, 213, 55, 55, 214, 54, 54, 216, 53, 52, 218, 52, 52, 219, 51, 52, 221, 51, 50, 222, 50, 50, 223, 49, 50, 225, 49, 48, 227, 47, 47, 228, 48, 48, 228, 46, 47, 229, 45, 46, 231, 45, 46, 231, 45, 46, 232, 44, 46, 233, 43, 43, 234, 42, 43, 234, 40, 42, 234, 40, 42, 236, 40, 42, 236, 38, 41, 236, 38, 39, 238, 37, 39, 236, 35, 37, 237, 35, 35, 237, 35, 35, 236, 34, 36, 238, 33, 34, 237, 32, 35, 238, 31, 35, 237, 31, 32, 236, 30, 31, 235, 29, 30, 235, 29, 30, 235, 29, 30, 234, 28, 29, 234, 26, 28, 233, 25, 27, 232, 24, 26, 232, 24, 26, 231, 23, 25, 231, 23, 25, 230, 22, 24, 232, 21, 24, 231, 20, 23, 230, 19, 22, 229, 18, 21, 230, 18, 21, 229, 17, 20, 229, 17, 20, 228, 16, 19, 227, 15, 18 }; + +const byte colorMap_glowBow[] = { 16, 16, 16, 19, 17, 18, 22, 16, 16, 25, 17, 18, 28, 17, 19, 31, 17, 20, 34, 17, 19, 36, 18, 20, 39, 18, 19, 43, 19, 21, 45, 18, 21, 48, 20, 21, 52, 19, 22, 54, 20, 23, 58, 20, 23, 63, 21, 23, 68, 21, 25, 70, 21, 26, 73, 22, 27, 75, 22, 26, 79, 22, 27, 81, 22, 28, 84, 23, 27, 87, 22, 28, 91, 24, 30, 96, 23, 30, 102, 24, 33, 104, 25, 32, 108, 25, 33, 110, 25, 34, 117, 25, 34, 120, 27, 34, 122, 27, 35, 127, 28, 35, 129, 27, 35, 132, 29, 37, 135, 27, 37, 138, 29, 38, 141, 29, 39, 143, 29, 40, 147, 29, 41, 150, 31, 41, 152, 30, 41, 155, 29, 42, 158, 30, 41, 165, 31, 44, 167, 32, 43, 170, 32, 44, 175, 33, 45, 177, 33, 46, 178, 32, 46, 182, 32, 45, 186, 33, 47, 188, 34, 48, 190, 34, 47, 194, 34, 48, 195, 35, 49, 195, 35, 47, 197, 38, 48, 196, 39, 46, 198, 39, 45, 199, 41, 44, 200, 42, 43, 201, 43, 43, 200, 44, 41, 201, 45, 42, 203, 46, 41, 204, 47, 42, 204, 47, 40, 205, 49, 40, 205, 49, 38, 206, 52, 38, 207, 52, 36, 208, 53, 37, 209, 54, 36, 210, 55, 36, 210, 58, 35, 211, 59, 34, 212, 60, 33, 213, 60, 33, 214, 61, 33, 213, 62, 31, 215, 64, 33, 215, 64, 31, 216, 66, 30, 218, 66, 30, 218, 66, 30, 218, 68, 29, 219, 70, 28, 220, 69, 28, 221, 72, 26, 223, 73, 26, 222, 74, 24, 223, 75, 25, 224, 76, 24, 225, 78, 23, 225, 78, 22, 226, 79, 23, 227, 80, 22, 227, 81, 20, 228, 82, 21, 229, 83, 20, 230, 83, 18, 231, 86, 19, 231, 86, 17, 232, 87, 16, 233, 88, 17, 234, 90, 16, 235, 91, 14, 235, 91, 14, 236, 93, 13, 237, 94, 12, 236, 96, 13, 237, 97, 13, 237, 99, 14, 237, 101, 13, 236, 103, 12, 236, 105, 13, 237, 106, 12, 236, 108, 11, 236, 112, 12, 237, 113, 13, 236, 115, 12, 236, 117, 13, 235, 119, 12, 236, 122, 12, 237, 123, 13, 237, 125, 13, 236, 127, 12, 236, 129, 13, 237, 130, 12, 236, 132, 13, 236, 134, 12, 237, 135, 12, 237, 137, 13, 237, 142, 12, 236, 144, 13, 236, 146, 12, 237, 147, 13, 237, 149, 12, 236, 151, 13, 237, 152, 13, 237, 154, 12, 236, 156, 13, 236, 158, 12, 236, 160, 11, 235, 161, 12, 236, 163, 12, 236, 165, 13, 235, 167, 12, 236, 170, 12, 236, 172, 11, 235, 173, 12, 236, 176, 12, 235, 179, 12, 236, 180, 13, 236, 182, 12, 236, 184, 11, 237, 185, 12, 236, 187, 11, 236, 188, 12, 235, 190, 12, 236, 191, 13, 235, 194, 12, 236, 196, 11, 235, 199, 11, 236, 201, 11, 235, 202, 12, 235, 204, 14, 236, 204, 19, 236, 205, 23, 236, 204, 27, 235, 206, 30, 236, 206, 34, 236, 207, 37, 236, 207, 41, 235, 208, 45, 236, 208, 48, 236, 209, 52, 236, 209, 56, 235, 211, 65, 236, 212, 68, 235, 212, 71, 236, 212, 74, 234, 212, 78, 235, 213, 82, 235, 214, 87, 236, 214, 91, 236, 215, 94, 235, 217, 97, 235, 216, 100, 235, 217, 105, 235, 216, 108, 234, 218, 111, 235, 218, 116, 235, 219, 122, 235, 220, 127, 236, 220, 131, 235, 221, 134, 235, 221, 138, 235, 222, 142, 235, 221, 146, 234, 222, 148, 235, 223, 153, 235, 224, 157, 235, 225, 160, 236, 225, 165, 234, 225, 168, 235, 226, 171, 235, 225, 175, 236, 227, 182, 235, 228, 187, 234, 228, 190, 234, 229, 195, 235, 230, 197, 236, 230, 202, 234, 230, 205, 235, 231, 208, 235, 232, 213, 235, 231, 216, 234, 232, 219, 234, 234, 224, 235, 234, 228, 235, 235, 235 }; + +const byte colorMap_grayscale[] = { 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, + 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 20, 21, 21, 21, 22, 22, 22, 23, 23, + 23, 24, 24, 24, 25, 25, 25, 26, 26, 26, 27, 27, 27, 28, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32, 32, 32, 33, 33, 33, 34, 34, 34, 35, + 35, 35, 36, 36, 36, 37, 37, 37, 38, 38, 38, 39, 39, 39, 40, 40, 40, 41, 41, 41, 42, 42, 42, 43, 43, 43, 44, 44, 44, 45, 45, 45, 46, 46, 46, + 47, 47, 47, 48, 48, 48, 49, 49, 49, 50, 50, 50, 51, 51, 51, 52, 52, 52, 53, 53, 53, 54, 54, 54, 55, 55, 55, 56, 56, 56, 57, 57, 57, 58, 58, + 58, 59, 59, 59, 60, 60, 60, 61, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 65, 65, 65, 66, 66, 66, 67, 67, 67, 68, 68, 68, 69, 69, 69, 70, + 70, 70, 71, 71, 71, 72, 72, 72, 73, 73, 73, 74, 74, 74, 75, 75, 75, 76, 76, 76, 77, 77, 77, 78, 78, 78, 79, 79, 79, 80, 80, 80, 81, 81, 81, + 82, 82, 82, 83, 83, 83, 84, 84, 84, 85, 85, 85, 86, 86, 86, 87, 87, 87, 88, 88, 88, 89, 89, 89, 90, 90, 90, 91, 91, 91, 92, 92, 92, 93, 93, + 93, 94, 94, 94, 95, 95, 95, 96, 96, 96, 97, 97, 97, 98, 98, 98, 99, 99, 99, 100, 100, 100, 101, 101, 101, 102, 102, 102, 103, 103, 103, 104, + 104, 104, 105, 105, 105, 106, 106, 106, 107, 107, 107, 108, 108, 108, 109, 109, 109, 110, 110, 110, 111, 111, 111, 112, 112, 112, 113, 113, + 113, 114, 114, 114, 115, 115, 115, 116, 116, 116, 117, 117, 117, 118, 118, 118, 119, 119, 119, 120, 120, 120, 121, 121, 121, 122, 122, 122, + 123, 123, 123, 124, 124, 124, 125, 125, 125, 126, 126, 126, 127, 127, 127, 128, 128, 128, 129, 129, 129, 130, 130, 130, 131, 131, 131, 132, + 132, 132, 133, 133, 133, 134, 134, 134, 135, 135, 135, 136, 136, 136, 137, 137, 137, 138, 138, 138, 139, 139, 139, 140, 140, 140, 141, 141, + 141, 142, 142, 142, 143, 143, 143, 144, 144, 144, 145, 145, 145, 146, 146, 146, 147, 147, 147, 148, 148, 148, 149, 149, 149, 150, 150, 150, + 151, 151, 151, 152, 152, 152, 153, 153, 153, 154, 154, 154, 155, 155, 155, 156, 156, 156, 157, 157, 157, 158, 158, 158, 159, 159, 159, 160, + 160, 160, 161, 161, 161, 162, 162, 162, 163, 163, 163, 164, 164, 164, 165, 165, 165, 166, 166, 166, 167, 167, 167, 168, 168, 168, 169, 169, + 169, 170, 170, 170, 171, 171, 171, 172, 172, 172, 173, 173, 173, 174, 174, 174, 175, 175, 175, 176, 176, 176, 177, 177, 177, 178, 178, 178, + 179, 179, 179, 180, 180, 180, 181, 181, 181, 182, 182, 182, 183, 183, 183, 184, 184, 184, 185, 185, 185, 186, 186, 186, 187, 187, 187, 188, + 188, 188, 189, 189, 189, 190, 190, 190, 191, 191, 191, 192, 192, 192, 193, 193, 193, 194, 194, 194, 195, 195, 195, 196, 196, 196, 197, 197, + 197, 198, 198, 198, 199, 199, 199, 200, 200, 200, 201, 201, 201, 202, 202, 202, 203, 203, 203, 204, 204, 204, 205, 205, 205, 206, 206, 206, + 207, 207, 207, 208, 208, 208, 209, 209, 209, 210, 210, 210, 211, 211, 211, 212, 212, 212, 213, 213, 213, 214, 214, 214, 215, 215, 215, 216, + 216, 216, 217, 217, 217, 218, 218, 218, 219, 219, 219, 220, 220, 220, 221, 221, 221, 222, 222, 222, 223, 223, 223, 224, 224, 224, 225, 225, + 225, 226, 226, 226, 227, 227, 227, 228, 228, 228, 229, 229, 229, 230, 230, 230, 231, 231, 231, 232, 232, 232, 233, 233, 233, 234, 234, 234, + 235, 235, 235, 236, 236, 236, 237, 237, 237, 238, 238, 238, 239, 239, 239, 240, 240, 240, 241, 241, 241, 242, 242, 242, 243, 243, 243, 244, + 244, 244, 245, 245, 245, 246, 246, 246, 247, 247, 247, 248, 248, 248, 249, 249, 249, 250, 250, 250, 251, 251, 251, 252, 252, 252, 253, 253, + 253, 254, 254, 254, 255, 255, 255 }; + +const byte colorMap_hottest[] = { 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 20, 21, 21, 21, 22, 22, 22, 23, 23, 23, 24, 24, 24, 25, 25, 25, 26, 26, 26, 27, 27, 27, 28, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32, 32, 32, 33, 33, 33, 34, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 37, 38, 38, 38, 39, 39, 39, 40, 40, 40, 41, 41, 41, 42, 42, 42, 43, 43, 43, 44, 44, 44, 44, 44, 44, 45, 45, 45, 46, 46, 46, 47, 47, 47, 48, 48, 48, 49, 49, 49, 50, 50, 50, 51, 51, 51, 52, 52, 52, 53, 53, 53, 54, 54, 54, 55, 55, 55, 56, 56, 56, 57, 57, 57, 58, 58, 58, 59, 59, 59, 60, 60, 60, 61, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 65, 65, 65, 66, 66, 66, 67, 67, 67, 68, 68, 68, 69, 69, 69, 70, 70, 70, 71, 71, 71, 72, 72, 72, 73, 73, 73, 74, 74, 74, 75, 75, 75, 76, 76, 76, 77, 77, 77, 78, 78, 78, 79, 79, 79, 80, 80, 80, 81, 81, 81, 82, 82, 82, 83, 83, 83, 84, 84, 84, 85, 85, 85, 86, 86, 86, 87, 87, 87, 88, 88, 88, 89, 89, 89, 90, 90, 90, 91, 91, 91, 92, 92, 92, 93, 93, 93, 94, 94, 94, 95, 95, 95, 96, 96, 96, 97, 97, 97, 98, 98, 98, 99, 99, 99, 99, 99, 99, 100, 100, 100, 101, 101, 101, 102, 102, 102, 103, 103, 103, 104, 104, 104, 105, 105, 105, 106, 106, 106, 107, 107, 107, 108, 108, 108, 109, 109, 109, 110, 110, 110, 111, 111, 111, 112, 112, 112, 113, 113, 113, 114, 114, 114, 115, 115, 115, 116, 116, 116, 117, 117, 117, 118, 118, 118, 119, 119, 119, 120, 120, 120, 121, 121, 121, 122, 122, 122, 123, 123, 123, 124, 124, 124, 125, 125, 125, 126, 126, 126, 127, 127, 127, 128, 128, 128, 129, 129, 129, 130, 130, 130, 131, 131, 131, 132, 132, 132, 133, 133, 133, 134, 134, 134, 135, 135, 135, 136, 136, 136, 137, 137, 137, 138, 138, 138, 139, 139, 139, 140, 140, 140, 141, 141, 141, 142, 142, 142, 143, 143, 143, 144, 144, 144, 145, 145, 145, 146, 146, 146, 147, 147, 147, 148, 148, 148, 149, 149, 149, 150, 150, 150, 151, 151, 151, 152, 152, 152, 153, 153, 153, 154, 154, 154, 154, 154, 154, 155, 155, 155, 156, 156, 156, 157, 157, 157, 158, 158, 158, 159, 159, 159, 160, 160, 160, 161, 161, 161, 162, 162, 162, 163, 163, 163, 164, 164, 164, 165, 165, 165, 166, 166, 166, 167, 167, 167, 168, 168, 168, 169, 169, 169, 170, 170, 170, 171, 171, 171, 172, 172, 172, 173, 173, 173, 174, 174, 174, 175, 175, 175, 176, 176, 176, 177, 177, 177, 178, 178, 178, 179, 179, 179, 180, 180, 180, 181, 181, 181, 182, 182, 182, 183, 183, 183, 184, 184, 184, 185, 185, 185, 186, 186, 186, 187, 187, 187, 188, 188, 188, 189, 189, 189, 190, 190, 190, 191, 191, 191, 192, 192, 192, 193, 193, 193, 194, 194, 194, 195, 195, 195, 196, 196, 196, 197, 197, 197, 198, 198, 198, 199, 199, 199, 200, 200, 200, 201, 201, 201, 202, 202, 202, 203, 203, 203, 204, 204, 204, 205, 205, 205, 206, 206, 206, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13 }; + +const byte colorMap_ironblack[] = { 255, 255, 255, 253, 253, 253, 251, 251, 251, 249, 249, 249, 247, 247, 247, 245, 245, 245, 243, 243, 243, 241, 241, + 241, 239, 239, 239, 237, 237, 237, 235, 235, 235, 233, 233, 233, 231, 231, 231, 229, 229, 229, 227, 227, 227, 225, 225, 225, 223, 223, 223, + 221, 221, 221, 219, 219, 219, 217, 217, 217, 215, 215, 215, 213, 213, 213, 211, 211, 211, 209, 209, 209, 207, 207, 207, 205, 205, 205, 203, + 203, 203, 201, 201, 201, 199, 199, 199, 197, 197, 197, 195, 195, 195, 193, 193, 193, 191, 191, 191, 189, 189, 189, 187, 187, 187, 185, 185, + 185, 183, 183, 183, 181, 181, 181, 179, 179, 179, 177, 177, 177, 175, 175, 175, 173, 173, 173, 171, 171, 171, 169, 169, 169, 167, 167, 167, + 165, 165, 165, 163, 163, 163, 161, 161, 161, 159, 159, 159, 157, 157, 157, 155, 155, 155, 153, 153, 153, 151, 151, 151, 149, 149, 149, 147, + 147, 147, 145, 145, 145, 143, 143, 143, 141, 141, 141, 139, 139, 139, 137, 137, 137, 135, 135, 135, 133, 133, 133, 131, 131, 131, 129, 129, + 129, 126, 126, 126, 124, 124, 124, 122, 122, 122, 120, 120, 120, 118, 118, 118, 116, 116, 116, 114, 114, 114, 112, 112, 112, 110, 110, 110, + 108, 108, 108, 106, 106, 106, 104, 104, 104, 102, 102, 102, 100, 100, 100, 98, 98, 98, 96, 96, 96, 94, 94, 94, 92, 92, 92, 90, 90, 90, 88, 88, + 88, 86, 86, 86, 84, 84, 84, 82, 82, 82, 80, 80, 80, 78, 78, 78, 76, 76, 76, 74, 74, 74, 72, 72, 72, 70, 70, 70, 68, 68, 68, 66, 66, 66, 64, + 64, 64, 62, 62, 62, 60, 60, 60, 58, 58, 58, 56, 56, 56, 54, 54, 54, 52, 52, 52, 50, 50, 50, 48, 48, 48, 46, 46, 46, 44, 44, 44, 42, 42, 42, + 40, 40, 40, 38, 38, 38, 36, 36, 36, 34, 34, 34, 32, 32, 32, 30, 30, 30, 28, 28, 28, 26, 26, 26, 24, 24, 24, 22, 22, 22, 20, 20, 20, 18, 18, + 18, 16, 16, 16, 14, 14, 14, 12, 12, 12, 10, 10, 10, 8, 8, 8, 6, 6, 6, 4, 4, 4, 2, 2, 2, 0, 0, 0, 0, 0, 9, 2, 0, 16, 4, 0, 24, 6, 0, 31, 8, 0, + 38, 10, 0, 45, 12, 0, 53, 14, 0, 60, 17, 0, 67, 19, 0, 74, 21, 0, 82, 23, 0, 89, 25, 0, 96, 27, 0, 103, 29, 0, 111, 31, 0, 118, 36, 0, 120, + 41, 0, 121, 46, 0, 122, 51, 0, 123, 56, 0, 124, 61, 0, 125, 66, 0, 126, 71, 0, 127, 76, 1, 128, 81, 1, 129, 86, 1, 130, 91, 1, 131, 96, 1, + 132, 101, 1, 133, 106, 1, 134, 111, 1, 135, 116, 1, 136, 121, 1, 136, 125, 2, 137, 130, 2, 137, 135, 3, 137, 139, 3, 138, 144, 3, 138, 149, 4, + 138, 153, 4, 139, 158, 5, 139, 163, 5, 139, 167, 5, 140, 172, 6, 140, 177, 6, 140, 181, 7, 141, 186, 7, 141, 189, 10, 137, 191, 13, 132, 194, + 16, 127, 196, 19, 121, 198, 22, 116, 200, 25, 111, 203, 28, 106, 205, 31, 101, 207, 34, 95, 209, 37, 90, 212, 40, 85, 214, 43, 80, 216, 46, + 75, 218, 49, 69, 221, 52, 64, 223, 55, 59, 224, 57, 49, 225, 60, 47, 226, 64, 44, 227, 67, 42, 228, 71, 39, 229, 74, 37, 230, 78, 34, 231, 81, + 32, 231, 85, 29, 232, 88, 27, 233, 92, 24, 234, 95, 22, 235, 99, 19, 236, 102, 17, 237, 106, 14, 238, 109, 12, 239, 112, 12, 240, 116, 12, + 240, 119, 12, 241, 123, 12, 241, 127, 12, 242, 130, 12, 242, 134, 12, 243, 138, 12, 243, 141, 13, 244, 145, 13, 244, 149, 13, 245, 152, 13, + 245, 156, 13, 246, 160, 13, 246, 163, 13, 247, 167, 13, 247, 171, 13, 248, 175, 14, 248, 178, 15, 249, 182, 16, 249, 185, 18, 250, 189, 19, + 250, 192, 20, 251, 196, 21, 251, 199, 22, 252, 203, 23, 252, 206, 24, 253, 210, 25, 253, 213, 27, 254, 217, 28, 254, 220, 29, 255, 224, 30, + 255, 227, 39, 255, 229, 53, 255, 231, 67, 255, 233, 81, 255, 234, 95, 255, 236, 109, 255, 238, 123, 255, 240, 137, 255, 242, 151, 255, 244, + 165, 255, 246, 179, 255, 248, 193, 255, 249, 207, 255, 251, 221, 255, 253, 235, 255, 255, 24 }; + +const byte colorMap_lava[] = { 16, 16, 16, 17, 19, 22, 19, 21, 30, 20, 24, 37, 22, 27, 43, 22, 31, 50, 24, 32, 57, 25, 37, 65, 26, 39, 70, 28, 43, 78, 29, 44, 85, 31, 47, 94, 32, 50, 100, 34, 53, 107, 34, 57, 113, 37, 59, 122, 37, 63, 128, 39, 66, 135, 40, 69, 141, 42, 71, 149, 44, 74, 156, 41, 76, 156, 41, 76, 156, 39, 78, 157, 36, 80, 155, 36, 82, 156, 34, 82, 156, 33, 85, 157, 31, 86, 157, 30, 86, 157, 29, 88, 156, 28, 91, 157, 26, 91, 157, 26, 93, 158, 23, 95, 158, 21, 97, 159, 20, 98, 159, 18, 99, 158, 17, 101, 160, 15, 102, 159, 15, 104, 160, 13, 105, 158, 13, 105, 158, 14, 106, 157, 13, 107, 157, 14, 108, 156, 14, 110, 156, 14, 111, 154, 15, 112, 155, 13, 113, 153, 13, 113, 151, 14, 114, 152, 14, 114, 151, 14, 116, 152, 14, 116, 150, 13, 118, 149, 13, 119, 147, 14, 120, 148, 13, 121, 146, 14, 122, 146, 14, 122, 146, 14, 124, 145, 14, 125, 143, 15, 126, 144, 14, 125, 143, 15, 126, 142, 13, 127, 142, 14, 128, 142, 14, 128, 140, 14, 130, 139, 14, 130, 139, 14, 130, 139, 14, 131, 137, 13, 133, 136, 13, 133, 135, 14, 134, 136, 13, 135, 134, 13, 135, 134, 13, 135, 134, 14, 137, 133, 14, 137, 131, 19, 133, 130, 24, 130, 132, 30, 125, 131, 35, 121, 130, 39, 118, 129, 46, 114, 129, 50, 109, 127, 56, 106, 127, 62, 101, 128, 67, 99, 126, 73, 94, 125, 78, 90, 126, 84, 85, 125, 89, 82, 124, 93, 78, 123, 99, 73, 122, 105, 70, 122, 109, 66, 121, 115, 62, 122, 120, 58, 121, 123, 57, 119, 124, 57, 115, 127, 56, 114, 130, 54, 112, 133, 54, 108, 134, 53, 108, 136, 51, 104, 137, 51, 102, 140, 50, 100, 144, 50, 98, 145, 50, 96, 148, 49, 94, 148, 47, 91, 151, 46, 90, 154, 45, 88, 155, 45, 84, 158, 44, 84, 161, 43, 81, 162, 42, 79, 165, 41, 77, 167, 41, 75, 168, 41, 74, 169, 40, 72, 171, 40, 72, 172, 39, 70, 174, 39, 67, 175, 38, 67, 176, 38, 65, 178, 38, 63, 180, 38, 62, 182, 38, 60, 183, 37, 60, 184, 37, 59, 186, 37, 57, 187, 36, 55, 189, 36, 53, 190, 35, 53, 191, 35, 52, 193, 34, 50, 194, 34, 48, 196, 36, 48, 199, 37, 48, 201, 39, 48, 203, 40, 47, 205, 41, 46, 207, 43, 48, 210, 43, 47, 212, 46, 48, 213, 47, 47, 215, 47, 46, 219, 49, 46, 220, 50, 46, 222, 53, 46, 224, 53, 47, 227, 55, 47, 228, 56, 46, 229, 57, 45, 233, 59, 46, 235, 60, 45, 237, 62, 45, 238, 63, 44, 237, 65, 41, 236, 67, 40, 237, 68, 39, 236, 70, 36, 237, 71, 35, 237, 73, 34, 238, 74, 33, 237, 77, 31, 237, 79, 30, 237, 79, 27, 237, 83, 25, 236, 84, 23, 237, 86, 21, 237, 88, 20, 237, 88, 16, 236, 90, 16, 237, 92, 15, 237, 94, 12, 237, 97, 13, 237, 99, 12, 236, 103, 12, 237, 106, 12, 236, 110, 12, 237, 113, 13, 237, 116, 13, 236, 120, 13, 237, 123, 13, 236, 127, 14, 237, 130, 14, 236, 132, 13, 236, 135, 13, 236, 139, 12, 235, 143, 12, 236, 146, 12, 237, 149, 12, 236, 153, 13, 235, 155, 12, 236, 158, 12, 237, 161, 12, 236, 163, 12, 236, 165, 13, 235, 167, 12, 236, 170, 12, 236, 172, 11, 235, 173, 12, 236, 176, 12, 235, 179, 12, 235, 181, 11, 236, 183, 13, 235, 185, 12, 235, 187, 11, 236, 189, 11, 236, 191, 13, 235, 194, 12, 236, 196, 11, 236, 199, 11, 236, 200, 12, 235, 202, 12, 236, 205, 23, 235, 207, 34, 235, 208, 45, 236, 209, 56, 235, 211, 67, 234, 212, 78, 236, 214, 90, 235, 216, 100, 234, 218, 111, 236, 220, 123, 234, 220, 133, 235, 221, 146, 235, 224, 157, 236, 225, 167, 235, 226, 179, 235, 229, 191, 235, 229, 201, 235, 232, 213, 235, 233, 224, 235, 235, 235 }; + +const byte colorMap_medical[] = { 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37 }; + +const byte colorMap_rainbow[] = { 1, 3, 74, 0, 3, 74, 0, 3, 75, 0, 3, 75, 0, 3, 76, 0, 3, 76, 0, 3, 77, 0, 3, 79, 0, 3, 82, 0, 5, 85, 0, 7, 88, 0, 10, + 91, 0, 14, 94, 0, 19, 98, 0, 22, 100, 0, 25, 103, 0, 28, 106, 0, 32, 109, 0, 35, 112, 0, 38, 116, 0, 40, 119, 0, 42, 123, 0, 45, 128, 0, 49, + 133, 0, 50, 134, 0, 51, 136, 0, 52, 137, 0, 53, 139, 0, 54, 142, 0, 55, 144, 0, 56, 145, 0, 58, 149, 0, 61, 154, 0, 63, 156, 0, 65, 159, 0, + 66, 161, 0, 68, 164, 0, 69, 167, 0, 71, 170, 0, 73, 174, 0, 75, 179, 0, 76, 181, 0, 78, 184, 0, 79, 187, 0, 80, 188, 0, 81, 190, 0, 84, 194, + 0, 87, 198, 0, 88, 200, 0, 90, 203, 0, 92, 205, 0, 94, 207, 0, 94, 208, 0, 95, 209, 0, 96, 210, 0, 97, 211, 0, 99, 214, 0, 102, 217, 0, 103, + 218, 0, 104, 219, 0, 105, 220, 0, 107, 221, 0, 109, 223, 0, 111, 223, 0, 113, 223, 0, 115, 222, 0, 117, 221, 0, 118, 220, 1, 120, 219, 1, 122, + 217, 2, 124, 216, 2, 126, 214, 3, 129, 212, 3, 131, 207, 4, 132, 205, 4, 133, 202, 4, 134, 197, 5, 136, 192, 6, 138, 185, 7, 141, 178, 8, 142, + 172, 10, 144, 166, 10, 144, 162, 11, 145, 158, 12, 146, 153, 13, 147, 149, 15, 149, 140, 17, 151, 132, 22, 153, 120, 25, 154, 115, 28, 156, + 109, 34, 158, 101, 40, 160, 94, 45, 162, 86, 51, 164, 79, 59, 167, 69, 67, 171, 60, 72, 173, 54, 78, 175, 48, 83, 177, 43, 89, 179, 39, 93, + 181, 35, 98, 183, 31, 105, 185, 26, 109, 187, 23, 113, 188, 21, 118, 189, 19, 123, 191, 17, 128, 193, 14, 134, 195, 12, 138, 196, 10, 142, + 197, 8, 146, 198, 6, 151, 200, 5, 155, 201, 4, 160, 203, 3, 164, 204, 2, 169, 205, 2, 173, 206, 1, 175, 207, 1, 178, 207, 1, 184, 208, 0, 190, + 210, 0, 193, 211, 0, 196, 212, 0, 199, 212, 0, 202, 213, 1, 207, 214, 2, 212, 215, 3, 215, 214, 3, 218, 214, 3, 220, 213, 3, 222, 213, 4, 224, + 212, 4, 225, 212, 5, 226, 212, 5, 229, 211, 5, 232, 211, 6, 232, 211, 6, 233, 211, 6, 234, 210, 6, 235, 210, 7, 236, 209, 7, 237, 208, 8, 239, + 206, 8, 241, 204, 9, 242, 203, 9, 244, 202, 10, 244, 201, 10, 245, 200, 10, 245, 199, 11, 246, 198, 11, 247, 197, 12, 248, 194, 13, 249, 191, + 14, 250, 189, 14, 251, 187, 15, 251, 185, 16, 252, 183, 17, 252, 178, 18, 253, 174, 19, 253, 171, 19, 254, 168, 20, 254, 165, 21, 254, 164, + 21, 255, 163, 22, 255, 161, 22, 255, 159, 23, 255, 157, 23, 255, 155, 24, 255, 149, 25, 255, 143, 27, 255, 139, 28, 255, 135, 30, 255, 131, + 31, 255, 127, 32, 255, 118, 34, 255, 110, 36, 255, 104, 37, 255, 101, 38, 255, 99, 39, 255, 93, 40, 255, 88, 42, 254, 82, 43, 254, 77, 45, + 254, 69, 47, 254, 62, 49, 253, 57, 50, 253, 53, 52, 252, 49, 53, 252, 45, 55, 251, 39, 57, 251, 33, 59, 251, 32, 60, 251, 31, 60, 251, 30, 61, + 251, 29, 61, 251, 28, 62, 250, 27, 63, 250, 27, 65, 249, 26, 66, 249, 26, 68, 248, 25, 70, 248, 24, 73, 247, 24, 75, 247, 25, 77, 247, 25, 79, + 247, 26, 81, 247, 32, 83, 247, 35, 85, 247, 38, 86, 247, 42, 88, 247, 46, 90, 247, 50, 92, 248, 55, 94, 248, 59, 96, 248, 64, 98, 248, 72, + 101, 249, 81, 104, 249, 87, 106, 250, 93, 108, 250, 95, 109, 250, 98, 110, 250, 100, 111, 251, 101, 112, 251, 102, 113, 251, 109, 117, 252, + 116, 121, 252, 121, 123, 253, 126, 126, 253, 130, 128, 254, 135, 131, 254, 139, 133, 254, 144, 136, 254, 151, 140, 255, 158, 144, 255, 163, + 146, 255, 168, 149, 255, 173, 152, 255, 176, 153, 255, 178, 155, 255, 184, 160, 255, 191, 165, 255, 195, 168, 255, 199, 172, 255, 203, 175, + 255, 207, 179, 255, 211, 182, 255, 216, 185, 255, 218, 190, 255, 220, 196, 255, 222, 200, 255, 225, 202, 255, 227, 204, 255, 230, 206, 255, + 233, 208 }; + +const byte colorMap_wheel1[] = { 238, 14, 239, 234, 17, 234, 229, 22, 230, 225, 27, 225, 221, 30, 220, 216, 35, 216, 211, 39, 212, 208, 44, 207, 203, 48, 202, 198, 52, 199, 195, 56, 195, 190, 61, 191, 185, 65, 185, 180, 70, 181, 177, 74, 177, 172, 79, 172, 167, 83, 167, 163, 87, 163, 159, 92, 158, 154, 97, 154, 150, 100, 151, 145, 105, 146, 141, 109, 142, 138, 114, 138, 132, 117, 132, 127, 122, 128, 124, 127, 124, 119, 131, 119, 114, 135, 114, 111, 140, 110, 106, 144, 105, 101, 149, 101, 97, 152, 98, 93, 157, 93, 88, 162, 89, 85, 166, 85, 79, 170, 79, 75, 175, 75, 71, 179, 71, 67, 184, 66, 61, 188, 61, 57, 193, 57, 53, 197, 52, 49, 201, 50, 43, 205, 45, 40, 209, 40, 35, 214, 36, 31, 219, 32, 27, 222, 26, 22, 227, 22, 17, 232, 18, 14, 236, 13, 14, 232, 18, 14, 227, 24, 14, 224, 27, 14, 218, 31, 13, 214, 36, 14, 209, 39, 13, 205, 44, 14, 200, 50, 13, 197, 53, 14, 192, 58, 14, 188, 61, 13, 184, 66, 14, 179, 71, 13, 176, 75, 14, 171, 80, 13, 167, 85, 14, 162, 88, 14, 158, 93, 14, 153, 98, 14, 150, 102, 15, 145, 107, 14, 141, 112, 15, 136, 115, 14, 132, 120, 15, 127, 125, 14, 124, 129, 15, 119, 134, 15, 115, 139, 15, 110, 142, 15, 106, 147, 16, 102, 151, 15, 98, 156, 16, 93, 161, 15, 89, 164, 16, 84, 169, 14, 79, 173, 15, 75, 177, 15, 71, 182, 15, 66, 187, 15, 62, 190, 14, 58, 195, 15, 53, 200, 14, 49, 204, 15, 45, 209, 15, 40, 214, 15, 36, 217, 15, 32, 222, 16, 27, 227, 15, 23, 231, 16, 19, 236, 15, 14, 241, 20, 18, 235, 25, 23, 232, 28, 28, 226, 32, 32, 222, 38, 36, 219, 42, 40, 213, 45, 45, 209, 51, 50, 204, 55, 53, 200, 58, 58, 196, 62, 62, 190, 68, 66, 187, 72, 70, 181, 75, 75, 177, 81, 79, 174, 85, 83, 168, 88, 88, 164, 93, 93, 159, 98, 96, 155, 102, 100, 151, 106, 106, 146, 111, 109, 142, 115, 113, 136, 119, 119, 133, 124, 122, 129, 128, 126, 123, 133, 131, 120, 136, 136, 116, 141, 140, 110, 145, 143, 106, 149, 149, 101, 154, 153, 97, 158, 156, 93, 163, 161, 88, 166, 166, 84, 171, 170, 78, 176, 174, 75, 179, 179, 71, 184, 183, 65, 189, 187, 62, 193, 191, 56, 196, 196, 52, 202, 200, 49, 206, 204, 43, 209, 209, 39, 215, 214, 34, 219, 217, 30, 223, 221, 26, 226, 226, 20, 232, 230, 17, 236, 234, 13, 231, 235, 15, 228, 235, 21, 223, 235, 25, 219, 234, 29, 214, 235, 34, 209, 235, 38, 206, 235, 43, 201, 235, 48, 197, 235, 52, 192, 235, 56, 188, 234, 61, 184, 235, 66, 180, 235, 70, 175, 235, 74, 169, 235, 79, 166, 235, 81, 161, 236, 87, 158, 235, 91, 152, 235, 95, 149, 235, 100, 143, 235, 104, 141, 235, 109, 135, 235, 114, 130, 235, 118, 126, 235, 122, 121, 235, 126, 118, 235, 132, 113, 235, 136, 109, 235, 140, 104, 235, 145, 100, 234, 149, 96, 236, 153, 91, 236, 157, 87, 235, 161, 82, 235, 166, 78, 235, 170, 74, 236, 175, 70, 235, 180, 65, 235, 184, 61, 235, 188, 57, 236, 193, 52, 236, 198, 48, 235, 202, 43, 235, 206, 39, 235, 211, 35, 236, 216, 31, 235, 220, 26, 236, 223, 22, 235, 227, 17, 235, 232, 14, 236, 237, 17, 231, 233, 21, 227, 228, 26, 222, 224, 31, 219, 219, 34, 214, 215, 39, 209, 210, 43, 205, 206, 49, 201, 202, 53, 196, 198, 56, 192, 192, 62, 188, 189, 66, 183, 186, 71, 179, 180, 74, 174, 176, 79, 171, 172, 84, 166, 168, 88, 161, 163, 94, 157, 160, 97, 153, 154, 101, 149, 150, 106, 144, 145, 110, 140, 142, 115, 135, 137, 119, 131, 133, 123, 127, 127, 129, 122, 125, 132, 118, 119, 136, 114, 115, 142, 110, 111, 146, 105, 107, 151, 100, 101, 154, 96, 98, 159, 93, 93, 164, 87, 89, 167, 83, 84, 172, 78, 80, 177, 75, 75, 181, 70, 72, 186, 66, 66, 190, 62, 63, 195, 57, 60, 199, 53, 54, 202, 48, 50, 208, 44, 46, 212, 40, 42, 217, 35, 36, 221, 30, 33, 225, 27, 28, 230, 22, 24, 234, 18, 19, 240, 14, 16 }; + +const byte colorMap_wheel2[] = { 17, 14, 17, 16, 23, 17, 17, 32, 17, 16, 40, 16, 16, 49, 16, 15, 58, 16, 15, 65, 16, 14, 74, 16, 15, 82, 16, 15, 91, 15, 14, 100, 15, 15, 108, 15, 14, 117, 14, 15, 125, 16, 14, 134, 15, 14, 143, 15, 15, 151, 15, 14, 160, 14, 15, 168, 14, 14, 177, 14, 14, 186, 13, 13, 192, 14, 13, 201, 14, 14, 209, 13, 14, 219, 14, 13, 228, 14, 14, 236, 13, 22, 227, 22, 28, 219, 29, 37, 212, 37, 46, 204, 46, 52, 196, 53, 61, 188, 61, 69, 181, 69, 76, 172, 76, 85, 164, 85, 94, 156, 95, 102, 147, 102, 109, 140, 110, 117, 132, 117, 126, 123, 126, 133, 116, 134, 141, 108, 141, 149, 101, 149, 157, 93, 157, 165, 84, 165, 174, 76, 175, 183, 68, 183, 189, 60, 190, 198, 52, 198, 205, 45, 205, 213, 36, 214, 222, 29, 222, 228, 21, 229, 238, 14, 239, 233, 17, 238, 229, 22, 238, 223, 27, 239, 218, 31, 238, 213, 37, 238, 209, 41, 238, 204, 45, 239, 199, 51, 239, 195, 55, 238, 191, 60, 238, 185, 65, 237, 180, 69, 238, 177, 74, 239, 171, 79, 238, 167, 84, 238, 161, 89, 239, 157, 93, 238, 153, 98, 239, 147, 101, 237, 142, 107, 237, 138, 111, 238, 133, 115, 237, 128, 121, 238, 123, 125, 237, 118, 131, 237, 114, 135, 238, 109, 139, 237, 104, 145, 237, 100, 149, 238, 96, 154, 239, 91, 159, 238, 85, 163, 237, 82, 169, 237, 76, 173, 238, 71, 177, 238, 67, 182, 237, 61, 187, 236, 57, 192, 236, 52, 196, 237, 47, 201, 237, 43, 206, 237, 37, 210, 238, 33, 215, 238, 29, 220, 237, 23, 226, 237, 19, 230, 237, 15, 235, 237, 19, 229, 232, 24, 226, 228, 28, 221, 224, 33, 216, 218, 36, 212, 214, 42, 208, 210, 46, 203, 206, 49, 199, 201, 54, 194, 197, 59, 191, 192, 64, 185, 188, 68, 181, 183, 73, 176, 179, 76, 172, 175, 81, 168, 171, 86, 163, 167, 91, 158, 162, 95, 155, 157, 100, 150, 153, 104, 146, 148, 108, 141, 144, 114, 137, 139, 117, 133, 136, 121, 129, 130, 126, 123, 126, 131, 118, 123, 136, 115, 118, 139, 111, 114, 144, 106, 109, 148, 101, 105, 154, 97, 100, 157, 93, 97, 161, 89, 91, 167, 85, 86, 172, 79, 83, 176, 75, 77, 179, 71, 73, 184, 66, 70, 189, 61, 64, 194, 57, 61, 197, 53, 56, 202, 48, 52, 207, 45, 47, 212, 39, 44, 216, 35, 38, 219, 31, 34, 225, 27, 30, 229, 22, 26, 234, 17, 20, 239, 14, 18, 233, 14, 22, 230, 13, 26, 224, 14, 31, 219, 14, 35, 214, 14, 39, 210, 13, 45, 206, 14, 49, 201, 14, 55, 195, 14, 59, 190, 14, 64, 186, 14, 68, 181, 14, 72, 176, 14, 77, 173, 14, 84, 168, 14, 88, 163, 14, 92, 158, 14, 97, 152, 14, 101, 147, 14, 107, 143, 14, 111, 139, 15, 117, 133, 14, 120, 130, 14, 125, 125, 14, 131, 120, 14, 136, 115, 14, 140, 109, 14, 144, 106, 14, 149, 100, 13, 155, 96, 15, 158, 91, 15, 164, 87, 14, 169, 82, 14, 173, 77, 14, 177, 72, 14, 182, 66, 14, 186, 64, 14, 193, 58, 15, 197, 53, 15, 202, 48, 15, 206, 44, 14, 210, 39, 14, 216, 34, 14, 221, 30, 13, 225, 24, 15, 230, 20, 15, 235, 15, 14, 241, 20, 18, 237, 23, 21, 232, 27, 26, 228, 30, 29, 223, 36, 33, 220, 38, 37, 215, 42, 41, 211, 45, 45, 207, 50, 49, 203, 54, 52, 199, 58, 56, 195, 61, 60, 192, 66, 64, 188, 69, 68, 184, 73, 72, 180, 78, 75, 176, 82, 79, 172, 84, 82, 167, 89, 87, 164, 91, 90, 159, 97, 95, 156, 100, 98, 151, 104, 103, 147, 108, 105, 144, 112, 109, 140, 115, 113, 136, 120, 117, 132, 124, 121, 128, 127, 125, 124, 131, 129, 120, 134, 133, 116, 139, 137, 111, 143, 140, 107, 148, 144, 105, 151, 148, 101, 155, 152, 97, 158, 156, 93, 162, 160, 89, 167, 164, 85, 169, 167, 80, 173, 171, 75, 176, 175, 71, 181, 179, 67, 185, 182, 63, 189, 186, 61, 192, 190, 57, 197, 194, 53, 200, 198, 49, 204, 202, 45, 209, 205, 40, 213, 209, 36, 216, 213, 32, 220, 217, 28, 223, 221, 24, 228, 225, 20, 232, 229, 16, 235, 233, 14 }; + +const byte colorMap_wheel3[] = { 17, 14, 17, 20, 14, 26, 26, 15, 36, 30, 14, 46, 35, 15, 56, 39, 15, 65, 45, 15, 75, 49, 14, 84, 55, 14, 94, 60, 15, 104, 64, 14, 113, 70, 14, 123, 74, 14, 132, 79, 15, 142, 83, 14, 152, 89, 15, 162, 93, 14, 171, 98, 15, 181, 103, 14, 190, 108, 14, 200, 112, 14, 209, 118, 14, 219, 122, 13, 228, 128, 13, 240, 122, 22, 228, 115, 33, 218, 111, 41, 211, 107, 50, 201, 102, 60, 190, 96, 70, 181, 91, 80, 170, 87, 89, 160, 81, 99, 151, 76, 109, 140, 70, 119, 130, 66, 130, 120, 63, 137, 113, 58, 148, 101, 54, 158, 93, 47, 167, 83, 44, 177, 72, 39, 187, 63, 35, 196, 54, 29, 206, 42, 24, 216, 33, 19, 225, 24, 15, 235, 12, 24, 225, 23, 34, 215, 34, 43, 206, 43, 52, 196, 51, 63, 187, 63, 72, 177, 71, 82, 166, 82, 93, 157, 92, 102, 148, 100, 111, 137, 111, 121, 129, 120, 131, 119, 131, 141, 110, 140, 150, 100, 151, 159, 90, 159, 169, 81, 171, 179, 71, 179, 188, 61, 188, 199, 51, 199, 209, 42, 208, 218, 31, 218, 228, 23, 228, 238, 14, 239, 227, 22, 235, 218, 33, 227, 208, 40, 225, 200, 51, 219, 189, 60, 214, 179, 71, 207, 170, 79, 204, 161, 89, 201, 150, 99, 192, 140, 109, 191, 130, 118, 186, 120, 128, 179, 111, 137, 174, 100, 147, 169, 91, 156, 164, 81, 166, 159, 71, 175, 152, 61, 185, 147, 53, 195, 143, 42, 205, 138, 32, 216, 134, 23, 225, 129, 13, 236, 125, 25, 225, 125, 34, 215, 126, 42, 206, 124, 52, 195, 125, 63, 187, 125, 72, 177, 125, 82, 166, 126, 92, 158, 126, 102, 148, 126, 112, 138, 125, 120, 128, 126, 131, 120, 125, 140, 109, 126, 150, 100, 125, 159, 90, 125, 170, 80, 127, 180, 71, 125, 189, 61, 126, 198, 53, 126, 208, 42, 126, 218, 33, 125, 227, 23, 126, 238, 14, 125, 228, 23, 130, 219, 32, 135, 208, 42, 140, 199, 52, 145, 189, 61, 150, 178, 71, 153, 169, 80, 158, 158, 90, 163, 149, 100, 169, 140, 109, 173, 130, 118, 178, 119, 128, 181, 110, 138, 189, 101, 147, 192, 90, 157, 196, 80, 166, 201, 71, 176, 207, 60, 186, 212, 51, 195, 217, 41, 205, 220, 31, 216, 225, 21, 224, 234, 15, 235, 237, 23, 225, 227, 33, 214, 217, 43, 204, 208, 53, 195, 197, 63, 184, 187, 72, 175, 178, 83, 166, 168, 91, 157, 159, 101, 146, 151, 111, 136, 142, 120, 127, 131, 130, 117, 122, 140, 109, 113, 150, 99, 102, 160, 89, 93, 168, 80, 83, 178, 70, 72, 189, 61, 64, 198, 52, 53, 210, 41, 46, 218, 31, 34, 229, 23, 24, 239, 14, 18, 229, 18, 25, 218, 23, 35, 208, 28, 45, 199, 32, 54, 189, 37, 64, 180, 41, 72, 170, 46, 82, 160, 50, 91, 151, 56, 102, 140, 60, 111, 132, 65, 120, 121, 69, 130, 110, 75, 143, 101, 79, 150, 92, 83, 160, 81, 89, 168, 73, 93, 178, 62, 98, 188, 52, 102, 200, 43, 109, 209, 33, 112, 217, 24, 118, 228, 15, 122, 238, 24, 123, 228, 34, 122, 218, 43, 123, 208, 53, 123, 199, 62, 123, 189, 71, 122, 179, 80, 123, 170, 91, 123, 160, 100, 122, 150, 109, 122, 140, 118, 123, 130, 131, 123, 122, 142, 123, 112, 151, 122, 102, 160, 123, 92, 170, 123, 83, 179, 124, 72, 189, 122, 63, 199, 123, 52, 208, 123, 44, 219, 123, 33, 227, 123, 24, 237, 122, 15, 228, 117, 24, 218, 113, 33, 209, 107, 44, 200, 102, 51, 190, 98, 61, 178, 93, 74, 169, 90, 81, 162, 84, 94, 151, 79, 104, 141, 74, 113, 132, 70, 123, 122, 65, 132, 112, 61, 142, 103, 55, 151, 93, 51, 162, 83, 46, 173, 73, 43, 181, 63, 38, 190, 54, 33, 202, 44, 28, 209, 35, 23, 221, 25, 18, 231, 15, 14, 241, 25, 23, 230, 32, 32, 220, 42, 40, 213, 50, 49, 203, 59, 57, 194, 68, 67, 185, 78, 75, 176, 85, 84, 166, 93, 93, 157, 103, 102, 148, 113, 110, 139, 121, 118, 131, 130, 127, 120, 139, 136, 113, 148, 144, 103, 156, 154, 94, 164, 163, 85, 174, 172, 76, 182, 180, 66, 191, 189, 58, 201, 197, 49, 210, 207, 40, 217, 215, 31, 227, 223, 23, 235, 233, 14 }; + +const byte colorMap_whiteHot[] = { 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 20, 21, 21, 21, 22, 22, 22, 23, 23, 23, 24, 24, 24, 25, 25, 25, 26, 26, 26, 27, 27, 27, 28, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32, 32, 32, 33, 33, 33, 34, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 37, 38, 38, 38, 39, 39, 39, 40, 40, 40, 41, 41, 41, 42, 42, 42, 43, 43, 43, 44, 44, 44, 44, 44, 44, 45, 45, 45, 46, 46, 46, 47, 47, 47, 48, 48, 48, 49, 49, 49, 50, 50, 50, 51, 51, 51, 52, 52, 52, 53, 53, 53, 54, 54, 54, 55, 55, 55, 56, 56, 56, 57, 57, 57, 58, 58, 58, 59, 59, 59, 60, 60, 60, 61, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 65, 65, 65, 66, 66, 66, 67, 67, 67, 68, 68, 68, 69, 69, 69, 70, 70, 70, 71, 71, 71, 72, 72, 72, 73, 73, 73, 74, 74, 74, 75, 75, 75, 76, 76, 76, 77, 77, 77, 78, 78, 78, 79, 79, 79, 80, 80, 80, 81, 81, 81, 82, 82, 82, 83, 83, 83, 84, 84, 84, 85, 85, 85, 86, 86, 86, 87, 87, 87, 88, 88, 88, 89, 89, 89, 90, 90, 90, 91, 91, 91, 92, 92, 92, 93, 93, 93, 94, 94, 94, 95, 95, 95, 96, 96, 96, 97, 97, 97, 98, 98, 98, 99, 99, 99, 99, 99, 99, 100, 100, 100, 101, 101, 101, 102, 102, 102, 103, 103, 103, 104, 104, 104, 105, 105, 105, 106, 106, 106, 107, 107, 107, 108, 108, 108, 109, 109, 109, 110, 110, 110, 111, 111, 111, 112, 112, 112, 113, 113, 113, 114, 114, 114, 115, 115, 115, 116, 116, 116, 117, 117, 117, 118, 118, 118, 119, 119, 119, 120, 120, 120, 121, 121, 121, 122, 122, 122, 123, 123, 123, 124, 124, 124, 125, 125, 125, 126, 126, 126, 127, 127, 127, 128, 128, 128, 129, 129, 129, 130, 130, 130, 131, 131, 131, 132, 132, 132, 133, 133, 133, 134, 134, 134, 135, 135, 135, 136, 136, 136, 137, 137, 137, 138, 138, 138, 139, 139, 139, 140, 140, 140, 141, 141, 141, 142, 142, 142, 143, 143, 143, 144, 144, 144, 145, 145, 145, 146, 146, 146, 147, 147, 147, 148, 148, 148, 149, 149, 149, 150, 150, 150, 151, 151, 151, 152, 152, 152, 153, 153, 153, 154, 154, 154, 154, 154, 154, 155, 155, 155, 156, 156, 156, 157, 157, 157, 158, 158, 158, 159, 159, 159, 160, 160, 160, 161, 161, 161, 162, 162, 162, 163, 163, 163, 164, 164, 164, 165, 165, 165, 166, 166, 166, 167, 167, 167, 168, 168, 168, 169, 169, 169, 170, 170, 170, 171, 171, 171, 172, 172, 172, 173, 173, 173, 174, 174, 174, 175, 175, 175, 176, 176, 176, 177, 177, 177, 178, 178, 178, 179, 179, 179, 180, 180, 180, 181, 181, 181, 182, 182, 182, 183, 183, 183, 184, 184, 184, 185, 185, 185, 186, 186, 186, 187, 187, 187, 188, 188, 188, 189, 189, 189, 190, 190, 190, 191, 191, 191, 192, 192, 192, 193, 193, 193, 194, 194, 194, 195, 195, 195, 196, 196, 196, 197, 197, 197, 198, 198, 198, 199, 199, 199, 200, 200, 200, 201, 201, 201, 202, 202, 202, 203, 203, 203, 204, 204, 204, 205, 205, 205, 206, 206, 206, 207, 207, 207, 208, 208, 208, 209, 209, 209, 209, 209, 209, 210, 210, 210, 211, 211, 211, 212, 212, 212, 213, 213, 213, 214, 214, 214, 215, 215, 215, 216, 216, 216, 217, 217, 217, 218, 218, 218, 219, 219, 219, 220, 220, 220, 221, 221, 221, 222, 222, 222, 223, 223, 223, 224, 224, 224, 225, 225, 225, 226, 226, 226, 227, 227, 227, 228, 228, 228, 229, 229, 229, 230, 230, 230, 231, 231, 231, 232, 232, 232, 233, 233, 233, 234, 234, 234, 235, 235, 235 }; + +const byte colorMap_yellow[] = { 62, 16, 15, 63, 17, 16, 61, 18, 15, 62, 19, 16, 61, 20, 16, 61, 22, 15, 59, 22, 15, 60, 23, 16, 60, 23, 16, 60, 25, 15, 60, 25, 15, 61, 26, 16, 59, 27, 16, 58, 28, 14, 59, 29, 15, 59, 31, 14, 59, 31, 14, 60, 32, 15, 61, 34, 15, 59, 34, 15, 60, 36, 14, 60, 37, 15, 60, 37, 15, 61, 39, 14, 61, 39, 14, 60, 40, 15, 61, 42, 14, 61, 42, 14, 62, 43, 15, 63, 44, 15, 63, 46, 16, 64, 47, 15, 64, 47, 15, 65, 49, 14, 65, 50, 15, 66, 52, 14, 66, 52, 14, 68, 52, 15, 68, 54, 15, 68, 54, 15, 69, 55, 14, 70, 56, 15, 71, 57, 16, 72, 57, 14, 72, 59, 15, 73, 60, 14, 75, 61, 13, 76, 62, 14, 78, 64, 15, 78, 64, 15, 79, 65, 14, 80, 66, 15, 82, 67, 14, 83, 68, 15, 84, 69, 14, 84, 69, 14, 85, 71, 14, 88, 71, 15, 89, 72, 14, 90, 73, 15, 93, 75, 15, 94, 76, 14, 95, 78, 13, 98, 78, 14, 99, 79, 14, 100, 80, 15, 102, 81, 14, 103, 82, 15, 105, 82, 14, 106, 84, 13, 107, 85, 14, 110, 85, 14, 110, 85, 14, 113, 87, 14, 114, 88, 15, 117, 89, 14, 119, 91, 14, 120, 92, 14, 122, 93, 15, 123, 94, 14, 125, 95, 13, 126, 96, 14, 129, 96, 13, 130, 97, 14, 132, 98, 13, 133, 99, 13, 136, 100, 14, 138, 100, 13, 139, 101, 14, 141, 102, 13, 144, 105, 14, 147, 106, 14, 148, 107, 13, 150, 107, 14, 153, 108, 13, 154, 109, 14, 156, 110, 13, 157, 111, 12, 158, 112, 13, 160, 113, 13, 161, 114, 14, 164, 114, 13, 166, 115, 12, 167, 116, 13, 170, 116, 12, 172, 119, 13, 174, 119, 12, 176, 121, 14, 178, 122, 13, 179, 123, 14, 182, 124, 13, 183, 125, 13, 185, 125, 14, 185, 126, 12, 186, 127, 13, 189, 127, 12, 190, 128, 13, 192, 129, 12, 194, 131, 12, 195, 132, 13, 196, 134, 13, 199, 135, 13, 202, 136, 14, 203, 137, 13, 203, 137, 13, 205, 138, 12, 206, 139, 12, 207, 140, 13, 208, 141, 12, 209, 142, 13, 209, 142, 13, 212, 143, 12, 213, 144, 13, 215, 146, 13, 215, 147, 12, 217, 149, 12, 219, 149, 13, 220, 151, 12, 221, 152, 13, 221, 152, 11, 222, 153, 12, 223, 154, 13, 224, 155, 12, 224, 155, 12, 225, 157, 12, 226, 158, 13, 227, 159, 12, 228, 160, 13, 228, 160, 11, 229, 161, 12, 230, 163, 11, 231, 164, 12, 231, 166, 12, 231, 166, 12, 232, 167, 13, 233, 168, 12, 233, 168, 12, 234, 169, 13, 232, 170, 11, 233, 171, 12, 233, 171, 12, 234, 174, 12, 234, 174, 12, 235, 175, 11, 233, 176, 11, 235, 179, 12, 235, 179, 12, 235, 180, 13, 235, 181, 11, 236, 182, 12, 235, 182, 12, 236, 183, 13, 234, 184, 11, 235, 185, 12, 235, 187, 11, 235, 187, 11, 233, 188, 11, 234, 189, 12, 233, 190, 11, 234, 191, 12, 234, 193, 13, 234, 193, 11, 232, 194, 11, 232, 196, 12, 232, 196, 12, 231, 197, 13, 231, 198, 11, 231, 199, 12, 231, 199, 12, 231, 201, 11, 229, 202, 11, 230, 203, 12, 229, 204, 12, 229, 204, 11, 228, 206, 12, 227, 207, 12, 227, 208, 13, 225, 209, 11, 226, 210, 12, 225, 211, 12, 223, 212, 12, 223, 212, 12, 223, 214, 11, 223, 216, 12, 223, 216, 12, 221, 217, 10, 222, 218, 11, 221, 218, 11, 220, 220, 12, 220, 220, 12, 219, 223, 11, 219, 223, 11, 217, 224, 11, 217, 225, 12, 217, 226, 11, 215, 226, 11, 215, 228, 12, 216, 229, 11, 215, 230, 11, 215, 230, 11, 214, 232, 12, 213, 233, 12, 213, 233, 10, 212, 235, 11 }; diff --git a/Firmware_V2/src/general/colorschemes.h b/Firmware_V2/src/general/colorschemes.h new file mode 100644 index 0000000..76b187c --- /dev/null +++ b/Firmware_V2/src/general/colorschemes.h @@ -0,0 +1,41 @@ +/* +* +* COLOR SCHEMES - Contains 19 different color schemes to display the thermal image +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef COLORSCHEMES_H +#define COLORSCHEMES_H + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +extern const byte colorMap_arctic[]; +extern const byte colorMap_blackHot[]; +extern const byte colorMap_blueRed[]; +extern const byte colorMap_coldest[]; +extern const byte colorMap_contrast[]; +extern const byte colorMap_doubleRainbow[]; +extern const byte colorMap_grayRed[]; +extern const byte colorMap_glowBow[]; +extern const byte colorMap_grayscale[]; +extern const byte colorMap_hottest[]; +extern const byte colorMap_ironblack[]; +extern const byte colorMap_lava[]; +extern const byte colorMap_medical[]; +extern const byte colorMap_rainbow[]; +extern const byte colorMap_wheel1[]; +extern const byte colorMap_wheel2[]; +extern const byte colorMap_wheel3[]; +extern const byte colorMap_whiteHot[]; +extern const byte colorMap_yellow[]; + +#endif /* COLORSCHEMES_H */ diff --git a/Firmware_V2/src/general/globaldefines.h b/Firmware_V2/src/general/globaldefines.h new file mode 100644 index 0000000..364c8c0 --- /dev/null +++ b/Firmware_V2/src/general/globaldefines.h @@ -0,0 +1,284 @@ +/* +* +* GLOBAL DEFINES - Global defines, that are used firmware-wide +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef GLOBALDEFINES_H +#define GLOBALDEFINES_H + +#ifdef __cplusplus +extern "C" { +#endif + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +//Current firmware version +#define Version "Firmware 2.47 from 08.09.2018" +#define fwVersion 247 + +//Pins +#define pin_button 2 +#define pin_laser 4 +#define pin_touch_irq 5 +#define pin_lcd_dc 6 +#define pin_touch_cs 9 //Only resistive touch +#define pin_cam_cs 10 +#define pin_mosi 11 +#define pin_miso 12 +#define pin_sck 13 +#define pin_alt_sck 14 +#define pin_lepton_cs 15 +#define pin_sda 18 +#define pin_scl 19 +#define pin_sd_cs 20 +#define pin_lcd_cs 21 +#define pin_lcd_backlight 22 +#define pin_bat_measure 23 +#define pin_usb_measure A14 + +//Modes +#define displayMode_thermal 0 +#define displayMode_visual 1 +#define displayMode_combined 2 + +//TeensyVersion +#define teensyVersion_old 0 //Teensy 3.1 / 3.2 +#define teensyVersion_new 1 //Teensy 3.6 + +//MLX90614 sensor version +#define mlx90614Version_old 0 //MLX90614-BCI +#define mlx90614Version_new 1 //MLX90614-DCI, MLX90614-BCF, MLX90614-DCH + +//FLIR Lepton sensor version +#define leptonVersion_2_0_shutter 0 //FLIR Lepton2 Shuttered +#define leptonVersion_3_0_shutter 1 //FLIR Lepton3 Shuttered +#define leptonVersion_2_0_noShutter 2 //FLIR Lepton2 Non-Shuttered +#define leptonVersion_2_5_shutter 3 //FLIR Lepton 2.5 Shuttered +#define leptonVersion_3_5_shutter 3 //FLIR Lepton 3.5 Shuttered + +//Temperature format +#define tempFormat_celcius 0 +#define tempFormat_fahrenheit 1 + +//Filter type +#define filterType_none 0 +#define filterType_gaussian 1 +#define filterType_box 2 + +//Display Min/Max Points +#define minMaxPoints_disabled 0 +#define minMaxPoints_min 1 +#define minMaxPoints_max 2 +#define minMaxPoints_both 3 + +//Shutter mode +#define leptonShutter_manual 0 +#define leptonShutter_auto 1 +#define leptonShutter_none 2 +#define leptonShutter_autoRAD 3 + +//Text color +#define textColor_white 0 +#define textColor_black 1 +#define textColor_red 2 +#define textColor_green 3 +#define textColor_blue 4 + +//Screen off time +#define screenOffTime_disabled 0 +#define screenOffTime_5min 1 +#define screenOffTime_20min 2 + +//Hot / cold +#define hotColdMode_disabled 0 +#define hotColdMode_cold 1 +#define hotColdMode_hot 2 + +//Hot / cold color +#define hotColdColor_white 0 +#define hotColdColor_black 1 +#define hotColdColor_red 2 +#define hotColdColor_green 3 +#define hotColdColor_blue 4 + +//Lepton3.5 Gain mode +#define lepton_3_5_gain_high 0 +#define lepton_3_5_gain_low 1 + +//EEPROM registers +#define eeprom_leptonVersion 100 +#define eeprom_tempFormat 101 +#define eeprom_colorScheme 102 +#define eeprom_convertEnabled 103 +#define eeprom_visualEnabled 104 +#define eeprom_spotEnabled 105 +#define eeprom_colorbarEnabled 106 +#define eeprom_batteryEnabled 107 +#define eeprom_timeEnabled 108 +#define eeprom_dateEnabled 109 +#define eeprom_teensyVersion 110 +#define eeprom_storageEnabled 111 +#define eeprom_rotationVert 112 +#define eeprom_displayMode 113 +#define eeprom_textColor 114 +#define eeprom_filterType 115 +#define eeprom_minValue1Low 116 +#define eeprom_minValue1High 117 +#define eeprom_maxValue1Low 118 +#define eeprom_maxValue1High 119 +#define eeprom_minMax1Set 120 +#define eeprom_adjComb1Left 121 +#define eeprom_adjComb1Right 122 +#define eeprom_adjComb1Up 123 +#define eeprom_adjComb1Down 124 +#define eeprom_adjComb1Alpha 125 +#define eeprom_adjComb1Set 126 +#define eeprom_minMaxPoints 127 +#define eeprom_screenOffTime 128 +#define eeprom_massStorage 129 +#define eeprom_calSlopeSet 130 +#define eeprom_calSlopeBase 131 //4 Byte (131-134) +#define eeprom_hotColdMode 135 +#define eeprom_hotColdLevelLow 136 +#define eeprom_hotColdLevelHigh 137 +#define eeprom_hotColdColor 138 +#define eeprom_adjComb2Left 139 +#define eeprom_adjComb2Right 140 +#define eeprom_adjComb2Up 141 +#define eeprom_adjComb2Down 142 +#define eeprom_adjComb2Alpha 143 +#define eeprom_adjComb2Set 144 +#define eeprom_adjComb3Left 145 +#define eeprom_adjComb3Right 146 +#define eeprom_adjComb3Up 147 +#define eeprom_adjComb3Down 148 +#define eeprom_adjComb3Alpha 149 +#define eeprom_firstStart 150 +#define eeprom_liveHelper 151 +#define eeprom_adjComb3Set 152 +#define eeprom_adjCombPreset 153 +#define eeprom_minValue2Low 154 +#define eeprom_minValue2High 155 +#define eeprom_maxValue2Low 156 +#define eeprom_maxValue2High 157 +#define eeprom_minMax2Set 158 +#define eeprom_minValue3Low 159 +#define eeprom_minValue3High 160 +#define eeprom_maxValue3Low 161 +#define eeprom_maxValue3High 162 +#define eeprom_minMax3Set 163 +#define eeprom_minMaxPreset 164 +#define eeprom_adjComb1Factor 165 +#define eeprom_adjComb2Factor 166 +#define eeprom_adjComb3Factor 167 +#define eeprom_hqRes 168 +#define eeprom_noShutter 169 +#define eeprom_batComp 170 +#define eeprom_rotationHorizont 171 +#define eeprom_minMax1Comp 172 //4 Byte (172-175) +#define eeprom_minMax2Comp 176 //4 Byte (176-179) +#define eeprom_minMax3Comp 180 //4 Byte (180-183) +#define eeprom_lepton_3_5_gain 184 +#define eeprom_fwVersion 250 +#define eeprom_setValue 200 + +//Presets for min/max & adjComb +#define minMax_temporary 0 +#define minMax_preset1 1 +#define minMax_preset2 2 +#define minMax_preset3 3 +#define adjComb_temporary 0 +#define adjComb_preset1 1 +#define adjComb_preset2 2 +#define adjComb_preset3 3 + +//Hardware diagnostic bit codes +#define diag_spot 0 +#define diag_display 1 +#define diag_camera 2 +#define diag_touch 3 +#define diag_sd 4 +#define diag_bat 5 +#define diag_lep_conf 6 +#define diag_lep_data 7 +#define diag_ok 255 + +//Color scheme numbers +#define colorSchemeTotal 19 +#define colorScheme_arctic 0 +#define colorScheme_blackHot 1 +#define colorScheme_blueRed 2 +#define colorScheme_coldest 3 +#define colorScheme_contrast 4 +#define colorScheme_doubleRainbow 5 +#define colorScheme_grayRed 6 +#define colorScheme_glowBow 7 +#define colorScheme_grayscale 8 +#define colorScheme_hottest 9 +#define colorScheme_ironblack 10 +#define colorScheme_lava 11 +#define colorScheme_medical 12 +#define colorScheme_rainbow 13 +#define colorScheme_wheel1 14 +#define colorScheme_wheel2 15 +#define colorScheme_wheel3 16 +#define colorScheme_whiteHot 17 +#define colorScheme_yellow 18 + +//Calibration +#define cal_warmup 0 +#define cal_standard 1 +#define cal_stdSlope 0.0300f //Standard slope value + +//Image save marker +#define imgSave_disabled 0 +#define imgSave_save 1 +#define imgSave_set 2 +#define imgSave_create 3 + +//Video save marker +#define videoSave_disabled 0 +#define videoSave_menu 1 +#define videoSave_recording 2 +#define videoSave_processing 3 + +//Show menu state +#define showMenu_disabled 0 +#define showMenu_desired 1 +#define showMenu_opened 2 + +//Load touch decision marker +#define loadTouch_none 0 +#define loadTouch_find 1 +#define loadTouch_delete 2 +#define loadTouch_previous 3 +#define loadTouch_next 4 +#define loadTouch_exit 5 +#define loadTouch_convert 6 +#define loadTouch_middle 7 + +//Visual camera resolution +#define camera_resLow 0 //160x120 for PTC-06 / PTC-08 and 320x240 for Arducam +#define camera_resMiddle 1 //320x240 for Arducam and PTC-06 / PTC-08 +#define camera_resHigh 2 //640x480 for Arducam and PTC-06 / PTC-08 + +//Visual camera transfer modes +#define camera_stream 0 +#define camera_save 1 +#define camera_serial 2 + +#ifdef __cplusplus +} +#endif + +#endif /* GLOBALDEFINES_H */ diff --git a/Firmware_V2/src/general/globalvariables.cpp b/Firmware_V2/src/general/globalvariables.cpp new file mode 100644 index 0000000..6809884 --- /dev/null +++ b/Firmware_V2/src/general/globalvariables.cpp @@ -0,0 +1,170 @@ +/* +* +* GLOBAL VARIABLES - Global variable declarations, that are used firmware-wide +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include + +/*############################# PUBLIC VARIABLES ##############################*/ + +//320x240 buffer (Teensy 3.6 only) +unsigned short* bigBuffer; +//160x120 buffer +unsigned short* smallBuffer; + +//Timer +Metro screenOff; +boolean screenPressed; +byte screenOffTime; + +//Button Debouncer +Bounce buttonDebouncer(pin_button, 200); +Bounce touchDebouncer(pin_touch_irq, 200); + +//SD +SdFat sd; +SdFile sdFile; +String sdInfo; + +//Save filename +char saveFilename[20]; + +//Battery ADC +ADC *batMeasure; +//Battery +int8_t batPercentage; +long batTimer; +int8_t batComp; + +//Convert RAW to BMP +bool convertEnabled; +//Save visual image +bool visualEnabled; +//Automatic mode +bool autoMode; +//Lock current limits +bool limitsLocked; +//Vertical display rotation +bool rotationVert; +bool rotationHorizont; + +//Display options +bool batteryEnabled; +bool timeEnabled; +bool dateEnabled; +bool spotEnabled; +bool colorbarEnabled; +bool storageEnabled; +byte filterType; +byte minMaxPoints; + +//Temperature format +bool tempFormat; + +//Text color +byte textColor; + +//Laser state +bool laserEnabled; + +//Display mode +byte displayMode; + +//Resolution, V2 only +bool hqRes; + +//Gain mode, Lepton3.5 only +bool gainMode; + +//FLIR Lepton sensor version +byte leptonVersion; +//FLIR Lepton Shutter mode +byte leptonShutter; + +//MLX90614 sensor version +boolean mlx90614Version; + +//Teensy version +byte teensyVersion; + +//HW diagnostic information +byte diagnostic = diag_ok; + +//Current color scheme - standard is rainbow +byte colorScheme; +//Pointer to the current color scheme +const byte *colorMap; +//Number of rgb elements inside the color scheme +int16_t colorElements; + +//Calibration offset +float calOffset; +//Calibration slope +float calSlope; +//Calibration status +byte calStatus; +//Calibration compensation +float calComp; +//Calibration warmup timer +long calTimer; + +//Min & max lepton raw values +uint16_t maxValue; +uint16_t minValue; + +//Spot & ambient temperature +float spotTemp; +float ambTemp; + +//Position of min and maxtemp +uint16_t minTempPos; +uint16_t minTempVal; +uint16_t maxTempPos; +uint16_t maxTempVal; + +//Hot / Cold mode +byte hotColdMode; +int16_t hotColdLevel; +byte hotColdColor; + +//Array to store the tempPoints +uint16_t tempPoints[96][2]; + +//Adjust combined image +float adjCombAlpha; +float adjCombFactor; +byte adjCombLeft; +byte adjCombRight; +byte adjCombUp; +byte adjCombDown; + +//Save Image in the next cycle +volatile byte imgSave; +//Save Video in the next cycle +volatile byte videoSave; +//Show Live Mode Menu in the next cycle +volatile byte showMenu; +//Handler for a long touch press +volatile bool longTouch; +//Check if in serial mode +volatile bool serialMode; +//Load touch decision marker +volatile byte loadTouch; diff --git a/Firmware_V2/src/general/globalvariables.h b/Firmware_V2/src/general/globalvariables.h new file mode 100644 index 0000000..63cf703 --- /dev/null +++ b/Firmware_V2/src/general/globalvariables.h @@ -0,0 +1,101 @@ +/* +* +* GLOBAL VARIABLES - Global variable declarations, that are used firmware-wide +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef GLOBALVARIABLES_H +#define GLOBALVARIABLES_H + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +extern unsigned short* bigBuffer; +extern unsigned short* smallBuffer; +extern Metro screenOff; +extern boolean screenPressed; +extern byte screenOffTime; +extern Bounce buttonDebouncer; +extern Bounce touchDebouncer; +extern SdFat sd; +extern SdFile sdFile; +extern String sdInfo; +extern char saveFilename[20]; +extern ADC *batMeasure; +extern int8_t batPercentage; +extern long batTimer; +extern int8_t batComp; +extern bool convertEnabled; +extern bool visualEnabled; +extern bool autoMode; +extern bool limitsLocked; +extern bool rotationVert; +extern bool rotationHorizont; +extern bool batteryEnabled; +extern bool timeEnabled; +extern bool dateEnabled; +extern bool spotEnabled; +extern bool colorbarEnabled; +extern bool storageEnabled; +extern byte filterType; +extern byte minMaxPoints; +extern bool tempFormat; +extern byte textColor; +extern bool laserEnabled; +extern byte displayMode; +extern bool hqRes; +extern bool gainMode; +extern byte leptonVersion; +extern byte leptonShutter; +extern boolean mlx90614Version; +extern byte teensyVersion; +extern byte diagnostic; +extern byte colorScheme; +extern const byte *colorMap; +extern int16_t colorElements; +extern float calOffset; +extern float calSlope; +extern byte calStatus; +extern float calComp; +extern long calTimer; +extern uint16_t maxValue; +extern uint16_t minValue; +extern float spotTemp; +extern float ambTemp; +extern uint16_t minTempPos; +extern uint16_t minTempVal; +extern uint16_t maxTempPos; +extern uint16_t maxTempVal; +extern byte hotColdMode; +extern int16_t hotColdLevel; +extern byte hotColdColor; +extern uint16_t tempPoints[96][2]; +extern float adjCombAlpha; +extern float adjCombFactor; +extern byte adjCombLeft; +extern byte adjCombRight; +extern byte adjCombUp; +extern byte adjCombDown; +extern volatile byte imgSave; +extern volatile byte videoSave; +extern volatile byte showMenu; +extern volatile bool longTouch; +extern volatile bool serialMode; +extern volatile byte loadTouch; + +#endif /* GLOBALVARIABLES_H */ diff --git a/Firmware_V2/src/gui/bitmaps.cpp b/Firmware_V2/src/gui/bitmaps.cpp new file mode 100644 index 0000000..6b9184b --- /dev/null +++ b/Firmware_V2/src/gui/bitmaps.cpp @@ -0,0 +1,1927 @@ +/* +* +* BITMAPS - Icons and graphics shown inside the GUI +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include + +/*############################# PUBLIC VARIABLES ##############################*/ + +/* Main Menu Icon 1 - Change color */ + +const uint16_t icon1Colors[] = { + 0xDBE5, 0xDC89, 0xFFFF, 0xFFDF +}; + +const uint8_t icon1Bitmap[] = { + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xFF, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAF, 0x54, 0x00, 0x01, 0x7E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0xD0, 0x00, 0x00, 0x00, 0x01, 0x7A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xCE, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0x06, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7A, 0xAA, 0xAA, 0xAA, 0xAA, 0xB4, 0x03, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xAA, 0xAA, 0xAA, 0xAF, 0x40, 0x03, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, 0xAA, 0xAA, 0xD0, 0x00, 0x01, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAA, 0xA9, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xAA, 0xB4, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x00, 0x01, 0xEA, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xEA, 0x90, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0xAA, 0xAA, 0xC0, 0x00, 0x00, 0x03, 0xAA, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7A, 0xC0, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0xAA, 0xAB, 0x00, 0x00, 0x00, 0x06, 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x40, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x0E, 0xAA, 0xAC, 0x00, 0x00, 0x5D, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0xAA, 0xB4, 0x00, 0x00, 0x00, 0x0E, 0xAA, 0xAC, 0x00, 0x01, 0xAA, 0xD0, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0xAA, + 0xAA, 0xAA, 0x90, 0x00, 0x00, 0x00, 0x06, 0xAA, 0xA4, 0x00, 0x06, 0xAA, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, + 0xAA, 0xAA, 0xC0, 0x00, 0x00, 0x00, 0x03, 0xAA, 0xB0, 0x00, 0x0E, 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, + 0xAA, 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0xEA, 0xC0, 0x00, 0x0E, 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0xAA, + 0xAA, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x0E, 0xAA, 0xAC, 0x00, 0x00, 0x04, 0x00, 0x00, 0x3A, 0xAA, + 0xAA, 0xA9, 0x00, 0x01, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, 0xA4, 0x00, 0x00, 0x39, 0x00, 0x01, 0xEA, 0xAA, + 0xAA, 0xAC, 0x00, 0x06, 0xAA, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xAA, 0x90, 0x00, 0x00, 0x6A, 0xD0, 0x1E, 0xAA, 0xAA, + 0xAA, 0xA4, 0x00, 0x0E, 0xAA, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x40, 0x00, 0x01, 0xEA, 0xC0, 0x3A, 0xAA, 0xAA, + 0xAA, 0xB0, 0x00, 0x1A, 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xAA, 0x40, 0x6A, 0xAA, 0xAA, + 0xAA, 0x90, 0x00, 0x1A, 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0xAB, 0x01, 0xEA, 0xAA, 0xAA, + 0xAA, 0x90, 0x00, 0x1A, 0xAA, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAD, 0x03, 0xAA, 0xAA, 0xAA, + 0xAA, 0x80, 0x00, 0x0E, 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0xA4, 0x06, 0xAA, 0xAA, 0xAA, + 0xAA, 0xC0, 0x00, 0x07, 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, 0xB0, 0x0E, 0xAA, 0xAA, 0xAA, + 0xAA, 0xC0, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, 0x90, 0x1A, 0xAA, 0xAA, 0xAA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xAA, 0x40, 0x3A, 0xAA, 0xAA, 0xAA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0xAB, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xA9, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7A, 0xA4, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, 0xB0, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, 0xD0, 0x00, 0xEA, 0xAA, 0xAA, 0xAA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xAA, 0x40, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0x40, 0x00, 0x07, 0xAD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAB, 0x00, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0x40, 0x00, 0x1E, 0xAB, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAC, 0x00, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xC0, 0x00, 0x3A, 0xAA, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0xA4, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xC0, 0x00, 0x6A, 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0x90, 0x00, 0x7A, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0x80, 0x00, 0x6A, 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x7D, 0x00, 0xEA, 0xC0, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0x90, 0x00, 0x2A, 0xAA, 0xC0, 0x00, 0x00, 0x00, 0x03, 0xA9, 0x01, 0xAB, 0x00, 0x1E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xB0, 0x00, 0x3A, 0xAA, 0x40, 0x00, 0x00, 0x00, 0x0E, 0xAC, 0x03, 0xAD, 0x01, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xB0, 0x00, 0x0E, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xA4, 0x06, 0xA4, 0x07, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA4, 0x00, 0x01, 0x54, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xB0, 0x1E, 0x90, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x90, 0x1A, 0x40, 0x3A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xC0, 0x3B, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x40, 0x6C, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAB, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x40, 0xF0, 0x07, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x01, 0xC0, 0x1E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x01, 0x00, 0x7A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x01, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x1E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE9, 0x00, 0x7A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAB, 0x47, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x07, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAB, 0x40, 0x00, 0x00, 0x00, 0x1E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xD0, 0x00, 0x00, 0x00, 0x7A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAD, 0x00, 0x00, 0x07, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xF5, 0x01, 0x7A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA +}; + +/* Main Menu Icon 2 - Change mode */ + +const uint16_t icon2Colors[] = { + 0x4249, 0x7BF0, 0x7C10, 0xFFDF +}; + +const uint8_t icon2Bitmap[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0xAA, 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, + 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, + 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, + 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xE4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0x90, 0x06, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x2F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x03, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x07, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF8, 0x00, 0x00, 0x2F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0x94, 0x1B, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0xFF, 0xFF, 0xE4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, + 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, + 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, + 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, + 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +/* Main Menu Icon 3 - Temperature limits */ + +const uint16_t icon3Colors[] = { + 0x7ACB, 0x8BAF, 0x93F0, 0xFFDF +}; + +const uint8_t icon3Bitmap[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA5, 0x5B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x02, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x02, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xAA, 0xAB, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x06, 0x40, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x2F, 0xE0, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, 0xFF, 0xFF, 0xEA, 0xAA, 0xAA, 0xAF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x03, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x6A, 0xAA, 0x6B, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xF8, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0xFF, 0xFF, 0xE0, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0x80, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x0B, 0xFF, 0xFF, 0xAF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xE9, 0x65, 0x55, 0x40, 0x00, 0x2F, 0xFF, 0xFE, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xF8, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xE0, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0x80, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFE, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xF8, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xEA, 0xAA, 0xAA, 0xAA, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x90, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFA, 0xBF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0xBF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0xBF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0xBF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0xBF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0xBF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xAA, 0xA0, 0x6A, 0xAF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0xBF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0xBF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x40, 0x06, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +/* Main Menu Icon 4 - Load menu */ + +const uint16_t icon4Colors[] = { + 0xDBE5, 0xDCAA, 0xFFDF, 0xFFDF +}; + +const uint8_t icon4Bitmap[] = { + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xB5, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7A, 0xAA, + 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAA, + 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0x01, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x40, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0x00, 0x0A, 0xAA, + 0xAA, 0xA5, 0x55, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0x55, 0x5A, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAB, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xEA, + 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, + 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, + 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, + 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, + 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, + 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, + 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, + 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, + 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, + 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, + 0xAA, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, + 0xAA, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xAA, + 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xAA, + 0xAA, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xAA, + 0xAA, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, + 0xAA, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, + 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, + 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, + 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, + 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, + 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, + 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0xAA, + 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, + 0xAA, 0xAB, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7A, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA +}; + +/* Main Menu Icon 5 - Mass storage or Shutter */ + +const uint16_t icon5Colors[] = { + 0x4249, 0x9CF3, 0xCE9A, 0xFFFF +}; + +const uint8_t icon5Bitmap_1[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x02, 0xAA, 0xA0, 0x0A, 0xAA, 0x80, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x03, 0xFF, 0xF0, 0x0F, 0xFF, 0xC0, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x03, 0xFF, 0xF0, 0x0F, 0xFF, 0xC0, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x03, 0xFF, 0xF0, 0x0F, 0xFF, 0xC0, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x03, 0xFF, 0xF0, 0x0F, 0xFF, 0xC0, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x03, 0xFF, 0xF0, 0x0F, 0xFF, 0xC0, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x02, 0xFF, 0xF0, 0x0F, 0xFF, 0x80, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE4, 0x00, 0x00, 0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA5, 0x5A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +#if defined(__MK66FX1M0__) +const uint8_t icon5Bitmap_2[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0x54, 0x16, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x02, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x02, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x07, 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x2F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0B, 0xFD, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x0B, 0xFC, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, + 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x0B, 0xFC, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFC, 0x00, 0x02, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFC, 0x00, 0x3F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFC, 0x07, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFC, 0xBF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, + 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, + 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, + 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, + 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x03, 0xFF, + 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x03, 0xFF, + 0xFF, 0xCF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x02, 0xFF, + 0xFF, 0x82, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x01, 0xFF, + 0xFF, 0x80, 0x3F, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x40, 0x0B, 0xFF, 0xF4, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x40, 0x01, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xC0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x0B, 0xFF, 0xF8, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x0B, 0xFF, 0xF8, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xD0, 0x00, 0xFF, + 0xFF, 0x40, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0xFF, + 0xFF, 0x80, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0x40, 0xFF, + 0xFF, 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x2F, 0xFF, 0xE2, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, + 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, + 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFE, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, + 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xE0, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFD, 0x00, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xD0, 0x00, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, + 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x7F, 0xD0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0xBF, 0xC0, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFD, 0x00, 0x07, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xC0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0xBF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xC0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xCB, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0x40, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0x40, 0x01, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; +#else +const uint8_t* icon5Bitmap_2 = NULL; +#endif + +/* Main Menu Icon 6 - Settings */ + +const uint16_t icon6Colors[] = { + 0x7AEC, 0x8B6E, 0x93F0, 0xFFDF +}; + +const uint8_t icon6Bitmap[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xAA, 0xAB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xD1, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x8B, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x2F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFD, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x06, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xE0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, + 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, + 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x1F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x1B, 0xFF, 0xE0, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0xBF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xF8, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xE6, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0x4B, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +/* Main Menu Icon 7 - Display settings */ + +const uint16_t icon7Colors[] = { + 0xD3E5, 0xDC89, 0xFFDF, 0xFFDF +}; + +const uint8_t icon7Bitmap[] = { + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAB, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xEA, 0xAA, + 0xAA, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0xAA, + 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xAA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, + 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, + 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xA9, 0x00, 0x05, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x50, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAF, 0xFA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x90, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x90, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xB5, 0xAA, 0xC0, 0x03, 0xAA, 0xDE, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xC0, 0x7D, 0x00, 0x00, 0x7D, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xC0, 0x00, 0x5F, 0xF5, 0x00, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0x90, 0x01, 0xAA, 0xAA, 0x40, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xC0, 0x0E, 0xAA, 0xAA, 0xB0, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0x40, 0x1A, 0xAA, 0xAA, 0xA4, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xBF, 0x40, 0x3A, 0xAA, 0xAA, 0xAD, 0x00, 0xFA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x6A, 0xAA, 0xAA, 0xA9, 0x00, 0x05, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x6A, 0xAA, 0xAA, 0xAB, 0x00, 0x01, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0xEA, 0xAA, 0xAA, 0xAB, 0x00, 0x01, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0xEA, 0xAA, 0xAA, 0xAB, 0x00, 0x01, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x6A, 0xAA, 0xAA, 0xA9, 0x00, 0x01, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xF5, 0x00, 0x6A, 0xAA, 0xAA, 0xA9, 0x00, 0x5F, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0x40, 0x3A, 0xAA, 0xAA, 0xAC, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xC0, 0x1E, 0xAA, 0xAA, 0xB4, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0x90, 0x07, 0xAA, 0xAA, 0xD0, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0x90, 0x00, 0x7A, 0xAF, 0x00, 0x07, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x15, 0x54, 0x00, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0x40, 0x14, 0x00, 0x00, 0x14, 0x01, 0xEA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0x91, 0xEB, 0x40, 0x01, 0xEB, 0x46, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAF, 0xAA, 0x80, 0x06, 0xAA, 0xFA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x90, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x90, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB5, 0x5E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x6A, + 0xA9, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x6A, + 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xAA, + 0xAA, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0xAA, + 0xAA, 0xAB, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x50, 0x00, 0x00, 0x05, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xEA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xBF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFE, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAB, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xEA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAB, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xEA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA +}; + +/* Main Menu Icon 8 - Laser or HQ */ + +const uint16_t icon8Colors[] = { + 0x4A6A, 0x7BF0, 0x7C10, 0xFFFF +}; + +const uint8_t icon8Bitmap_1[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xBF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xBF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xBB, 0x7A, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xD6, 0xAB, 0xEF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDA, 0x00, 0x6E, 0x6F, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0x00, 0x07, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE9, 0x00, 0x0A, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x0F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE5, 0x00, 0x29, 0xBF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x6D, 0x00, 0xAF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0xAB, 0xBB, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xBB, 0x7E, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0x7F, 0xAF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x3F, 0xFF, 0xDF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE9, 0x54, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +#if defined(__MK66FX1M0__) +const uint8_t icon8Bitmap_2[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xE4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0xFF, 0xFF, + 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, + 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, + 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, + 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, + 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, + 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, + 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, + 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, + 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, + 0xFF, 0xFF, 0xE4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; +#else +const uint8_t* icon8Bitmap_2 = NULL; +#endif + + +/* Main Menu Icon 9 - Display off */ + +const uint16_t icon9Colors[] = { + 0x7AEC, 0x8B8E, 0x93F0, 0xFFDF +}; + +const uint8_t icon9Bitmap[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xFF, 0xFF, + 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, + 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, + 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFE, 0x00, 0x0A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x2F, 0xC0, 0x2F, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x2F, 0xC0, 0x2F, 0xC0, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x2F, 0xC0, 0x2F, 0xC0, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x2F, 0xC0, 0x2F, 0xC0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x3F, 0xC0, 0x2F, 0xE0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0xBF, 0xD0, 0x2F, 0xF4, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFC, 0x02, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFC, 0x02, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xFF, 0xF8, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x2F, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0xBF, 0xF8, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0xAF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, + 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, + 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, + 0xFF, 0xFF, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA0, 0x00, 0x00, 0x0A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +/* Main Menu Icon 10 - Calibration */ + +const uint16_t icon10Colors[] = { + 0xDBE5, 0xDCAA, 0xFFDF, 0xFFDF +}; + +const uint8_t icon10Bitmap[] = { + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xA9, 0x0A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xA4, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA0, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0x90, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0x40, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0x40, 0x00, 0x0A, 0xA9, 0x1A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x00, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0x80, 0x00, 0x02, 0xA4, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA0, 0x00, 0x00, 0x06, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0x90, 0x00, 0x00, 0x90, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x01, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x69, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, + 0xAA, 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x1A, 0x40, 0x00, 0x00, 0x1A, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x00, 0x00, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xA0, 0x06, 0x90, 0x00, 0x00, 0x06, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0x90, 0x00, 0x00, 0x00, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x01, 0xA4, 0x00, 0x00, 0x01, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0x29, 0x00, 0x29, 0x00, 0x00, 0x00, 0x6A, 0xAA, + 0xAA, 0xAA, 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAA, 0xAA, 0xA8, 0x0A, 0x40, 0x0A, 0x40, 0x00, 0x00, 0x2A, 0xAA, + 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, 0xAA, 0xA9, 0x02, 0x90, 0x02, 0x90, 0x00, 0x00, 0x1A, 0xAA, + 0xAA, 0xAA, 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, 0xAA, 0x86, 0x80, 0xA4, 0x00, 0xA4, 0x00, 0x00, 0x2A, 0xAA, + 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x01, 0xA0, 0x28, 0x00, 0x28, 0x00, 0x00, 0x6A, 0xAA, + 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xA9, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0xAA, 0x40, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xA4, 0x06, 0x80, 0x00, 0x00, 0x00, 0x1A, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, 0x41, 0xA0, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xA5, 0x68, 0x00, 0x00, 0x01, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, 0x9A, 0x00, 0x00, 0x06, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0x00, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, 0x90, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xA9, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x90, 0x00, 0x00, 0x2A, 0x90, 0x00, 0x00, 0x00, 0x06, 0xAA, 0x96, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x2A, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0x40, 0x00, 0x1A, 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x05, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x90, 0x00, 0x0A, 0x9A, 0xAA, 0x40, 0x00, 0x00, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA0, 0x00, 0x06, 0x91, 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x02, 0xA0, 0x1A, 0xAA, 0x40, 0x00, 0x00, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x00, 0x01, 0xA8, 0x01, 0x6A, 0xA5, 0x00, 0x00, 0x00, 0x01, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0x00, 0x00, 0xAA, 0x00, 0x06, 0xAA, 0x90, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x6A, 0x80, 0x00, 0x6A, 0xA9, 0x00, 0x00, 0x00, 0x1A, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x2A, 0xA0, 0x00, 0x06, 0xAA, 0x90, 0x00, 0x00, 0x0A, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x2A, 0x80, 0x00, 0x2A, 0x64, 0x00, 0x00, 0x6A, 0xA9, 0x40, 0x00, 0x06, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x0A, 0x90, 0x00, 0x1A, 0x90, 0x00, 0x01, 0xAA, 0xAA, 0xA4, 0x00, 0x02, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0x02, 0xA0, 0x00, 0x0A, 0x90, 0x00, 0x06, 0xAA, 0xAA, 0xAA, 0x40, 0x01, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x86, 0x41, 0xA4, 0x00, 0x06, 0x90, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x1A, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x01, 0x90, 0xA8, 0x00, 0x02, 0xA0, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, 0x50, 0x01, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x00, 0x64, 0x69, 0x00, 0x01, 0xA4, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0x50, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x19, 0x6A, 0x00, 0x00, 0xA8, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0x86, 0x80, 0x06, 0x2A, 0x40, 0x00, 0x69, 0x1A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0x41, 0xA0, 0x00, 0x1A, 0x80, 0x00, 0x2A, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0x90, 0x68, 0x00, 0x0A, 0x90, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xA1, 0xA4, 0x1A, 0x00, 0x06, 0xA0, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0x80, 0x69, 0x06, 0x80, 0x02, 0xA4, 0x00, 0x0A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0x40, 0x1A, 0x41, 0xA0, 0x01, 0xA8, 0x00, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0x90, 0x06, 0x80, 0x68, 0x00, 0xA9, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xA0, 0xA4, 0x01, 0x90, 0x1A, 0x00, 0x6A, 0x00, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0x80, 0x29, 0x00, 0x00, 0x06, 0x00, 0x2A, 0x40, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0x00, 0x0A, 0x40, 0x00, 0x00, 0x00, 0x1A, 0x40, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x02, 0x90, 0x00, 0x00, 0x00, 0x0A, 0x80, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0x90, 0x00, 0x00, 0xA4, 0x00, 0x00, 0x00, 0x0A, 0x90, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0x80, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x1A, 0xA0, 0x00, 0x0A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0x40, 0x00, 0x00, 0x0A, 0x40, 0x00, 0x00, 0x6A, 0xA4, 0x00, 0x0A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0x40, 0x00, 0x00, 0x02, 0x90, 0x00, 0x01, 0xAA, 0xA8, 0x00, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0x90, 0x00, 0x00, 0x00, 0x40, 0x00, 0x06, 0xAA, 0xA9, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAA, 0xAA, 0x00, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, 0x40, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0x40, 0x00, 0x00, 0x00, 0x01, 0xAA, 0xAA, 0xAA, 0x90, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0x90, 0x00, 0x00, 0x00, 0x06, 0xAA, 0xAA, 0xAA, 0xA9, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x1A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0x1A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x5A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0x90, 0x00, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x9A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA6, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xA9, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0x42, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA +}; + +/* Main Menu Icon 11_1 - Hot / cold */ + +const uint16_t icon11_1Colors[] = { + 0x4249, 0x7BF0, 0x7C10, 0xFFDF +}; + +const uint8_t icon11_1Bitmap[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x2F, 0xFD, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x2F, 0xF0, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x2F, 0xE0, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x2F, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xF0, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x0B, 0xE0, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x02, 0x80, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xE0, 0x00, 0x00, 0x0F, 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xD0, 0x00, 0x00, 0x2F, 0x80, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xC0, 0x18, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xC0, 0x2C, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x40, 0x00, 0x07, 0xFF, 0xE0, 0x3E, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x40, 0x00, 0x0F, 0xFF, 0xF0, 0xBF, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x40, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0xBF, 0xFF, 0xFE, 0x1F, 0xE0, 0x3F, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xC0, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, 0xFF, 0xFC, 0x03, 0xE0, 0x3E, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0xFC, 0x00, 0xE0, 0x3C, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xD0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0xFF, 0xF9, 0xFE, 0x00, 0x10, 0x10, 0x03, 0xF9, 0xBF, 0xFF, 0xFF, + 0xFF, 0xE0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x02, 0xFF, 0xE0, 0x3F, 0x80, 0x00, 0x00, 0x0F, 0xE0, 0x3F, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xE0, 0x2F, 0xE0, 0x00, 0x00, 0x3F, 0xD0, 0x3F, 0xFF, 0xFF, + 0xFF, 0xFC, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xF0, 0x2F, 0xF8, 0x00, 0x00, 0xFF, 0xC0, 0x3F, 0xFF, 0xFF, + 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x40, 0x2F, 0xEF, 0xF0, 0x0F, 0xFE, 0x00, 0x03, 0xFF, 0xC0, 0x7F, 0xAF, 0xFF, + 0xFF, 0xFF, 0x40, 0x1F, 0xFF, 0xFF, 0xFD, 0x00, 0x7F, 0x02, 0xF4, 0x0F, 0xFF, 0x80, 0x0F, 0xFF, 0x80, 0xBD, 0x0B, 0xFF, + 0xFF, 0xFF, 0xD0, 0x07, 0xFF, 0xFF, 0xF4, 0x02, 0xFF, 0x00, 0x28, 0x0B, 0xFF, 0xE0, 0x3F, 0xFF, 0x40, 0xD0, 0x03, 0xFF, + 0xFF, 0xFF, 0xF8, 0x00, 0x6F, 0xFE, 0x40, 0x0B, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xE0, 0x3F, 0xFF, 0x00, 0x00, 0x0B, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x40, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xE0, 0x3F, 0xFF, 0x00, 0x00, 0x2F, 0xFF, + 0xFF, 0xFF, 0xFF, 0xE4, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFD, 0x00, 0x03, 0xFF, 0xE0, 0x3F, 0xFE, 0x00, 0x02, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xA9, 0x5A, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0xBF, 0x80, 0x0B, 0xF4, 0x00, 0x0B, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA0, 0x00, 0x00, 0x18, 0x00, 0x02, 0x80, 0x00, 0x00, 0x6F, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x02, 0xF4, 0x00, 0x0B, 0x00, 0x00, 0xB9, 0x00, 0x01, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x02, 0xBF, 0xFE, 0x40, 0x7F, 0xE0, 0x0B, 0xFF, 0xE9, 0x02, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0xFF, 0xFF, 0x80, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xEF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0xBF, 0xF4, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x0A, 0xFF, 0xFF, 0x80, 0xBF, 0xF0, 0x0B, 0xFF, 0xFE, 0x43, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x0B, 0xF8, 0x00, 0x0B, 0x80, 0x00, 0xFE, 0x40, 0x01, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x04, 0x00, 0x00, 0x40, 0x00, 0x00, 0x1B, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x7F, 0x40, 0x0B, 0xE0, 0x00, 0x06, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x02, 0xFF, 0xD0, 0x2F, 0xFD, 0x00, 0x07, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x03, 0xFF, 0xE0, 0x3F, 0xFE, 0x00, 0x00, 0xBF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xE0, 0x3F, 0xFF, 0x00, 0x00, 0x0B, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x18, 0x0B, 0xFF, 0xE0, 0x3F, 0xFF, 0x40, 0x80, 0x03, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xB8, 0x0F, 0xFF, 0xC0, 0x2F, 0xFF, 0x80, 0xF8, 0x07, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8B, 0xF0, 0x0F, 0xFF, 0x00, 0x07, 0xFF, 0x80, 0x7F, 0x5F, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x1F, 0xFC, 0x00, 0x01, 0xFF, 0xC0, 0x3F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x2F, 0xF0, 0x00, 0x00, 0xBF, 0xD0, 0x3F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x3F, 0xC0, 0x00, 0x00, 0x1F, 0xE0, 0x3F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xBF, 0x00, 0x00, 0x00, 0x07, 0xF4, 0xBF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0xE0, 0x38, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x02, 0xE0, 0x3E, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x0F, 0xE0, 0x3F, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +/* Main Menu Icon 11_2 - Adjust combined */ + +const uint16_t icon11_2Colors[] = { + 0x4269, 0x7BF0, 0x7C10, 0xF7BE +}; + +const uint8_t icon11_2Bitmap[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x00, 0x01, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x54, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xE9, 0x55, 0x55, 0x55, 0x54, 0x00, 0x01, 0x55, 0x55, 0x55, 0x56, 0xBF, 0xFF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x55, 0x55, 0x55, 0x40, 0x00, 0xBF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFE, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFE, 0x00, 0x01, 0x55, 0x55, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFF, 0xFF, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +/* Main Menu Icon 12 - Temperature points */ + +const uint16_t icon12Colors[] = { + 0x72AB, 0x8B6E, 0x93F0, 0xFFBE +}; + +const uint8_t icon12Bitmap[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0x69, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xC2, 0xFF, 0x82, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x8B, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x0F, 0xFF, 0xF8, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x1F, 0xFF, 0xFC, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xFE, 0xA8, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x90, 0x1B, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xF0, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x0A, 0x81, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xFA, 0xA8, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x7F, 0xF4, 0xBF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xFF, 0xFC, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFD, 0x2F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xFF, 0xF8, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE2, 0xFF, 0xFE, 0x2F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xF0, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xF9, 0x54, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFF, 0xAA, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xFF, 0xFC, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFD, 0x00, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xFF, 0xFC, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFF, 0xAA, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xF9, 0x54, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xF4, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFF, 0xEE, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xFF, 0xF8, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFD, 0x00, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xFF, 0xFC, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFE, 0xAA, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0x92, 0xFC, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0x00, 0xBC, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2E, 0x00, 0x7C, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFE, 0x00, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2E, 0x00, 0x7C, 0xBF, 0xFF, 0xFF, 0xFF, 0xEA, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFE, 0x55, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2E, 0x00, 0x7C, 0xBF, 0xFF, 0xFF, 0xFE, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xD3, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2E, 0x00, 0x7C, 0xBF, 0xFF, 0xFF, 0xF4, 0x2A, 0x43, 0xFF, 0xFF, 0xFF, 0xD3, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2E, 0x00, 0x7C, 0xBF, 0xFF, 0xFF, 0xD2, 0xFF, 0xE0, 0xFF, 0xFF, 0xFF, 0xD3, 0xE0, 0x2F, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2E, 0x00, 0x7C, 0xBF, 0xFF, 0xFF, 0x87, 0xFF, 0xF8, 0xBF, 0xFF, 0xFF, 0xD3, 0xD0, 0x0F, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2E, 0x00, 0x7C, 0xBF, 0xFF, 0xFF, 0x8B, 0xFF, 0xFC, 0x7F, 0xFF, 0xFF, 0xD3, 0xD0, 0x0F, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFE, 0x1E, 0x00, 0x7C, 0x7F, 0xFF, 0xFF, 0x4F, 0xFF, 0xFC, 0x3F, 0xFF, 0xFF, 0xD3, 0xD0, 0x0F, 0x1F, 0xFF, 0xFF, + 0xFF, 0xF8, 0x2F, 0x00, 0x7E, 0x1F, 0xFF, 0xFF, 0x4F, 0xFE, 0xA8, 0x3F, 0xFF, 0xFF, 0xD3, 0xD0, 0x0F, 0x1F, 0xFF, 0xFF, + 0xFF, 0xF0, 0xFE, 0x00, 0x2F, 0x8B, 0xFF, 0xFF, 0x4F, 0xF4, 0x00, 0x3F, 0xFF, 0xFF, 0xD3, 0xD0, 0x0F, 0x1F, 0xFF, 0xFF, + 0xFF, 0xD2, 0xF0, 0x00, 0x0B, 0xD3, 0xFF, 0xFF, 0x4F, 0xFE, 0xA8, 0x3F, 0xFF, 0xFF, 0xD3, 0xD0, 0x0F, 0x1F, 0xFF, 0xFF, + 0xFF, 0xC7, 0xD0, 0x00, 0x02, 0xE2, 0xFF, 0xFF, 0x4F, 0xFF, 0xFD, 0x3F, 0xFF, 0xFF, 0xD3, 0xD0, 0x0F, 0x1F, 0xFF, 0xFF, + 0xFF, 0x8B, 0x80, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0x4F, 0xFF, 0xB8, 0x3F, 0xFF, 0xFF, 0xD3, 0xD0, 0x0F, 0x1F, 0xFF, 0xFF, + 0xFF, 0x8F, 0x40, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0x4F, 0xF4, 0x00, 0x3F, 0xFF, 0xFF, 0x83, 0xD0, 0x0F, 0x0B, 0xFF, 0xFF, + 0xFF, 0x8F, 0x40, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0x4F, 0xFA, 0xA4, 0x3F, 0xFF, 0xFE, 0x0B, 0xC0, 0x0F, 0xC2, 0xFF, 0xFF, + 0xFF, 0x8F, 0x40, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0x4F, 0xFF, 0xFD, 0x3F, 0xFF, 0xFC, 0x2F, 0x40, 0x07, 0xF0, 0xFF, 0xFF, + 0xFF, 0x8F, 0x40, 0x00, 0x00, 0xF8, 0xFF, 0xFF, 0x4F, 0xFF, 0xFD, 0x3F, 0xFF, 0xF8, 0xBC, 0x00, 0x00, 0xF8, 0xBF, 0xFF, + 0xFF, 0x8B, 0x80, 0x00, 0x01, 0xF0, 0xFF, 0xFF, 0x4F, 0xF9, 0x54, 0x3F, 0xFF, 0xF4, 0xF4, 0x00, 0x00, 0x7C, 0x3F, 0xFF, + 0xFF, 0xC7, 0xD0, 0x00, 0x02, 0xE2, 0xFF, 0xFF, 0x4F, 0xF8, 0x00, 0x3F, 0xFF, 0xF2, 0xF0, 0x00, 0x00, 0x2E, 0x3F, 0xFF, + 0xFF, 0xE2, 0xF4, 0x00, 0x0B, 0xC3, 0xFF, 0xFF, 0x4F, 0xFF, 0xFD, 0x3F, 0xFF, 0xE2, 0xE0, 0x00, 0x00, 0x2E, 0x2F, 0xFF, + 0xFF, 0xF0, 0xBE, 0x40, 0xBF, 0x4B, 0xFF, 0xFF, 0x4F, 0xFF, 0xFD, 0x3F, 0xFF, 0xE2, 0xE0, 0x00, 0x00, 0x2E, 0x2F, 0xFF, + 0xFF, 0xF8, 0x2F, 0xFF, 0xFD, 0x1F, 0xFF, 0xFF, 0x4F, 0xD1, 0xBD, 0x3F, 0xFF, 0xE2, 0xE0, 0x00, 0x00, 0x2E, 0x2F, 0xFF, + 0xFF, 0xFE, 0x06, 0xFF, 0xE0, 0xBF, 0xFF, 0xFF, 0x4F, 0x40, 0x3D, 0x3F, 0xFF, 0xF2, 0xF0, 0x00, 0x00, 0x2E, 0x3F, 0xFF, + 0xFF, 0xFF, 0xE0, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0x4F, 0x40, 0x3D, 0x3F, 0xFF, 0xF4, 0xF4, 0x00, 0x00, 0x7C, 0x3F, 0xFF, + 0xFF, 0xFF, 0xFE, 0x55, 0xAF, 0xFF, 0xFF, 0xFF, 0x4F, 0x40, 0x3D, 0x3F, 0xFF, 0xF8, 0xBC, 0x00, 0x00, 0xF8, 0xBF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0x40, 0x3D, 0x3F, 0xFF, 0xFC, 0x2F, 0x40, 0x07, 0xF0, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0x40, 0x3D, 0x3F, 0xFF, 0xFE, 0x0B, 0xFA, 0xBF, 0xC2, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0x40, 0x3D, 0x3F, 0xFF, 0xFF, 0x82, 0xFF, 0xFE, 0x0B, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0x40, 0x3D, 0x3F, 0xFF, 0xFF, 0xE0, 0x2A, 0xA0, 0x2F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0x40, 0x3D, 0x3F, 0xFF, 0xFF, 0xFE, 0x40, 0x06, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x40, 0x3D, 0x2F, 0xFF, 0xFF, 0xFF, 0xFE, 0xBF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x2F, 0x40, 0x3E, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0xBE, 0x00, 0x2F, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE2, 0xF8, 0x00, 0x07, 0xE2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xE0, 0x00, 0x02, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0xC0, 0x00, 0x00, 0xB8, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8B, 0x80, 0x00, 0x00, 0xB8, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8B, 0x80, 0x00, 0x00, 0x7C, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8B, 0x80, 0x00, 0x00, 0x7C, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8B, 0x80, 0x00, 0x00, 0xB8, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0xC0, 0x00, 0x00, 0xB8, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xE0, 0x00, 0x02, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, 0xF8, 0x00, 0x07, 0xE2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0xBE, 0x40, 0x2F, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x2F, 0xFF, 0xFE, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x06, 0xFF, 0xE4, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x04, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x40, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +/* Main Menu Backward */ + +const uint16_t iconBWColors[] = { + 0x9CC, 0x63F3, 0x7475, 0xFFFF +}; + +const uint8_t iconBWBitmap[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x01, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +/* Main Menu Return */ + +const uint16_t iconReturnColors[] = { + 0xA22B, 0xBBD0, 0xFFDF, 0x0 +}; + +const uint8_t iconReturnBitmap[] = { + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0x1A, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA9, 0x56, 0xAA, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x06, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x01, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xA5, 0x55, 0x55, 0x55, 0x55, 0x54, 0x00, 0x00, 0x6A, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x01, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x06, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA9, 0x56, 0xAA, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0x1A, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x55, 0x55, 0x55, 0x54, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x55, 0x55, 0x55, 0x55, 0x56, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x40, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA +}; + +/* Main Menu Forward */ + +const uint16_t iconFWColors[] = { + 0x9CC, 0x5371, 0x7495, 0xFFFF +}; + +const uint8_t iconFWBitmap[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF9, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x64, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +/* DIY-Thermocam Logo */ + +const uint16_t logoColors[] = { + 0x2C56, 0x44B7, 0x6538, 0xEA06, 0xF32B, 0xF40F, 0x85DA, 0x961A, 0xAE7B, 0xB69C, 0xF534, 0xFE17, 0xC6FD, 0xD75D, 0xFE99, 0xFFFF +}; + +const uint8_t logoBitmap[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x20, 0x00, 0x00, 0x02, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xCF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9F, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x18, 0xFF, 0xFF, 0x81, 0x00, 0x00, 0x00, 0x00, 0x1F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x01, 0xDF, 0xFF, 0xFF, 0xFD, 0x10, 0x00, 0x00, 0x00, 0x0C, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x70, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x07, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x02, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xD6, 0x66, 0x66, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0x82, 0x22, 0x21, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xD6, 0x66, 0x66, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0x72, 0x22, 0x21, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xD7, 0x66, 0x66, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x62, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x22, 0x22, 0x22, 0x26, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0x43, 0x3B, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFA, 0x33, 0x33, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x7D, 0xDD, 0xDD, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xDD, 0xDD, 0xD7, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x06, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0x60, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x09, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0x90, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x70, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xCF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x9F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDC, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x82, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x28, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xCD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xCF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, + 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6F, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xFF, + 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x02, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x79, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x97, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x20, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, + 0xF9, 0x00, 0x00, 0x00, 0x00, 0x01, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x10, 0x00, 0x00, 0x00, 0x00, 0x9F, + 0xF2, 0x00, 0x00, 0x00, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x2F, + 0xF0, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x0F, + 0xC0, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x0C, + 0x80, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x08, + 0x70, 0x00, 0x00, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD6, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x00, + 0x6D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x9F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF4, 0x33, 0x33, 0xEF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x06, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xF4, 0x33, 0x33, 0xEF, 0xFF, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1D, 0xFF, 0xF4, 0x33, 0x33, 0xEF, 0xFF, 0xD1, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0xFF, 0xFF, 0xF4, 0x33, 0x33, 0xEF, 0xFF, 0xFF, 0x71, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xFF, 0xFF, 0xFF, 0xF4, 0x33, 0x33, 0xEF, 0xFF, 0xFF, 0xFF, 0x81, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x33, 0x33, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0x90, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x10, 0x00, 0x00, 0x00, 0x00, 0x06, 0xFF, 0xFF, 0xFF, 0xFF, 0xEA, 0x53, 0x33, 0x33, 0x4A, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, + 0x60, 0x00, 0x00, 0x00, 0x00, 0x01, 0xDF, 0xFF, 0xFF, 0xFF, 0xD1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9F, 0xFF, 0xFF, 0xFE, 0x53, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x4A, 0xFF, 0xFF, 0xFF, + 0xF9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFD, 0x86, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x70, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xFB, 0x43, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x35, 0xFF, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x01, 0xDF, 0xFF, 0xFE, 0x43, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3A, 0xFF, + 0xFF, 0xFD, 0x10, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xA3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x4E, + 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x90, 0x00, 0x00, 0x00, 0x00, 0x9F, 0xFF, 0xF5, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0xEF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x53, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x3E, 0xFF, 0xFF, 0x70, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF5, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0xEF, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x9F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xA3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x4F, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x06, 0xFF, 0xFE, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x34, 0x5B, 0xEE, 0xEE, 0xA5, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x35, 0xFF, 0xFF, 0x60, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x60, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xF4, 0x33, 0x33, 0x33, 0x33, 0x33, 0x34, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0xBF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x06, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xB3, 0x33, 0x33, 0x33, 0x33, 0x33, 0xAF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x4F, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0xCF, 0xFF, 0x43, 0x33, 0x33, 0x33, 0x33, 0x3E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x53, 0x33, 0x33, + 0x33, 0x33, 0x3B, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFB, 0x33, 0x33, 0x33, 0x33, 0x33, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF5, 0x33, 0x33, + 0x33, 0x33, 0x34, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x06, 0xFF, 0xF4, 0x33, 0x33, 0x33, 0x33, 0x3A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x43, 0x33, + 0x33, 0x33, 0x33, 0xEF, 0xFF, 0x60, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0x00, 0x00, 0x00, 0x09, 0xFF, 0xF3, 0x33, 0x33, 0x33, 0x33, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB3, 0x33, + 0x33, 0x33, 0x33, 0x5F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xB3, 0x33, 0x33, 0x33, 0x33, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x33, + 0x33, 0x33, 0x33, 0x4F, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0x53, 0x33, 0x33, 0x33, 0x34, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0x33, + 0x33, 0x33, 0x33, 0x3F, 0xFF, 0xF1, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0x43, 0x33, 0x33, 0x33, 0x3A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x33, + 0x33, 0x33, 0x33, 0x3E, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x90, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0x33, 0x33, 0x33, 0x33, 0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x43, + 0x33, 0x33, 0x33, 0x3A, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0x33, 0x33, 0x33, 0x33, 0x3E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x53, + 0x33, 0x33, 0x33, 0x3A, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x6F, 0xFF, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x53, + 0x33, 0x33, 0x33, 0x3A, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0x33, 0x33, 0x33, 0x33, 0x3E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x53, + 0x33, 0x33, 0x33, 0x3A, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x90, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0x33, 0x33, 0x33, 0x33, 0x3E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x53, + 0x33, 0x33, 0x33, 0x3A, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0x43, 0x33, 0x33, 0x33, 0x3A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x33, + 0x33, 0x33, 0x33, 0x3B, 0xFF, 0xF1, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0x53, 0x33, 0x33, 0x33, 0x35, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x33, + 0x33, 0x33, 0x33, 0x3E, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xA3, 0x33, 0x33, 0x33, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0x33, + 0x33, 0x33, 0x33, 0x3F, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xE3, 0x33, 0x33, 0x33, 0x33, 0xAF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF3, 0x33, + 0x33, 0x33, 0x33, 0x4F, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xF3, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x53, 0x33, + 0x33, 0x33, 0x33, 0xAF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF5, 0x33, 0x33, 0x33, 0x33, 0x34, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0x33, 0x33, + 0x33, 0x33, 0x33, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x9F, 0xFE, 0x33, 0x33, 0x33, 0x33, 0x33, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0x33, 0x33, + 0x33, 0x33, 0x35, 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0x53, 0x33, 0x33, 0x33, 0x33, 0x34, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x3E, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xE3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x4B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x53, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x5F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFA, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x4A, 0xFF, 0xFF, 0xFF, 0xFE, 0x53, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x8F, 0xFF, 0x43, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x34, 0x45, 0x44, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x3B, 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF4, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x5F, 0xFF, 0xF1, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFE, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x35, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x5F, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0x43, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3A, + 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x00, 0x8F, 0xFF, 0xF5, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xEF, + 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x06, 0xFF, 0xFF, 0xE4, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xAF, 0xFF, + 0xFF, 0x60, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xB4, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x5F, 0xFF, 0xFF, + 0xF2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x01, 0xCF, 0xFF, 0xFF, 0xFA, 0x43, 0x33, 0x33, 0x33, 0x33, 0x45, 0xEF, 0xFF, 0xFF, 0xFC, + 0x10, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xDF, 0xFF, 0xFF, 0xFF, 0xEE, 0xBA, 0xAE, 0xEF, 0xFF, 0xFF, 0xFF, 0xFD, 0x60, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x60, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x6C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC6, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x67, 0x88, 0x88, 0x76, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x06, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0xDF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x7C, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDC, 0x86, 0x66, 0x66, 0x68, 0xCD, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x07, + 0x90, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0x00, 0x00, 0x00, 0x09, + 0xD0, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x0D, + 0xF0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x0F, + 0xF6, 0x00, 0x00, 0x00, 0x00, 0x2D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD2, 0x00, 0x00, 0x00, 0x00, 0x6F, + 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, + 0xFF, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xFF, + 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, + 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9F, 0xFF, + 0xFF, 0xFF, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xC1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; diff --git a/Firmware_V2/src/gui/bitmaps.h b/Firmware_V2/src/gui/bitmaps.h new file mode 100644 index 0000000..7cc66a8 --- /dev/null +++ b/Firmware_V2/src/gui/bitmaps.h @@ -0,0 +1,66 @@ +/* +* +* BITMAPS - Icons and graphics shown inside the GUI +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef BITMAPS_H +#define BITMAPS_H + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +extern const uint16_t icon1Colors[]; +extern const uint8_t icon1Bitmap[]; +extern const uint16_t icon2Colors[]; +extern const uint8_t icon2Bitmap[]; +extern const uint16_t icon3Colors[]; +extern const uint8_t icon3Bitmap[]; +extern const uint16_t icon4Colors[]; +extern const uint8_t icon4Bitmap[]; +extern const uint16_t icon5Colors[]; +extern const uint8_t icon5Bitmap_1[]; +#if defined(__MK66FX1M0__) +extern const uint8_t icon5Bitmap_2[]; +#else +extern const uint8_t* icon5Bitmap_2; +#endif +extern const uint16_t icon6Colors[]; +extern const uint8_t icon6Bitmap[]; +extern const uint16_t icon7Colors[]; +extern const uint8_t icon7Bitmap[]; +extern const uint16_t icon8Colors[]; +extern const uint8_t icon8Bitmap_1[]; +#if defined(__MK66FX1M0__) +extern const uint8_t icon8Bitmap_2[]; +#else +extern const uint8_t* icon8Bitmap_2; +#endif +extern const uint16_t icon9Colors[]; +extern const uint8_t icon9Bitmap[]; +extern const uint16_t icon10Colors[]; +extern const uint8_t icon10Bitmap[]; +extern const uint16_t icon11_1Colors[]; +extern const uint8_t icon11_1Bitmap[]; +extern const uint16_t icon11_2Colors[]; +extern const uint8_t icon11_2Bitmap[]; +extern const uint16_t icon12Colors[]; +extern const uint8_t icon12Bitmap[]; +extern const uint16_t iconBWColors[]; +extern const uint8_t iconBWBitmap[]; +extern const uint16_t iconReturnColors[]; +extern const uint8_t iconReturnBitmap[]; +extern const uint16_t iconFWColors[]; +extern const uint8_t iconFWBitmap[]; +extern const uint16_t logoColors[]; +extern const uint8_t logoBitmap[]; + +#endif /* BITMAPS_H */ diff --git a/Firmware_V2/src/gui/buttons.cpp b/Firmware_V2/src/gui/buttons.cpp new file mode 100644 index 0000000..2030565 --- /dev/null +++ b/Firmware_V2/src/gui/buttons.cpp @@ -0,0 +1,392 @@ +/* + * + * Buttons - Touch buttons for the GUI + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include + +/*################# DATA TYPES, CONSTANTS & MACRO DEFINITIONS #################*/ + +#define BUTTON_DISABLED 0x0001 +#define BUTTON_SYMBOL 0x0002 +#define BUTTON_SYMBOL_REP_3X 0x0004 +#define BUTTON_BITMAP 0x0008 +#define BUTTON_NO_BORDER 0x0010 +#define BUTTON_UNUSED 0x8000 + +typedef struct { + uint16_t pos_x, pos_y, width, height; + uint16_t flags; + boolean largetouch; + char *label; + const uint8_t* data; + const uint16_t* palette; +} button_type; + +/*######################### STATIC DATA DECLARATIONS ##########################*/ + +//Store up to 20 buttons +static button_type buttons[20]; +//Button attributes +static word buttons_colorText; +static word buttons_colorTextInactive; +static word buttons_colorBackGround; +static word buttons_colorBorder; +static word buttons_colorHilite; +//Button fonts +static uint8_t* buttons_fontText; +static uint8_t* buttons_fontSymbol; + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Add a text button */ +int buttons_addButton(uint16_t x, uint16_t y, uint16_t width, uint16_t height, char *label, uint16_t flags, boolean largetouch) +{ + int btcnt = 0; + + while (((buttons[btcnt].flags & BUTTON_UNUSED) == 0) + && (btcnt < 20)) + btcnt++; + + if (btcnt == 20) + return -1; + else { + buttons[btcnt].pos_x = x; + buttons[btcnt].pos_y = y; + buttons[btcnt].width = width; + buttons[btcnt].height = height; + buttons[btcnt].flags = flags; + buttons[btcnt].label = label; + buttons[btcnt].data = NULL; + buttons[btcnt].palette = NULL; + buttons[btcnt].largetouch = largetouch; + return btcnt; + } +} + +/* Add a bitmap button */ +int buttons_addButton(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, const uint16_t* palette, uint16_t flags) +{ + int btcnt = 0; + + while (((buttons[btcnt].flags & BUTTON_UNUSED) == 0) && (btcnt < 20)) + btcnt++; + + if (btcnt == 20) + return -1; + else + { + buttons[btcnt].pos_x = x; + buttons[btcnt].pos_y = y; + buttons[btcnt].width = width; + buttons[btcnt].height = height; + buttons[btcnt].flags = flags | BUTTON_BITMAP; + buttons[btcnt].label = NULL; + buttons[btcnt].data = data; + buttons[btcnt].palette = palette; + return btcnt; + } +} + +/* Draw a specific button */ +void buttons_drawButton(int buttonID) { + int text_x, text_y; + uint8_t *_font_current = display_getFont(); + ; + word _current_color = display_getColor(); + word _current_back = display_getBackColor(); + + if (buttons[buttonID].flags & BUTTON_BITMAP) { + display_writeRect2BPP(buttons[buttonID].pos_x, buttons[buttonID].pos_y, + buttons[buttonID].width, buttons[buttonID].height, + buttons[buttonID].data, buttons[buttonID].palette); + if (!(buttons[buttonID].flags & BUTTON_NO_BORDER)) { + if ((buttons[buttonID].flags & BUTTON_DISABLED)) + display_setColor(buttons_colorTextInactive); + else + display_setColor(buttons_colorBorder); + display_drawRoundRect(buttons[buttonID].pos_x, buttons[buttonID].pos_y, + buttons[buttonID].pos_x + buttons[buttonID].width, + buttons[buttonID].pos_y + buttons[buttonID].height); + display_drawRoundRect(buttons[buttonID].pos_x - 1, buttons[buttonID].pos_y - 1, + buttons[buttonID].pos_x + buttons[buttonID].width + 1, + buttons[buttonID].pos_y + buttons[buttonID].height + 1); + } + } + else { + display_setColor(buttons_colorBackGround); + display_fillRoundRect(buttons[buttonID].pos_x, buttons[buttonID].pos_y, + buttons[buttonID].pos_x + buttons[buttonID].width, + buttons[buttonID].pos_y + buttons[buttonID].height); + display_setColor(buttons_colorBorder); + display_drawRoundRect(buttons[buttonID].pos_x, buttons[buttonID].pos_y, + buttons[buttonID].pos_x + buttons[buttonID].width, + buttons[buttonID].pos_y + buttons[buttonID].height); + display_drawRoundRect(buttons[buttonID].pos_x - 1, buttons[buttonID].pos_y - 1, + buttons[buttonID].pos_x + buttons[buttonID].width + 1, + buttons[buttonID].pos_y + buttons[buttonID].height + 1); + if (buttons[buttonID].flags & BUTTON_DISABLED) + display_setColor(buttons_colorTextInactive); + else + display_setColor(buttons_colorText); + if (buttons[buttonID].flags & BUTTON_SYMBOL) { + display_setFont(buttons_fontSymbol); + text_x = (buttons[buttonID].width / 2) - (display_getFontXsize() / 2) + + buttons[buttonID].pos_x; + text_y = (buttons[buttonID].height / 2) + - (display_getFontYsize() / 2) + buttons[buttonID].pos_y; + } + else { + display_setFont(buttons_fontText); + text_x = ((buttons[buttonID].width / 2) + - ((strlen(buttons[buttonID].label) * display_getFontXsize()) + / 2)) + buttons[buttonID].pos_x; + text_y = (buttons[buttonID].height / 2) + - (display_getFontYsize() / 2) + buttons[buttonID].pos_y; + } + display_setBackColor(buttons_colorBackGround); + display_print(buttons[buttonID].label, text_x, text_y); + if ((buttons[buttonID].flags & BUTTON_SYMBOL) + && (buttons[buttonID].flags & BUTTON_SYMBOL_REP_3X)) { + display_print(buttons[buttonID].label, + text_x - display_getFontXsize(), text_y); + display_print(buttons[buttonID].label, + text_x + display_getFontXsize(), text_y); + } + } + display_setFont(_font_current); + display_setColor(_current_color); + display_setBackColor(_current_back); +} + +/* Draw all buttons */ +void buttons_drawButtons() { + for (int i = 0; i < 20; i++) { + if ((buttons[i].flags & BUTTON_UNUSED) == 0) + buttons_drawButton(i); + } +} + +/* Enable a specific button */ +void buttons_enableButton(int buttonID, boolean redraw) { + if (!(buttons[buttonID].flags & BUTTON_UNUSED)) { + buttons[buttonID].flags = buttons[buttonID].flags & ~BUTTON_DISABLED; + if (redraw) + buttons_drawButton(buttonID); + } +} + +/* Disable a specific button */ +void buttons_disableButton(int buttonID, boolean redraw) { + if (!(buttons[buttonID].flags & BUTTON_UNUSED)) { + buttons[buttonID].flags = buttons[buttonID].flags | BUTTON_DISABLED; + if (redraw) + buttons_drawButton(buttonID); + } +} + +/* Relabel a specific button */ +void buttons_relabelButton(int buttonID, char *label, boolean redraw) { + if (!(buttons[buttonID].flags & BUTTON_UNUSED)) { + buttons[buttonID].label = label; + if (redraw) + buttons_drawButton(buttonID); + } +} + +/* Check if the button is enabled */ +boolean buttons_buttonEnabled(int buttonID) { + return !(buttons[buttonID].flags & BUTTON_DISABLED); +} + +/* Delete a specific button */ +void buttons_deleteButton(int buttonID) { + if (!(buttons[buttonID].flags & BUTTON_UNUSED)) + buttons[buttonID].flags = BUTTON_UNUSED; +} + +/* Delete all buttons */ +void buttons_deleteAllButtons() { + for (int i = 0; i < 20; i++) { + buttons[i].pos_x = 0; + buttons[i].pos_y = 0; + buttons[i].width = 0; + buttons[i].height = 0; + buttons[i].flags = BUTTON_UNUSED; + buttons[i].label = (char*) ""; + } +} + +/* Check which button is pressed */ +int buttons_checkButtons(boolean timeout, boolean fast) { + TS_Point p = touch_getPoint(); + int x = p.x; + int y = p.y; + int result = -1; + word _current_color = display_getColor(); + int xpos, ypos, width, height; + for (int i = 0; i < 20; i++) { + xpos = buttons[i].pos_x; + ypos = buttons[i].pos_y; + width = buttons[i].width; + height = buttons[i].height; + if (buttons[i].largetouch) { + xpos -= 30; + ypos -= 20; + width += 60; + height += 40; + } + if (((buttons[i].flags & BUTTON_UNUSED) == 0) + && ((buttons[i].flags & BUTTON_DISABLED) == 0) + && (result == -1)) { + if ((x >= xpos) + && (x <= (xpos + width)) + && (y >= ypos) + && (y <= (ypos + height))) + result = i; + } + } + if (result != -1) { + if (!(buttons[result].flags & BUTTON_NO_BORDER)) { + display_setColor(buttons_colorHilite); + if (buttons[result].flags & BUTTON_BITMAP) + display_drawRoundRect(buttons[result].pos_x, + buttons[result].pos_y, + buttons[result].pos_x + buttons[result].width, + buttons[result].pos_y + buttons[result].height); + else + display_drawRoundRect(buttons[result].pos_x, + buttons[result].pos_y, + buttons[result].pos_x + buttons[result].width, + buttons[result].pos_y + buttons[result].height); + display_drawRoundRect(buttons[result].pos_x - 1, + buttons[result].pos_y - 1, + buttons[result].pos_x + buttons[result].width + 1, + buttons[result].pos_y + buttons[result].height + 1); + } + } + if (fast) { + long time = millis(); + while ((touch_touched() == 1) + && ((millis() - time) < 50)) { + }; + } + else if (timeout) { + long time = millis(); + while ((touch_touched() == 1) + && ((millis() - time) < 150)) { + }; + } + else { + while (touch_touched() == 1) { + }; + } + if (result != -1) { + if (!(buttons[result].flags & BUTTON_NO_BORDER)) { + display_setColor(buttons_colorBorder); + if (buttons[result].flags & BUTTON_BITMAP) + display_drawRoundRect(buttons[result].pos_x, + buttons[result].pos_y, + buttons[result].pos_x + buttons[result].width, + buttons[result].pos_y + buttons[result].height); + else + display_drawRoundRect(buttons[result].pos_x, + buttons[result].pos_y, + buttons[result].pos_x + buttons[result].width, + buttons[result].pos_y + buttons[result].height); + display_drawRoundRect(buttons[result].pos_x - 1, + buttons[result].pos_y - 1, + buttons[result].pos_x + buttons[result].width + 1, + buttons[result].pos_y + buttons[result].height + 1); + } + } + display_setColor(_current_color); + return result; +} + +/* Set a specific button to active */ +void buttons_setActive(int buttonID) { + int text_x, text_y; + display_setColor(VGA_AQUA); + display_fillRect(buttons[buttonID].pos_x + 3, buttons[buttonID].pos_y + 3, + buttons[buttonID].pos_x + buttons[buttonID].width - 3, + buttons[buttonID].pos_y + buttons[buttonID].height - 3); + display_setFont(buttons_fontText); + display_setColor(buttons_colorText); + text_x = ((buttons[buttonID].width / 2) + - ((strlen(buttons[buttonID].label) * display_getFontXsize()) / 2)) + + buttons[buttonID].pos_x; + text_y = (buttons[buttonID].height / 2) - (display_getFontYsize() / 2) + + buttons[buttonID].pos_y; + display_setBackColor(VGA_AQUA); + display_print(buttons[buttonID].label, text_x, text_y); +} + +/* Set a specific button to inactive */ +void buttons_setInactive(int buttonID) { + int text_x, text_y; + display_setColor(buttons_colorBackGround); + display_fillRect(buttons[buttonID].pos_x + 3, buttons[buttonID].pos_y + 3, + buttons[buttonID].pos_x + buttons[buttonID].width - 3, + buttons[buttonID].pos_y + buttons[buttonID].height - 3); + display_setFont(buttons_fontText); + display_setColor(buttons_colorText); + text_x = ((buttons[buttonID].width / 2) + - ((strlen(buttons[buttonID].label) * display_getFontXsize()) / 2)) + + buttons[buttonID].pos_x; + text_y = (buttons[buttonID].height / 2) - (display_getFontYsize() / 2) + + buttons[buttonID].pos_y; + display_setBackColor(buttons_colorBackGround); + display_print(buttons[buttonID].label, text_x, text_y); +} + +/* Set the text font of all buttons */ +void buttons_setTextFont(const uint8_t* font) { + buttons_fontText = (uint8_t*) font; +} + +/* Set the symbol font of all buttons */ +void buttons_setSymbolFont(const uint8_t* font) { + buttons_fontSymbol = (uint8_t*) font; +} + +/* Set the buttons color */ +void buttons_setButtonColors(word atxt, word iatxt, word brd, word brdhi, + word back) { + buttons_colorText = atxt; + buttons_colorTextInactive = iatxt; + buttons_colorBackGround = back; + buttons_colorBorder = brd; + buttons_colorHilite = brdhi; +} + +/* Init the buttons */ +void buttons_init() { + buttons_deleteAllButtons(); + buttons_colorText = VGA_WHITE; + buttons_colorTextInactive = VGA_GRAY; + buttons_colorBackGround = VGA_BLUE; + buttons_colorBorder = VGA_BLACK; + buttons_colorHilite = VGA_BLUE; + buttons_fontText = NULL; + buttons_fontSymbol = NULL; + buttons_setButtonColors(VGA_BLACK, VGA_BLACK, VGA_BLACK, VGA_BLUE, + VGA_WHITE); +} diff --git a/Firmware_V2/src/gui/buttons.h b/Firmware_V2/src/gui/buttons.h new file mode 100644 index 0000000..1027798 --- /dev/null +++ b/Firmware_V2/src/gui/buttons.h @@ -0,0 +1,39 @@ +/* +* +* Buttons - Touch buttons for the GUI +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef BUTTONS_H +#define BUTTONS_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +int buttons_addButton(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, const uint16_t* palette, uint16_t flags = 0); +int buttons_addButton(uint16_t x, uint16_t y, uint16_t width, uint16_t height, char *label, uint16_t flags = 0, boolean largetouch = 0); +boolean buttons_buttonEnabled(int buttonID); +int buttons_checkButtons(boolean timeout = 0, boolean fast = 0); +void buttons_deleteAllButtons(); +void buttons_deleteButton(int buttonID); +void buttons_disableButton(int buttonID, boolean redraw = 0); +void buttons_drawButtons(); +void buttons_drawButton(int buttonID); +void buttons_enableButton(int buttonID, boolean redraw = 0); +void buttons_init(); +void buttons_relabelButton(int buttonID, char *label, boolean redraw = 0); +void buttons_setActive(int buttonID); +void buttons_setButtonColors(word atxt, word iatxt, word brd, word brdhi, word back); +void buttons_setInactive(int buttonID); +void buttons_setSymbolFont(const uint8_t* font); +void buttons_setTextFont(const uint8_t* font); + +#endif /* BUTTONS_H */ diff --git a/Firmware_V2/src/gui/firststart.cpp b/Firmware_V2/src/gui/firststart.cpp new file mode 100644 index 0000000..bd02974 --- /dev/null +++ b/Firmware_V2/src/gui/firststart.cpp @@ -0,0 +1,416 @@ +/* +* +* FIRST START - Menu that is displayed on the first device start +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Check if the first start needs to be done */ +boolean checkFirstStart() { + return EEPROM.read(eeprom_firstStart) != eeprom_setValue; +} + +/* Show welcome Screen for the first start procedure */ +void welcomeScreen() { + //Background & Title + display_fillScr(200, 200, 200); + display_setBackColor(200, 200, 200); + display_setFont(smallFont); + display_printC("Welcome to the", CENTER, 20); + display_setFont(bigFont); + + //DIY-Thermocam V2 + if (teensyVersion == teensyVersion_new) + display_printC("DIY-Thermocam V2", CENTER, 60, VGA_BLUE); + + //DIY-Thermocam V1 + else if (mlx90614Version == mlx90614Version_new) + display_printC("DIY-Thermocam V1", CENTER, 60, VGA_BLUE); + + //Thermocam V4 + else + display_printC("Cheap-Thermocam V4", CENTER, 60, VGA_BLUE); + + //Explanation + display_setFont(smallFont); + display_printC("This is the first time setup.", CENTER, 110); + display_printC("It will guide you through the", CENTER, 140); + display_printC("basic settings for your device.", CENTER, 170); + display_printC("-> Please touch screen <-", CENTER, 210, VGA_BLUE); + + //Wait for touch press or updater request + while (!touch_touched()) + checkForUpdater(); + + //Touch release again + while (touch_touched()); +} + +/* Shows an info screen during the first start procedure */ +void infoScreen(String* text, bool cont) { + //Background & Title + display_fillScr(200, 200, 200); + display_setBackColor(200, 200, 200); + display_setFont(bigFont); + display_printC(text[0], CENTER, 20, VGA_BLUE); + + //Content + display_setFont(smallFont); + display_printC(text[1], CENTER, 55); + display_printC(text[2], CENTER, 80); + display_printC(text[3], CENTER, 105); + display_printC(text[4], CENTER, 130); + + //Show hint to touch the screen + if (cont) { + display_printC(text[5], CENTER, 155); + display_printC(text[6], CENTER, 180); + display_printC("-> Please touch screen <-", CENTER, 212, VGA_BLUE); + //Wait for touch press or updater request + while (!touch_touched()) + checkForUpdater(); + //Touch release again + while (touch_touched()); + } + + //Show more information + else { + display_printC(text[5], CENTER, 180); + display_printC(text[6], CENTER, 205); + } +} + +/* Setting screen for the time and date */ +void timeDateScreen() { + //Content + String text[7]; + text[0] = "Set Time & Date"; + text[1] = "In the next screens, you can"; + text[2] = "set the time and date, so "; + text[3] = "that it matches your current"; + text[4] = "time zone. If the settings do"; + text[5] = "not survive a reboot, check"; + text[6] = "the coin cell battery voltage."; + infoScreen(text); + + //Reset values + setTime(12, 30, 30, 15, 6, 2017); + + //Adjust time + timeMenu(true); + timeMenuHandler(true); + + //Adjust date + dateMenu(true); + dateMenuHandler(true); +} + +/* Setting screen for the temperature format */ +void tempFormatScreen() { + //Content + String text[7]; + text[0] = "Set Temp. Format"; + text[1] = "In the next screen, you can"; + text[2] = "set the temperature format "; + text[3] = "for the temperature display_"; + text[4] = "Choose between Celsius or"; + text[5] = "Fahrenheit, the conversion will"; + text[6] = "be done automatically."; + infoScreen(text); + + //Temperature format menu + tempFormatMenu(true); +} + +/* Setting screen for the convert image option */ +void convertImageScreen() { + //Content + String text[7]; + text[0] = "Convert DAT to BMP"; + text[1] = "In the next screen, select if"; + text[2] = "you also want to create a bitmap"; + text[3] = "file for every saved thermal"; + text[4] = "raw image file on the device. "; + text[5] = "You can still convert images man-"; + text[6] = "ually in the load menu later."; + infoScreen(text); + + //Convert image menu + convertImageMenu(true); +} + +/* Setting screen for the visual image option */ +void visualImageScreen() { + //Content + String text[7]; + text[0] = "Save visual image"; + text[1] = "In the next screen, choose"; + text[2] = "if you want to save an addi-"; + text[3] = "tional visual image together"; + text[4] = "with each saved thermal image."; + text[5] = "Enable this if you want to"; + text[6] = "create combined images on the PC."; + infoScreen(text); + + //Visual image menu + visualImageMenu(true); +} + +/* Setting screen for the combined image alignment */ +void combinedAlignmentScreen() { + //Content + String text[7]; + text[0] = "Combined Alignment"; + text[1] = "In the next screen, you can"; + text[2] = "move, increase or decrease"; + text[3] = "the visual image on top of the"; + text[4] = "thermal image. Press the A-button"; + text[5] = "to change the alpha transparency"; + text[6] = "and touch the middle to refresh."; + infoScreen(text); + + //Set color scheme to rainbow + colorMap = colorMap_rainbow; + colorElements = 256; + + //Disable showmenu + showMenu = showMenu_disabled; + + //Adjust combined menu + adjustCombinedNewMenu(true); +} + +/* Setting screen for the calibration procedure */ +void calibrationHelperScreen() { + //Content + String text[7]; + text[0] = "Calibration"; + text[1] = "Before using the device, you need"; + text[2] = "to calibrate it first. Point the "; + text[3] = "device to different hot and cold"; + text[4] = "objects in the surrounding area"; + text[5] = "slowly, until the calibration"; + text[6] = "process has been completed."; + infoScreen(text); + + //Calibration procedure + calibrationProcess(false, true); +} + +/* Format the SD card for the first time */ +void firstFormat() { + //ThermocamV4 or DIY-Thermocam V2, check SD card + if ((mlx90614Version == mlx90614Version_old) || + (teensyVersion == teensyVersion_new)) { + + //Show message + showFullMessage((char*) "Checking SD card.."); + + //Check for SD card + if (!checkSDCard()) { + showFullMessage((char*) "Please insert SD card"); + //Wait until card is inserted + while (!checkSDCard()) + delay(1000); + } + } + + //Format the SD card + showFullMessage((char*) "Formatting SD card.."); + formatCard(); +} + +/* Show the first start complete screen */ +void firstStartComplete() { + //Content + String text[7]; + text[0] = "Setup completed"; + text[1] = "The first-time setup is"; + text[2] = "now complete. Please reboot"; + text[3] = "the device by turning the"; + text[4] = "power switch off and on again."; + text[5] = "Afterwards, you will be redirected"; + text[6] = "to the align combined menu."; + infoScreen(text, false); + + //Wait for hard-reset + while (true); +} + +/* Check if the live mode helper needs to be shown */ +boolean checkLiveModeHelper() { + return EEPROM.read(eeprom_liveHelper) != eeprom_setValue; +} + +/* Help screen for the first start of live mode */ +void liveModeHelper() { + String text[7]; + + //Do the first time calibration if spot sensor working and not using radiometric Lepton + if (checkDiagnostic(diag_spot) && (leptonVersion != leptonVersion_2_5_shutter) + && (leptonVersion != leptonVersion_3_5_shutter)) + calibrationHelperScreen(); + + //Hint screen for the live mode #1 + text[0] = "First time helper"; + text[1] = "To enter the live mode menu,"; + text[2] = "touch the screen. 'Exit' will"; + text[3] = "bring you to the main menu."; + text[4] = "Pressing the push button on"; + text[5] = "top of the device short takes"; + text[6] = "an image, long records a video."; + infoScreen(text); + + //Hint screen for the live mode #2 + if ((leptonVersion != leptonVersion_2_5_shutter) && (leptonVersion != leptonVersion_3_5_shutter)) { + text[1] = "The device needs 30 seconds to"; + text[2] = "warmup the sensor, more functions"; + text[3] = "will be activated afterwards. You"; + } + else { + text[1] = "The device has detected that you"; + text[2] = "are using the radiometric Lepton2.5"; + text[3] = "sensor and adjusted to that. You"; + } + text[4] = "can lock the limits or toggle dif-"; + text[5] = "ferent temperature limits by pres-"; + text[6] = "sing the screen long in live mode."; + infoScreen(text); + + //Show waiting message + showFullMessage((char*)"Please wait.."); + + //Set EEPROM marker to complete + EEPROM.write(eeprom_liveHelper, eeprom_setValue); +} + + +/* Set the EEPROM values to default for the first time */ +void stdEEPROMSet() { + //Flash spot sensor, not for radiometric Lepton + if ((leptonVersion != leptonVersion_2_5_shutter) && (leptonVersion != leptonVersion_3_5_shutter)) { + //Show message + showFullMessage((char*) "Flashing spot EEPROM settings.."); + + //Only if spot sensor is connected + if (checkDiagnostic(diag_spot)) + { + //Set spot maximum temp to 380°C + mlx90614_setMax(); + //Set spot minimum temp to -70° + mlx90614_setMin(); + //Set spot filter settings + mlx90614_setFilter(); + //Set spot emissivity to 0.9 + mlx90614_setEmissivity(); + } + } + + //Set device EEPROM settings + EEPROM.write(eeprom_rotationVert, false); + EEPROM.write(eeprom_rotationHorizont, false); + EEPROM.write(eeprom_spotEnabled, false); + EEPROM.write(eeprom_colorbarEnabled, true); + EEPROM.write(eeprom_batteryEnabled, true); + EEPROM.write(eeprom_timeEnabled, true); + EEPROM.write(eeprom_dateEnabled, true); + EEPROM.write(eeprom_storageEnabled, true); + EEPROM.write(eeprom_displayMode, displayMode_thermal); + EEPROM.write(eeprom_textColor, textColor_white); + EEPROM.write(eeprom_minMaxPoints, minMaxPoints_max); + EEPROM.write(eeprom_screenOffTime, screenOffTime_disabled); + EEPROM.write(eeprom_hotColdMode, hotColdMode_disabled); + + //Set Color Scheme to Rainbow + EEPROM.write(eeprom_colorScheme, colorScheme_rainbow); + + //Set filter type to box blur + EEPROM.write(eeprom_filterType, filterType_gaussian); + + //For DIY-Thermocam V2, set HQ res to true + if (teensyVersion == teensyVersion_new) + EEPROM.write(eeprom_hqRes, true); + + //Set disable shutter to false + EEPROM.write(eeprom_noShutter, false); + + //Battery gauge standard compensation values + //DIY-Thermocam V1 + if ((teensyVersion == teensyVersion_old) && (mlx90614Version == mlx90614Version_new)) + EEPROM.write(eeprom_batComp, 0); + //Thermocam V4 + else if ((teensyVersion == teensyVersion_old) && (mlx90614Version == mlx90614Version_old)) + EEPROM.write(eeprom_batComp, 0); + //DIY-Thermocam V2 + else + EEPROM.write(eeprom_batComp, 20); + + //Set current firmware version + EEPROM.write(eeprom_fwVersion, fwVersion); + + //Set first start marker to true + EEPROM.write(eeprom_firstStart, eeprom_setValue); + + //Set live helper to false to show it the next time + EEPROM.write(eeprom_liveHelper, false); +} + +/* First start setup*/ +void firstStart() { + //Clear EEPROM + clearEEPROM(); + + //Welcome screen + welcomeScreen(); + + //Hint screen for the time and date settings + timeDateScreen(); + + //Hint screen for temperature format setting + tempFormatScreen(); + + //Hint screen for the convert image settings + convertImageScreen(); + + //Hint screen for the visual image settings + visualImageScreen(); + + //Format SD card for the first time + firstFormat(); + + //Set EEPROM values + stdEEPROMSet(); + + //Show completion message + firstStartComplete(); +} diff --git a/Firmware_V2/src/gui/firststart.h b/Firmware_V2/src/gui/firststart.h new file mode 100644 index 0000000..8f39451 --- /dev/null +++ b/Firmware_V2/src/gui/firststart.h @@ -0,0 +1,37 @@ +/* +* +* FIRST START - Menu that is displayed on the first device start +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef FIRSTSTART_H +#define FIRSTSTART_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void calibrationHelperScreen(); +boolean checkFirstStart(); +boolean checkLiveModeHelper(); +void combinedAlignmentScreen(); +void convertImageScreen(); +void firstFormat(); +void firstStartComplete(); +void firstStart(); +void infoScreen(String* text, bool cont = true); +void liveModeHelper(); +void stdEEPROMSet(); +void tempFormatScreen(); +void timeDateScreen(); +void visualImageScreen(); +void welcomeScreen(); + +#endif /* FIRSTSTART_H */ diff --git a/Firmware_V2/src/gui/gui.cpp b/Firmware_V2/src/gui/gui.cpp new file mode 100644 index 0000000..831e73b --- /dev/null +++ b/Firmware_V2/src/gui/gui.cpp @@ -0,0 +1,239 @@ +/* +* +* GUI - Main Methods to lcd the Graphical-User-Interface +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Converts a given float to char array */ +void floatToChar(char* buffer, float val) { + int units = val; + int hundredths = val * 100; + hundredths = abs(hundredths % 100); + sprintf(buffer, "%d.%02d", units, hundredths); +} + +/* Sets the text color to the right one */ +void changeTextColor() { + //Red + if (textColor == textColor_red) + display_setColor(VGA_RED); + //Black + else if (textColor == textColor_black) + display_setColor(VGA_BLACK); + //Green + else if (textColor == textColor_green) + display_setColor(VGA_GREEN); + //Blue + else if (textColor == textColor_blue) + display_setColor(VGA_BLUE); + //White + else + display_setColor(VGA_WHITE); +} + +/* Shows a full screen message */ +void showFullMessage(char* message, bool small) { + //Fill screen complete + if (!small) + display_fillScr(200, 200, 200); + + //Make a round corner around it + else { + display_setColor(200, 200, 200); + display_fillRoundRect(6, 6, 314, 234); + } + + //Display the text + display_setFont(smallFont); + display_setBackColor(200, 200, 200); + display_setColor(VGA_BLACK); + display_print(message, CENTER, 110); +} + +/* Shows a transparent message in live mode */ +void showTransMessage(char* msg) { + //Set text color + changeTextColor(); + //Set background transparent + display_setBackColor(VGA_TRANSPARENT); + //Display to screen in big font + display_setFont(bigFont); + //Display higher if spot is enabled + if (spotEnabled) + display_print(msg, CENTER, 70); + else + display_print(msg, CENTER, 110); + //Switch back to small font + display_setFont(smallFont); + //Wait some time to read the text + delay(1000); +} + +/* Draw a BigFont Text in the center of a menu*/ +void drawCenterElement(int element) { + display_setFont(bigFont); + display_setColor(VGA_BLACK); + display_setBackColor(200, 200, 200); + display_printNumI(element, CENTER, 80, 2, '0'); + display_setFont(smallFont); +} + +/* Draws the border for the main menu */ +void drawMainMenuBorder() +{ + display_setColor(VGA_BLACK); + display_fillRoundRect(5, 5, 315, 235); + display_fillRoundRect(4, 4, 316, 236); +} + +/* Draw a title on the screen */ +void drawTitle(char* name, bool firstStart) { + if (firstStart) + display_fillScr(200, 200, 200); + else { + display_setColor(200, 200, 200); + display_fillRoundRect(6, 6, 314, 234); + } + display_setFont(bigFont); + display_setBackColor(200, 200, 200); + display_setColor(VGA_BLACK); + display_print(name, CENTER, 25); + display_setFont(smallFont); +} + +/* Shows the hadware diagnostics */ +void showDiagnostic() { + //Display title & background + display_fillScr(200, 200, 200); + display_setFont(bigFont); + display_setBackColor(200, 200, 200); + display_setColor(VGA_BLUE); + display_print((char*) "Self-diagnostic", CENTER, 10); + + //Change text color and font + display_setFont(smallFont); + display_setColor(VGA_BLACK); + + //Display hardware module names + display_print((char*) "Spot sensor ", 50, 50); + display_print((char*) "Display ", 50, 70); + display_print((char*) "Visual camera", 50, 90); + display_print((char*) "Touch screen ", 50, 110); + display_print((char*) "SD card ", 50, 130); + display_print((char*) "Battery gauge", 50, 150); + display_print((char*) "Lepton config", 50, 170); + display_print((char*) "Lepton data ", 50, 190); + + //Check spot sensor + if (checkDiagnostic(diag_spot)) + display_print((char*) "OK ", 220, 50); + else + display_print((char*) "Failed", 220, 50); + + //Check display + if (checkDiagnostic(diag_display)) + display_print((char*) "OK ", 220, 70); + else + display_print((char*) "Failed", 220, 70); + + //Check visual camera + if (checkDiagnostic(diag_camera)) + display_print((char*) "OK ", 220, 90); + else + display_print((char*) "Failed", 220, 90); + + //Check touch screen + if (checkDiagnostic(diag_touch)) + display_print((char*) "OK ", 220, 110); + else + display_print((char*) "Failed", 220, 110); + + //Check sd card + if (checkDiagnostic(diag_sd)) + display_print((char*) "OK ", 220, 130); + else + display_print((char*) "Failed", 220, 130); + + //Check battery gauge + if (checkDiagnostic(diag_bat)) + display_print((char*) "OK ", 220, 150); + else + display_print((char*) "Failed", 220, 150); + + //Check lepton config + if (checkDiagnostic(diag_lep_conf)) + display_print((char*) "OK ", 220, 170); + else + display_print((char*) "Failed", 220, 170); + + //Check lepton data + if (checkDiagnostic(diag_lep_data)) + display_print((char*) "OK ", 220, 190); + else + display_print((char*) "Failed", 220, 190); + + //Wait some time + delay(1500); + + //Show hint + display_print((char*) "Trying to start..", CENTER, 220); +} + +/* Draw the Boot screen */ +void bootScreen() { + //Set rotation + setDisplayRotation(); + + //Init the buttons + buttons_init(); + + //Set Fonts + buttons_setTextFont((uint8_t*) smallFont); + display_setFont(smallFont); + + //Draw Screen + display_fillScr(255, 255, 255); + display_setFont(bigFont); + display_setBackColor(255, 255, 255); + display_setColor(VGA_BLACK); + + //Show the logo and boot text + display_writeRect4BPP(90, 35, 140, 149, logoBitmap, logoColors); + display_print((char*) "Booting", CENTER, 194); + display_setFont(smallFont); + + //Show hardware version + if (teensyVersion == teensyVersion_new) + display_print((char*)"DIY-Thermocam V2", CENTER, 10); + else + display_print((char*)"DIY-Thermocam V1", CENTER, 10); + + //Display version + display_print((char*)Version, CENTER, 220); + + //Wait some time + delay(2000); +} diff --git a/Firmware_V2/src/gui/gui.h b/Firmware_V2/src/gui/gui.h new file mode 100644 index 0000000..9c36ce3 --- /dev/null +++ b/Firmware_V2/src/gui/gui.h @@ -0,0 +1,31 @@ +/* +* +* GUI - Main Methods to lcd the Graphical-User-Interface +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef GUI_H +#define GUI_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void bootScreen(); +void changeTextColor(); +void drawCenterElement(int element); +void drawMainMenuBorder(); +void drawTitle(char* name, bool firstStart = false); +void floatToChar(char* buffer, float val); +void showDiagnostic(); +void showFullMessage(char* message, bool small = false); +void showTransMessage(char* msg); + +#endif /* GUI_H */ diff --git a/Firmware_V2/src/gui/livemode.cpp b/Firmware_V2/src/gui/livemode.cpp new file mode 100644 index 0000000..a861ad0 --- /dev/null +++ b/Firmware_V2/src/gui/livemode.cpp @@ -0,0 +1,316 @@ +/* +* +* LIVE MODE - GUI functions used in the live mode +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Display battery status in percentage */ +void displayBatteryStatus() { + //Check battery status every 60 seconds + if (batTimer == 0) + batTimer = millis(); + if ((millis() - batTimer) > 60000) { + checkBattery(); + batTimer = millis(); + } + + //USB Power only + if (batPercentage == -1) + display_print((char*) "USB Power", 225, 0); + + //Low Battery + else if (batPercentage == 0) + display_print((char*) "LOW", 270, 0); + + //Display battery status in percentage + else { + //Charging, show plus symbol + if ((analogRead(pin_usb_measure) > 50) && (batPercentage != 100)) + { + display_printNumI(batPercentage, 270, 0, 3, ' '); + display_print((char*) "%", 300, 0); + display_print((char*) "+", 310, 0); + } + + //Not charging + else + { + display_printNumI(batPercentage, 280, 0, 3, ' '); + display_print((char*) "%", 310, 0); + } + } +} + +/* Display the current time on the screen*/ +void displayTime() { + //Tiny font + if ((teensyVersion == teensyVersion_old) || (!hqRes)) { + display_printNumI(hour(), 5, 228, 2, '0'); + display_print((char*) ":", 23, 228); + display_printNumI(minute(), 27, 228, 2, '0'); + display_print((char*) ":", 45, 228); + display_printNumI(second(), 49, 228, 2, '0'); + } + + //Small font + else + { + display_printNumI(hour(), 5, 228, 2, '0'); + display_print((char*) ":", 20, 228); + display_printNumI(minute(), 27, 228, 2, '0'); + display_print((char*) ":", 42, 228); + display_printNumI(second(), 49, 228, 2, '0'); + } +} + +/* Display the date on screen */ +void displayDate() { + //Tiny font + if ((teensyVersion == teensyVersion_old) || (!hqRes)) { + display_printNumI(day(), 5, 0, 2, '0'); + display_print((char*) ".", 23, 0); + display_printNumI(month(), 27, 0, 2, '0'); + display_print((char*) ".", 45, 0); + display_printNumI(year(), 49, 0, 4); + } + + //Small font + else + { + display_printNumI(day(), 5, 0, 2, '0'); + display_print((char*) ".", 20, 0); + display_printNumI(month(), 27, 0, 2, '0'); + display_print((char*) ".", 42, 0); + display_printNumI(year(), 49, 0, 4); + } +} + +/* Display the warmup message on screen*/ +void displayWarmup() { + char buffer[25]; + + //Create string + sprintf(buffer, "Sensor warmup, %2ds left", (int)abs(30 - ((millis() - calTimer) / 1000))); + //Tinyfont + if ((teensyVersion == teensyVersion_old) || (!hqRes)) + display_print(buffer, 45, 200); + //Smallfont + else + display_print(buffer, 65, 200); +} + +/* Display the current temperature mode on top*/ +void displayTempMode() +{ + char buffer[10]; + + //Warmup + if (calStatus == cal_warmup) + sprintf(buffer, " WARMUP"); + //Limits locked + else if (limitsLocked) + sprintf(buffer, " LOCKED"); + //Auto mode + else if (autoMode) + sprintf(buffer, " AUTO"); + //Temperature presets + else + { + //Check which preset is active + byte minMaxPreset; + byte read = EEPROM.read(eeprom_minMaxPreset); + if ((read >= minMax_preset1) && (read <= minMax_preset3)) + minMaxPreset = read; + else + minMaxPreset = minMax_temporary; + //Choose corresponding text + if ((minMaxPreset == minMax_preset1) && (EEPROM.read(eeprom_minMax1Set) == eeprom_setValue)) + sprintf(buffer, "PRESET 1"); + else if ((minMaxPreset == minMax_preset2) && (EEPROM.read(eeprom_minMax2Set) == eeprom_setValue)) + sprintf(buffer, "PRESET 2"); + else if ((minMaxPreset == minMax_preset3) && (EEPROM.read(eeprom_minMax3Set) == eeprom_setValue)) + sprintf(buffer, "PRESET 3"); + else + sprintf(buffer, " MANUAL"); + } + + //Low resolution display + if ((teensyVersion == teensyVersion_old) || (!hqRes)) + display_print(buffer, 120, 0); + //HQ resolution display + else + display_print(buffer, 140, 0); +} + +/* Display the minimum and maximum point on the screen */ +void displayMinMaxPoint(bool min) +{ + int16_t xpos, ypos; + + //Calculate x and y position + if (min) + calculatePointPos(&xpos, &ypos, minTempPos); + else + calculatePointPos(&xpos, &ypos, maxTempPos); + + //Draw the marker + display_drawLine(xpos, ypos, xpos, ypos); + + //Warmup completed, show absolute temp + if (calStatus != cal_warmup) + { + //Calc x position for the text + xpos -= 20; + if (xpos < 0) + xpos = 0; + if (xpos > 279) + xpos = 279; + + //Calc y position for the text + ypos += 15; + if (ypos > 229) + ypos = 229; + + //Show min or max value as absolute temperature + if (min) + display_printNumF(calFunction(minTempVal), 2, xpos, ypos); + else + display_printNumF(calFunction(maxTempVal), 2, xpos, ypos); + } + + //Warmup, show C / H + else + { + //Calc x and y position + xpos += 4; + if (xpos >= 310) + xpos -= 10; + if (ypos > 230) + ypos = 230; + + //Show min or max value as symbol + if (min) + display_print('C', xpos, ypos); + else + display_print('H', xpos, ypos); + } +} + +/* Display free space on screen*/ +void displayFreeSpace() { + //Tinyfont + if ((teensyVersion == teensyVersion_old) || (!hqRes)) + display_print(sdInfo, 197, 228); + //Smallfont + else + display_print(sdInfo, 220, 228); +} + +/* Show the current spot temperature on screen*/ +void showSpot() { + char buffer[10]; + + //Draw the spot circle + display_drawCircle(160, 120, 12); + + //Draw the lines + display_drawLine(136, 120, 148, 120); + display_drawLine(172, 120, 184, 120); + display_drawLine(160, 96, 160, 108); + display_drawLine(160, 132, 160, 144); + + //Convert spot temperature to char array + floatToChar(buffer, spotTemp); + + //Print value on display + display_print(buffer, 145, 150); +} + +/* Display addition information on the screen */ +void displayInfos() { + //Set text color + changeTextColor(); + //Set font and background + display_setBackColor(VGA_TRANSPARENT); + + //For Teensy 3.6, set small font + if ((teensyVersion == teensyVersion_new) && (hqRes)) + display_setFont((uint8_t*) smallFont); + //For Teensy 3.1 / 3.2, set tiny font + else + display_setFont((uint8_t*) tinyFont); + + //Set write to image, not display + display_writeToImage = true; + + //Check warmup + checkWarmup(); + + //If not saving image or video + if ((imgSave != imgSave_create) && (!videoSave)) { + //Show battery status in percantage + if (batteryEnabled) + displayBatteryStatus(); + //Show the time + if (timeEnabled) + displayTime(); + //Show the date + if (dateEnabled) + displayDate(); + //Show storage information + if (storageEnabled) + displayFreeSpace(); + //Display warmup if required + if (calStatus == cal_warmup) + displayWarmup(); + //Display temperature mode + displayTempMode(); + } + + //Show the minimum / maximum points + if (minMaxPoints & minMaxPoints_min) + displayMinMaxPoint(true); + if (minMaxPoints & minMaxPoints_max) + displayMinMaxPoint(false); + + //Show the spot in the middle + if (spotEnabled) + showSpot(); + + //Show the color bar when warmup is over and if enabled, not in visual mode + if ((colorbarEnabled) && (calStatus != cal_warmup) && (displayMode != displayMode_visual)) + showColorBar(); + + //Show the temperature points + showTemperatures(); + + //Set write back to display + display_writeToImage = false; +} diff --git a/Firmware_V2/src/gui/livemode.h b/Firmware_V2/src/gui/livemode.h new file mode 100644 index 0000000..c2cbcf6 --- /dev/null +++ b/Firmware_V2/src/gui/livemode.h @@ -0,0 +1,31 @@ +/* +* +* LIVE MODE - GUI functions used in the live mode +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef LIVEMODE_H +#define LIVEMODE_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void displayBatteryStatus(); +void displayDate(); +void displayFreeSpace(); +void displayInfos(); +void displayMinMaxPoint(bool min); +void displayTempMode(); +void displayTime(); +void displayWarmup(); +void showSpot(); + +#endif /* LIVEMODE_H */ diff --git a/Firmware_V2/src/gui/loadmenu.cpp b/Firmware_V2/src/gui/loadmenu.cpp new file mode 100644 index 0000000..8ca55cf --- /dev/null +++ b/Firmware_V2/src/gui/loadmenu.cpp @@ -0,0 +1,530 @@ +/* +* +* LOAD MENU - Display the menu to load images and videos +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Display the GUI elements for the load menu */ +void displayGUI(int imgCount, char* infoText) { + //Set text color + changeTextColor(); + //set Background transparent + display_setBackColor(VGA_TRANSPARENT); + display_setFont(bigFont); + //Delete image or video from internal storage + display_print((char*) "Delete", 220, 10); + //Find image by time and date + display_print((char*) "Find", 5, 10); + //Display prev/next if there is more than one image + if (imgCount != 1) { + display_print((char*) "<", 10, 110); + display_print((char*) ">", 295, 110); + } + //Convert image to bitmap + display_print((char*) "Convert", 5, 210); + //Exit to main menu + display_print((char*) "Exit", 250, 210); + display_setFont(smallFont); + //Display either frame number or image date and time + display_print(infoText, 80, 12); +} + +/* Asks the user if he wants to delete the video */ +void deleteVideo(char* dirname) { + //Title & Background + drawTitle((char*) "Delete Video", true); + display_setColor(VGA_BLACK); + display_setFont(smallFont); + display_setBackColor(200, 200, 200); + display_print((char*)"Do you want to delete this video?", CENTER, 66); + display_print((char*)"This will also remove the", CENTER, 105); + display_print((char*)"other related files to it. ", CENTER, 125); + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(15, 160, 140, 55, (char*) "Yes"); + buttons_addButton(165, 160, 140, 55, (char*) "No"); + buttons_drawButtons(); + buttons_setTextFont(smallFont); + //Touch handler + while (true) { + //If touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(true); + //YES + if (pressedButton == 0) { + showFullMessage((char*) "Delete video.."); + //Start SD + startAltClockline(); + + //Delete the ending for a video + dirname[14] = '\0'; + + //Go into the video folder + sd.chdir("/"); + sd.chdir(dirname); + //Delete all files + uint16_t videoCounter = 0; + bool exists; + char filename[] = "00000.DAT"; + //Go through the frames + while (1) { + //Get the frame name + frameFilename(filename, videoCounter); + //Check frame existance + exists = sd.exists(filename); + //If the frame does not exists, end remove + if (!exists) + break; + //Otherwise remove file + else + sd.remove(filename); + //Remove Bitmap if there + strcpy(&filename[5], ".BMP"); + if (sd.exists(filename)) + sd.remove(filename); + //Remove Jpeg if there + strcpy(&filename[5], ".JPG"); + if (sd.exists(filename)) + sd.remove(filename); + //Reset ending + strcpy(&filename[5], ".DAT"); + //Raise counter + videoCounter++; + } + //Switch back to the root + sd.chdir("/"); + //Remove the folder itself + sd.rmdir(dirname); + //End SD + endAltClockline(); + showFullMessage((char*) "Video deleted"); + delay(1000); + return; + } + //NO + else if (pressedButton == 1) { + //Start SD + startAltClockline(); + sd.chdir("/"); + endAltClockline(); + return; + } + } + } +} + +/* Asks the user if he wants to delete the image */ +void deleteImage(char* filename) { + //Title & Background + drawTitle((char*) "Delete Image", true); + display_setColor(VGA_BLACK); + display_setFont(smallFont); + display_setBackColor(200, 200, 200); + display_print((char*)"Do you want to delete this image?", CENTER, 66); + display_print((char*)"This will also remove the", CENTER, 105); + display_print((char*)"other related files to it. ", CENTER, 125); + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(165, 160, 140, 55, (char*) "Yes"); + buttons_addButton(15, 160, 140, 55, (char*) "No"); + buttons_drawButtons(); + buttons_setTextFont(smallFont);; + //Touch handler + while (true) { + //If touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(true); + //YES + if (pressedButton == 0) { + showFullMessage((char*) "Delete image.."); + //Start SD + startAltClockline(); + //Delete .DAT file + sd.remove(filename); + //Delete .JPG file + strcpy(&filename[14], ".JPG"); + if (sd.exists(filename)) + sd.remove(filename); + //Delete .BMP file + strcpy(&filename[14], ".BMP"); + if (sd.exists(filename)) + sd.remove(filename); + endAltClockline(); + showFullMessage((char*) "Image deleted"); + delay(1000); + return; + } + //NO + else if (pressedButton == 1) { + return; + } + } + } +} + +/* Asks the user if he really wants to convert the image/video */ +bool convertPrompt() { + //Title & Background + drawTitle((char*) "Conversion Prompt", true); + display_setColor(VGA_BLACK); + display_setFont(smallFont); + display_setBackColor(200, 200, 200); + display_print((char*)"Do you want to convert?", CENTER, 66); + display_print((char*)"That process will create", CENTER, 105); + display_print((char*)"bitmap(s) out of the raw data.", CENTER, 125); + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(165, 160, 140, 55, (char*) "Yes"); + buttons_addButton(15, 160, 140, 55, (char*) "No"); + buttons_drawButtons(); + buttons_setTextFont(smallFont); + //Wait for touch release + while (touch_touched()); + //Touch handler + while (true) { + //If touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(true); + //YES + if (pressedButton == 0) { + return true; + } + //NO + else if (pressedButton == 1) { + return false; + } + } + } +} + +/* Convert a raw image lately to BMP */ +void convertImage(char* filename) { + + //Check if image is a bitmap + if (filename[15] == 'B') { + showFullMessage((char*) "Image is already converted"); + delay(500); + return; + } + + //Check if the image is already there + strcpy(&filename[14], ".BMP"); + startAltClockline(); + bool exists = sd.exists(filename); + endAltClockline(); + + //If image is already converted, return + if (exists) { + showFullMessage((char*) "Image is already converted"); + delay(500); + strcpy(&filename[14], ".DAT"); + return; + } + + //If the user does not want to convert, return + if (!convertPrompt()) { + strcpy(&filename[14], ".DAT"); + return; + } + + //Show convert message + showFullMessage((char*) "Converting image to BMP.."); + + //Save image + saveBuffer(filename); + + //Show finish message + showFullMessage((char*) "Image converted"); + delay(1000); + strcpy(&filename[14], ".DAT"); +} + +/* Convert a raw video lately to BMP frames */ +void convertVideo(char* dirname) { + uint16_t frames = getVideoFrameNumber(dirname); + char filename[] = "00000.BMP"; + + //Switch Clock to Alternative + startAltClockline(); + + //Delete the ending for a video + dirname[14] = '\0'; + + //Go into the folder + sd.chdir(dirname); + + //Get the frame name of the first frame + frameFilename(filename, 0); + bool exists = sd.exists(filename); + endAltClockline(); + + //If video is already converted, return + if (exists) { + showFullMessage((char*) "Video is already converted"); + delay(500); + return; + } + + //If the user does not want to convert the video, return + if (!convertPrompt()) + return; + + //Show convert message + showFullMessage((char*) "Converting video to BMP.."); + delay(1000); + + //Convert video + processVideoFrames(frames, dirname); + videoSave = videoSave_disabled; +} + +/* Loads an image from the SDCard and prints it on screen */ +void openImage(char* filename, int imgCount) { + //Show message on screen + showFullMessage((char*) "Please wait, image is loading.."); + + //Display raw data + if (filename[15] == 'D') { + //Load Raw data + loadRawData(filename); + //Display Raw Data + displayRawData(); + } + + //Load bitmap + else if (filename[15] == 'B') { + loadBMPImage(filename); + } + + //Unsupported file type + else { + showFullMessage((char*) "Unsupported file type"); + delay(1000); + return; + } + + //Create string for time and date + char nameStr[20] = { + //Day + filename[6], filename[7], '.', + //Month + filename[4], filename[5], '.', + //Year + filename[2], filename[3], ' ', + //Hour + filename[8], filename[9], ':', + //Minute + filename[10], filename[11], ':', + //Second + filename[12], filename[13], '\0' + }; + + //Display GUI + displayGUI(imgCount, nameStr); + + //Attach interrupt + attachInterrupt(pin_touch_irq, loadTouchIRQ, FALLING); + + //Wait for touch press + while (loadTouch == loadTouch_none); + + //Disable touch handler + detachInterrupt(pin_touch_irq); +} + +/* Get the number of frames in the video */ +uint16_t getVideoFrameNumber(char* dirname) { + uint16_t videoCounter = 0; + bool exists; + char filename[] = "00000.DAT"; + //Switch Clock to Alternative + startAltClockline(); + //Go into the folder + sd.chdir(dirname); + //Look how many frames we have + while (true) { + //Get the frame name + frameFilename(filename, videoCounter); + //Check frame existance + exists = sd.exists(filename); + //Raise counter + if (exists) + videoCounter++; + //Leave + else + break; + } + //Switch Clock back to Standard + endAltClockline(); + return videoCounter; +} + +/* Display the selected video frame */ +void displayVideoFrame(int i, char* dirname) +{ + char filename[] = "00000.DAT"; + + //Get the frame name + frameFilename(filename, i); + + //Load Raw data + loadRawData(filename, dirname); + + //Display Raw Data + displayRawData(); +} + +/* Play a video from the internal storage */ +void playVideo(char* dirname, int imgCount) { + char buffer[14]; + //Save the current frame number + int frameNumber = 0; + + //Get the total number of frames in the dir + uint16_t numberOfFrames = getVideoFrameNumber(dirname); + + //Jump here when pausing a video +showFrame: + //Display frame + displayVideoFrame(frameNumber, dirname); + //Create string + sprintf(buffer, "%5d / %-5d", frameNumber + 1, numberOfFrames); + //Display GUI + displayGUI(imgCount, buffer); + //Display play message + display_setFont(bigFont); + if (spotEnabled) + display_print((char*) "Play", CENTER, 70); + else + display_print((char*) "Play", CENTER, 110); + display_setFont(smallFont); + + //Repeat until we get a valid touch + do { + //Wait for touch press + while (!touch_touched()); + //Interpret touch coordinates + loadTouchIRQ(); + } while (loadTouch == loadTouch_none); + + //Wait for touch to release + while (touch_touched()); + + //Check if we play the video + if (loadTouch != loadTouch_middle) + return; + loadTouch = loadTouch_none; + + //Play forever + while (true) { + //Go through the frames + for (; frameNumber < numberOfFrames; frameNumber++) { + //Check for touch press + if (touch_touched()) + //Get touch function + loadTouchIRQ(); + //Pause the video + if (loadTouch == loadTouch_middle) + { + //Wait for touch release + while (touch_touched()); + //Display the static frame + goto showFrame; + } + //Any other action + if (loadTouch != loadTouch_none) + return; + + //Display frame + displayVideoFrame(frameNumber, dirname); + //Create string + sprintf(buffer, "%5d / %-5d", frameNumber + 1, numberOfFrames); + //Display GUI + displayGUI(imgCount, buffer); + } + //Reset frame number for next play + frameNumber = 0; + } +} + +/* Shows a menu where the user can choose the time & date items for the image */ +int loadMenu(char* title, int* array, int length) { + //Draw the title on screen + drawTitle(title); + //Draw the Buttons + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 70, 70, (char*) "-"); + buttons_addButton(230, 60, 70, 70, (char*) "+"); + buttons_addButton(20, 150, 130, 70, (char*) "Back"); + buttons_addButton(170, 150, 130, 70, (char*) "Choose"); + buttons_drawButtons(); + int currentPos = 0; + //Display the first element for the array + drawCenterElement(array[currentPos]); + //Touch handler + while (true) { + //Touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(); + //Minus + if (pressedButton == 1) { + //Decrease element by one + if (currentPos > 0) + currentPos--; + //Go from lowest to highest element + else if (currentPos == 0) + currentPos = length - 1; + //Draw it on screen + drawCenterElement(array[currentPos]); + } + //Plus + else if (pressedButton == 0) { + //Increase element by one + if (currentPos < (length - 1)) + currentPos++; + //Go from highest to lowest element + else if (currentPos == (length - 1)) + currentPos = 0; + //Draw it on screen + drawCenterElement(array[currentPos]); + } + //Back - return minus 1 + else if (pressedButton == 2) { + return -1; + } + //Set - return element's position + else if (pressedButton == 3) { + return currentPos; + } + } + } +} diff --git a/Firmware_V2/src/gui/loadmenu.h b/Firmware_V2/src/gui/loadmenu.h new file mode 100644 index 0000000..71ab7d1 --- /dev/null +++ b/Firmware_V2/src/gui/loadmenu.h @@ -0,0 +1,33 @@ +/* +* +* LOAD MENU - Display the menu to load images and videos +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef LOADMENU_H +#define LOADMENU_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void convertImage(char* filename); +bool convertPrompt(); +void convertVideo(char* dirname); +void deleteImage(char* filename); +void deleteVideo(char* dirname); +void displayGUI(int imgCount, char* infoText); +void displayVideoFrame(int i, char* dirname); +uint16_t getVideoFrameNumber(char* dirname); +int loadMenu(char* title, int* array, int length); +void openImage(char* filename, int imgCount); +void playVideo(char* dirname, int imgCount); + +#endif /* LOADMENU_H */ diff --git a/Firmware_V2/src/gui/mainmenu.cpp b/Firmware_V2/src/gui/mainmenu.cpp new file mode 100644 index 0000000..cb3b567 --- /dev/null +++ b/Firmware_V2/src/gui/mainmenu.cpp @@ -0,0 +1,1806 @@ +/* + * + * MAIN MENU - Display the main menu with icons + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Draws the background in the main menu */ +void mainMenuBackground() +{ + display_setColor(120, 120, 120); + display_fillRoundRect(6, 6, 314, 234); + display_setColor(200, 200, 200); + display_fillRect(6, 36, 314, 180); + display_setColor(VGA_BLACK); + display_drawHLine(6, 36, 314); + display_drawHLine(6, 180, 314); +} + +/* Draws the content of the selection menu*/ +void drawSelectionMenu() +{ + //Buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(15, 45, 38, 77, (char*) "<", 0, true); + buttons_addButton(267, 45, 38, 77, (char*) ">", 0, true); + buttons_addButton(15, 188, 120, 40, (char*) "Back"); + buttons_addButton(95, 132, 130, 35, (char*) "OK"); + buttons_drawButtons(); + //Border + display_setColor(VGA_BLUE); + display_drawRect(65, 57, 257, 111); +} + +/* Draws the title in the main menu */ +void mainMenuTitle(char* title) +{ + display_setFont(bigFont); + display_setBackColor(120, 120, 120); + display_setColor(VGA_WHITE); + display_print(title, CENTER, 14); +} + +/* Draws the current selection in the menu */ +void mainMenuSelection(char* selection) +{ + //Clear the old content + display_setColor(VGA_WHITE); + display_fillRect(66, 58, 257, 111); + //Print the text + display_setBackColor(VGA_WHITE); + display_setColor(VGA_BLUE); + display_print(selection, CENTER, 77); +} + +/* Asks the user if he really wants to enter mass storage mode */ +bool massStoragePrompt() +{ + //Title & Background + drawTitle((char*) "USB File Transfer"); + display_setColor(VGA_BLACK); + display_setFont(smallFont); + display_setBackColor(200, 200, 200); + display_print((char*) "Do you want to enter mass storage", CENTER, 65); + display_print((char*) "to transfer files to the PC?", CENTER, 85); + display_print((char*) "Do not use this for FW updates", CENTER, 105); + display_print((char*) "or for the USB serial connection.", CENTER, 125); + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(165, 160, 140, 55, (char*) "Yes"); + buttons_addButton(15, 160, 140, 55, (char*) "No"); + buttons_drawButtons(); + buttons_setTextFont(smallFont); + //Wait for touch release + while (touch_touched()) + ; + //Touch handler + while (true) + { + //If touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //YES + if (pressedButton == 0) + { + return true; + } + //NO + if (pressedButton == 1) + { + return false; + } + } + } +} + +/* Calibration Repeat Choose */ +bool calibrationRepeat() +{ + //Title & Background + mainMenuBackground(); + mainMenuTitle((char*) "Bad Calibration"); + display_setColor(VGA_BLACK); + display_setFont(bigFont); + display_setBackColor(200, 200, 200); + display_print((char*) "Try again?", CENTER, 66); + display_setFont(smallFont); + display_setBackColor(120, 120, 120); + display_setColor(VGA_WHITE); + display_print((char*) "Use different calibration objects", CENTER, 201); + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(165, 106, 140, 55, (char*) "Yes"); + buttons_addButton(15, 106, 140, 55, (char*) "No"); + buttons_drawButtons(); + //Touch handler + while (true) + { + //If touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //YES + if (pressedButton == 0) + { + return true; + } + //NO + if (pressedButton == 1) + { + return false; + } + } + } +} + +/* Calibration*/ +void calibrationScreen(bool firstStart) +{ + //Normal mode + if (firstStart == false) + { + mainMenuBackground(); + mainMenuTitle((char*) "Calibrating.."); + display_setColor(VGA_BLACK); + display_setBackColor(200, 200, 200); + display_setFont(smallFont); + display_print((char*) "Point the camera to different", CENTER, 63); + display_print((char*) "hot and cold objects in the area.", CENTER, 96); + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(90, 188, 140, 40, (char*) "Abort"); + buttons_drawButtons(); + display_setFont(bigFont); + display_print((char*) "Status: 0%", CENTER, 140); + } + //First start + else + { + display_fillScr(200, 200, 200); + display_setFont(bigFont); + display_setBackColor(200, 200, 200); + display_setColor(VGA_BLACK); + display_print((char*) "Calibrating..", CENTER, 100); + display_print((char*) "Status: 0%", CENTER, 140); + } +} + +/* Menu to add or remove temperature points to the thermal image */ +bool tempPointsMenu() +{ + //Still in warmup, do not add points + if (calStatus == cal_warmup) + { + showFullMessage((char*) "Please wait for sensor warmup", true); + delay(1000); + return false; + } + redraw: + //Background + mainMenuBackground(); + //Title + mainMenuTitle((char*) "Temp. points"); + //Draw the selection menu + buttons_deleteAllButtons(); + buttons_setTextFont(smallFont); + buttons_addButton(15, 45, 90, 122, (char*) "Add"); + buttons_addButton(115, 45, 90, 122, (char*) "Remove"); + buttons_addButton(215, 45, 90, 122, (char*) "Clear"); + buttons_addButton(15, 188, 120, 40, (char*) "Back"); + buttons_drawButtons(); + //Save the current position inside the menu + while (true) + { + //Touch screen pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //Add + if (pressedButton == 0) + { + tempPointFunction(false); + goto redraw; + } + //Remove + if (pressedButton == 1) + { + tempPointFunction(true); + goto redraw; + } + //Clear + if (pressedButton == 2) + { + clearTempPoints(); + showFullMessage((char*) "All points cleared", true); + delay(1000); + goto redraw; + } + //BACK + if (pressedButton == 3) + return false; + } + } +} + +/* Select the color for the live mode string */ +void hotColdColorMenuString(int pos) +{ + char* text = (char*) ""; + switch (pos) + { + //White + case 0: + text = (char*) "White"; + break; + //Black + case 1: + text = (char*) "Black"; + break; + //Red + case 2: + text = (char*) "Red"; + break; + //Green + case 3: + text = (char*) "Green"; + break; + //Blue + case 4: + text = (char*) "Blue"; + break; + } + mainMenuSelection(text); +} + +/* Menu to display the color in hot/cold color mode */ +bool hotColdColorMenu() +{ + //Save the current position inside the menu + byte hotColdColorMenuPos; + if (hotColdMode == hotColdMode_hot) + hotColdColorMenuPos = 2; + else if (hotColdMode == hotColdMode_cold) + hotColdColorMenuPos = 4; + else + hotColdColorMenuPos = 0; + //Background + mainMenuBackground(); + //Title + mainMenuTitle((char*) "Select color"); + //Draw the selection menu + drawSelectionMenu(); + //Draw the current item + hotColdColorMenuString(hotColdColorMenuPos); + //Save the current position inside the menu + while (true) + { + //Touch screen pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //SELECT + if (pressedButton == 3) + { + //Save + hotColdColor = hotColdColorMenuPos; + //Write to EEPROM + EEPROM.write(eeprom_hotColdColor, hotColdColor); + return true; + } + //BACK + if (pressedButton == 2) + return false; + //BACKWARD + else if (pressedButton == 0) + { + if (hotColdColorMenuPos > 0) + hotColdColorMenuPos--; + else if (hotColdColorMenuPos == 0) + hotColdColorMenuPos = 4; + } + //FORWARD + else if (pressedButton == 1) + { + if (hotColdColorMenuPos < 4) + hotColdColorMenuPos++; + else if (hotColdColorMenuPos == 4) + hotColdColorMenuPos = 0; + } + //Change the menu name + hotColdColorMenuString(hotColdColorMenuPos); + } + } +} + +/* Touch handler for the hot & cold limit changer menu */ +void hotColdChooserHandler() +{ + //Help variables + char margin[14]; + + //Display level as temperature + if (!tempFormat) + { + sprintf(margin, "Limit: %dC", hotColdLevel); + } + else + { + sprintf(margin, "Limit: %dF", hotColdLevel); + } + display_print(margin, CENTER, 153); + + //Touch handler + while (true) + { + waitTouch: + + //If touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //RESET + if (pressedButton == 0) + { + if (hotColdMode == hotColdMode_cold) + hotColdLevel = (int16_t) round( + calFunction( + 0.2 * (maxValue - minValue) + minValue)); + if (hotColdMode == hotColdMode_hot) + hotColdLevel = (int16_t) round( + calFunction( + 0.8 * (maxValue - minValue) + minValue)); + } + //SELECT + else if (pressedButton == 1) + { + //Save to EEPROM + EEPROM.write(eeprom_hotColdLevelHigh, + (hotColdLevel & 0xFF00) >> 8); + EEPROM.write(eeprom_hotColdLevelLow, hotColdLevel & 0x00FF); + break; + } + //MINUS + else if (pressedButton == 2) + { + if (hotColdLevel > round(calFunction(minValue))) + hotColdLevel--; + else + goto waitTouch; + } + //PLUS + else if (pressedButton == 3) + { + if (hotColdLevel < round(calFunction(maxValue))) + hotColdLevel++; + else + goto waitTouch; + } + //Prepare the preview image + delay(10); + createThermalImg(true); + //Display the preview image + display_drawBitmap(80, 48, 160, 120, smallBuffer, 1); + + //Display level as temperature + if (!tempFormat) + { + sprintf(margin, "Limit: %dC", hotColdLevel); + } + else + { + sprintf(margin, "Limit: %dF", hotColdLevel); + } + display_print(margin, CENTER, 153); + } + } +} + +/* Select the limit in hot/cold mode */ +void hotColdChooser() +{ + //Background & title + mainMenuBackground(); + mainMenuTitle((char*) "Set Limit"); + + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(15, 188, 140, 40, (char*) "Reset"); + buttons_addButton(165, 188, 140, 40, (char*) "OK"); + buttons_addButton(15, 48, 55, 120, (char*) "-"); + buttons_addButton(250, 48, 55, 120, (char*) "+"); + buttons_drawButtons(); + + //Draw the border for the preview image + display_setColor(VGA_BLACK); + display_drawRect(79, 47, 241, 169); + + //Set text color + display_setFont(smallFont); + display_setBackColor(VGA_TRANSPARENT); + changeTextColor(); + + //Find min and max values + if ((autoMode) && (!limitsLocked)) + { + lepton_getRawValues(); + limitValues(); + } + + //Calculate initial level + if (hotColdMode == hotColdMode_cold) + hotColdLevel = (int16_t) round( + calFunction(0.2 * (maxValue - minValue) + minValue)); + if (hotColdMode == hotColdMode_hot) + hotColdLevel = (int16_t) round( + calFunction(0.8 * (maxValue - minValue) + minValue)); + + //Prepare the preview image + delay(10); + createThermalImg(true); + + //Display the preview image + display_drawBitmap(80, 48, 160, 120, smallBuffer, 1); + + //Go into the normal touch handler + hotColdChooserHandler(); +} + +/* Menu to display hot or cold areas */ +bool hotColdMenu() +{ + //Still in warmup, do not add points + if (calStatus == cal_warmup) + { + showFullMessage((char*) "Please wait for sensor warmup", true); + delay(1000); + return false; + } + + redraw: + //Background + mainMenuBackground(); + + //Title + mainMenuTitle((char*) "Hot / Cold"); + + //Draw the selection menu + buttons_deleteAllButtons(); + buttons_setTextFont(smallFont); + buttons_addButton(15, 45, 90, 122, (char*) "Hot"); + buttons_addButton(115, 45, 90, 122, (char*) "Cold"); + buttons_addButton(215, 45, 90, 122, (char*) "Disabled"); + buttons_addButton(15, 188, 120, 40, (char*) "Back"); + buttons_drawButtons(); + + //Save the current position inside the menu + while (true) + { + //Touch screen pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //Hot + if (pressedButton == 0) + { + //Set marker to hot + hotColdMode = hotColdMode_hot; + + //Choose the color + if (hotColdColorMenu()) + //Set the limit + hotColdChooser(); + + //Go back + else + { + hotColdMode = hotColdMode_disabled; + goto redraw; + } + + //Leave loop + break; + } + //Cold + if (pressedButton == 1) + { + //Set marker to cold + hotColdMode = hotColdMode_cold; + + //Choose the color + if (hotColdColorMenu()) + //Set the limit + hotColdChooser(); + + //Go back + else + { + hotColdMode = hotColdMode_disabled; + goto redraw; + } + + //Leave loop + break; + } + //Disabled + if (pressedButton == 2) + { + //Set marker to disabled + hotColdMode = hotColdMode_disabled; + + //Leave loop + break; + } + //Back to main menu + if (pressedButton == 3) + return false; + } + } + + //Write to EEPROM + EEPROM.write(eeprom_hotColdMode, hotColdMode); + + //Disable auto FFC for isotherm mode + if (hotColdMode != hotColdMode_disabled) + lepton_ffcMode(false); + //Enable it when isotherm disabled + else + lepton_ffcMode(true); + + //Go back + return true; +} + +/* Switch the current preset menu item */ +void tempLimitsPresetSaveString(int pos) +{ + char* text = (char*) ""; + switch (pos) + { + case 0: + text = (char*) "Temporary"; + break; + case 1: + text = (char*) "Preset 1"; + break; + case 2: + text = (char*) "Preset 2"; + break; + case 3: + text = (char*) "Preset 3"; + break; + } + mainMenuSelection(text); +} + +/* Menu to save the temperature limits to a preset */ +bool tempLimitsPresetSaveMenu() +{ + //Save the current position inside the menu + byte menuPos = 1; + //Background + mainMenuBackground(); + //Title + mainMenuTitle((char*) "Select Preset"); + //Draw the selection menu + drawSelectionMenu(); + //Draw the current item + tempLimitsPresetSaveString(menuPos); + //Save the current position inside the menu + while (true) + { + //Touch screen pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //SELECT + if (pressedButton == 3) + { + uint8_t farray[4]; + switch (menuPos) + { + //Temporary + case 0: + EEPROM.write(eeprom_minMaxPreset, minMax_temporary); + break; + //Preset 1 + case 1: + EEPROM.write(eeprom_minValue1High, + (minValue & 0xFF00) >> 8); + EEPROM.write(eeprom_minValue1Low, minValue & 0x00FF); + EEPROM.write(eeprom_maxValue1High, + (maxValue & 0xFF00) >> 8); + EEPROM.write(eeprom_maxValue1Low, maxValue & 0x00FF); + floatToBytes(farray, (float) calComp); + for (int i = 0; i < 4; i++) + EEPROM.write(eeprom_minMax1Comp + i, (farray[i])); + EEPROM.write(eeprom_minMax1Set, eeprom_setValue); + EEPROM.write(eeprom_minMaxPreset, minMax_preset1); + break; + //Preset 2 + case 2: + EEPROM.write(eeprom_minValue2High, + (minValue & 0xFF00) >> 8); + EEPROM.write(eeprom_minValue2Low, minValue & 0x00FF); + EEPROM.write(eeprom_maxValue2High, + (maxValue & 0xFF00) >> 8); + EEPROM.write(eeprom_maxValue2Low, maxValue & 0x00FF); + floatToBytes(farray, (float) calComp); + for (int i = 0; i < 4; i++) + EEPROM.write(eeprom_minMax2Comp + i, (farray[i])); + EEPROM.write(eeprom_minMax2Set, eeprom_setValue); + EEPROM.write(eeprom_minMaxPreset, minMax_preset2); + break; + //Preset 3 + case 3: + EEPROM.write(eeprom_minValue3High, + (minValue & 0xFF00) >> 8); + EEPROM.write(eeprom_minValue3Low, minValue & 0x00FF); + EEPROM.write(eeprom_maxValue3High, + (maxValue & 0xFF00) >> 8); + EEPROM.write(eeprom_maxValue3Low, maxValue & 0x00FF); + floatToBytes(farray, (float) calComp); + for (int i = 0; i < 4; i++) + EEPROM.write(eeprom_minMax3Comp + i, (farray[i])); + EEPROM.write(eeprom_minMax3Set, eeprom_setValue); + EEPROM.write(eeprom_minMaxPreset, minMax_preset3); + break; + } + return true; + } + //BACKWARD + else if (pressedButton == 0) + { + if (menuPos > 0) + menuPos--; + else if (menuPos == 0) + menuPos = 3; + } + //FORWARD + else if (pressedButton == 1) + { + if (menuPos < 3) + menuPos++; + else if (menuPos == 3) + menuPos = 0; + } + //BACK + else if (pressedButton == 2) + return false; + //Change the menu name + tempLimitsPresetSaveString(menuPos); + } + } +} + +/* Touch Handler for the limit chooser menu */ +bool tempLimitsManualHandler() +{ + + //Set both modes to false for the first time + bool minChange = false; + bool maxChange = false; + //Buffer + int min, max; + char minC[10]; + char maxC[10]; + + //Touch handler + while (true) + { + //Set font & text color + display_setFont(smallFont); + display_setBackColor(VGA_TRANSPARENT); + changeTextColor(); + + //Update minimum & maximum + min = (int) round(calFunction(minValue)); + max = (int) round(calFunction(maxValue)); + if (tempFormat == tempFormat_celcius) + { + sprintf(minC, "Min:%dC", min); + sprintf(maxC, "Max:%dC", max); + } + else + { + sprintf(minC, "Min:%dF", min); + sprintf(maxC, "Max:%dF", max); + } + display_print(maxC, 180, 153); + display_print(minC, 85, 153); + display_setFont(bigFont); + + //If touch pressed + if (touch_touched() == true) + { + int pressedButton; + //Change values continously and fast when the user holds the plus or minus button + if (minChange || maxChange) + pressedButton = buttons_checkButtons(true, true); + //Normal check when not in minChange or maxChange mode + else + pressedButton = buttons_checkButtons(); + //RESET + if (pressedButton == 0) + { + //Refresh min and max + delay(10); + lepton_getRawValues(); + limitValues(); + } + //SELECT + else if (pressedButton == 1) + { + //Leave the minimum or maximum change mode + if (minChange || maxChange) + { + buttons_relabelButton(1, (char*) "OK", true); + buttons_relabelButton(2, (char*) "Min", true); + buttons_relabelButton(3, (char*) "Max", true); + if (minChange == true) + minChange = false; + if (maxChange == true) + maxChange = false; + } + //Go back + else + { + if (tempLimitsPresetSaveMenu()) + return true; + else + return false; + } + } + //DECREASE + else if (pressedButton == 2) + { + //In minimum change mode - decrease minimum temp + if ((minChange == true) && (maxChange == false)) + { + //Check if minimum is in range + if (min > -70) + { + min--; + minValue = tempToRaw(min); + } + } + //Enter minimum change mode + else if ((minChange == false) && (maxChange == false)) + { + buttons_relabelButton(1, (char*) "Back", true); + buttons_relabelButton(2, (char*) "-", true); + buttons_relabelButton(3, (char*) "+", true); + minChange = true; + } + //In maximum change mode - decrease maximum temp + else if ((minChange == false) && (maxChange == true)) + { + //Check if maximum is bigger than minimum + if (max > (min + 1)) + { + max--; + maxValue = tempToRaw(max); + } + } + } + //INCREASE + else if (pressedButton == 3) + { + //In maximum change mode - increase maximum temp + if ((minChange == false) && (maxChange == true)) + { + //Check if maximum is in range + if (max < 380) + { + max++; + maxValue = tempToRaw(max); + } + } + //Enter maximum change mode + else if ((minChange == false) && (maxChange == false)) + { + buttons_relabelButton(1, (char*) "Back", true); + buttons_relabelButton(2, (char*) "-", true); + buttons_relabelButton(3, (char*) "+", true); + maxChange = true; + + } + //In minimum change mode - increase minimum temp + else if ((minChange == true) && (maxChange == false)) + { + //Check if minimum is smaller than maximum + if (min < (max - 1)) + { + min++; + minValue = tempToRaw(min); + } + } + } + + //Prepare the preview image + delay(10); + createThermalImg(true); + + //Display the preview image + display_drawBitmap(80, 48, 160, 120, smallBuffer, 1); + } + } +} + +/* Select the limits in Manual Mode*/ +void tempLimitsManual() +{ + redraw: + //Background & title + mainMenuBackground(); + mainMenuTitle((char*) "Temp. Limits"); + + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(15, 188, 140, 40, (char*) "Reset"); + buttons_addButton(165, 188, 140, 40, (char*) "OK"); + buttons_addButton(15, 48, 55, 120, (char*) "Min"); + buttons_addButton(250, 48, 55, 120, (char*) "Max"); + buttons_drawButtons(); + + //Prepare the preview image + delay(10); + autoMode = true; + createThermalImg(true); + autoMode = false; + + //Display the preview image + display_drawBitmap(80, 48, 160, 120, smallBuffer, 1); + + //Draw the border for the preview image + display_setColor(VGA_BLACK); + display_drawRect(79, 47, 241, 169); + + //Go into the normal touch handler + if (!tempLimitsManualHandler()) + goto redraw; +} + +/* Switch the temperature limits preset string */ +void tempLimitsPresetsString(int pos) +{ + char* text = (char*) ""; + switch (pos) + { + case 0: + text = (char*) "New"; + break; + case 1: + text = (char*) "Preset 1"; + break; + case 2: + text = (char*) "Preset 2"; + break; + case 3: + text = (char*) "Preset 3"; + break; + } + mainMenuSelection(text); +} + +/* Menu to save the temperature limits to a preset */ +bool tempLimitsPresets() +{ + //Save the current position inside the menu + byte tempLimitsMenuPos = 0; + //Background + mainMenuBackground(); + //Title + mainMenuTitle((char*) "Choose Preset"); + //Draw the selection menu + drawSelectionMenu(); + //Draw the current item + tempLimitsPresetsString(tempLimitsMenuPos); + //Save the current position inside the menu + while (true) + { + //Touch screen pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //SELECT + if (pressedButton == 3) + { + switch (tempLimitsMenuPos) + { + //New + case 0: + tempLimitsManual(); + return true; + //Load Preset 1 + case 1: + if (EEPROM.read(eeprom_minMax1Set) == eeprom_setValue) + EEPROM.write(eeprom_minMaxPreset, minMax_preset1); + else + { + showFullMessage((char*) "Preset 1 not saved", true); + delay(1000); + return false; + } + break; + //Load Preset 2 + case 2: + if (EEPROM.read(eeprom_minMax2Set) == eeprom_setValue) + EEPROM.write(eeprom_minMaxPreset, minMax_preset2); + else + { + showFullMessage((char*) "Preset 2 not saved", true); + delay(1000); + return false; + } + break; + //Load Preset 3 + case 3: + if (EEPROM.read(eeprom_minMax2Set) == eeprom_setValue) + EEPROM.write(eeprom_minMaxPreset, minMax_preset3); + else + { + showFullMessage((char*) "Preset 3 not saved", true); + delay(1000); + return false; + } + break; + } + //Read temperature limits from EEPROM + readTempLimits(); + return true; + } + //BACKWARD + else if (pressedButton == 0) + { + if (tempLimitsMenuPos > 0) + tempLimitsMenuPos--; + else if (tempLimitsMenuPos == 0) + tempLimitsMenuPos = 3; + } + //FORWARD + else if (pressedButton == 1) + { + if (tempLimitsMenuPos < 3) + tempLimitsMenuPos++; + else if (tempLimitsMenuPos == 3) + tempLimitsMenuPos = 0; + } + //BACK + else if (pressedButton == 2) + return false; + //Change the menu name + tempLimitsPresetsString(tempLimitsMenuPos); + } + } +} + +void changeGain() +{ + /* Generate menu */ + drawTitle((char*) "Gain Mode"); + buttons_deleteAllButtons(); + buttons_setTextFont(smallFont); + buttons_addButton(20, 60, 130, 70, (char*) "-10C - +140C"); + buttons_addButton(170, 60, 130, 70, (char*) "-10C - +450C"); + buttons_addButton(20, 150, 280, 70, (char*) "Save"); + buttons_drawButtons(); + if (gainMode == lepton_3_5_gain_high) + buttons_setActive(0); + else + buttons_setActive(1); + //Touch handler + while (true) + { + //touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(); + //High gain + if (pressedButton == 0) + { + if (gainMode == lepton_3_5_gain_low) + { + gainMode = lepton_3_5_gain_high; + buttons_setActive(0); + buttons_setInactive(1); + } + } + //Low gain + else if (pressedButton == 1) + { + if (gainMode == lepton_3_5_gain_high) + { + gainMode = lepton_3_5_gain_low; + buttons_setActive(1); + buttons_setInactive(0); + } + } + //Save + else if (pressedButton == 2) + { + //Change gain mode + if(gainMode == lepton_3_5_gain_low) + { + lepton_3_5_set_low_gain(); + } + else + { + lepton_3_5_set_high_gain(); + } + + //Write new settings to EEPROM + EEPROM.write(eeprom_lepton_3_5_gain, gainMode); + + //Trigger shutter + lepton_ffc(true, true); + + break; + } + } + } +} + +/* Temperature Limit Mode Selection */ +bool tempLimits() +{ + //Do not show in visual mode + if (displayMode == displayMode_visual) + { + showFullMessage((char*) "No use in visual mode", true); + delay(1000); + return false; + } + + //Still in warmup, do not let the user do this + if (calStatus == cal_warmup) + { + showFullMessage((char*) "Please wait for sensor warmup", true); + delay(1000); + return false; + } + + //Title & Background + redraw: + mainMenuBackground(); + mainMenuTitle((char*) "Temp. Limits"); + + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(15, 47, 140, 120, (char*) "Auto"); + buttons_addButton(165, 47, 140, 120, (char*) "Manual"); + buttons_addButton(15, 188, 140, 40, (char*) "Back"); + if (leptonVersion == leptonVersion_3_5_shutter) + { + buttons_addButton(165, 188, 140, 40, (char*) "Gain"); + } + buttons_drawButtons(); + + //Touch handler + while (true) + { + //If touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //AUTO + if (pressedButton == 0) + { + //Show message + showFullMessage((char*) "Please wait..", true); + + //Enable auto mode again and disable limits locked + autoMode = true; + limitsLocked = false; + + //Set temperature presets to temporary, so it does not load + EEPROM.write(eeprom_minMaxPreset, minMax_temporary); + + //Enable auto FFC + lepton_ffcMode(true); + + //Go back + return true; + } + //MANUAL + if (pressedButton == 1) + { + //Switch to manual FFC mode + if (leptonShutter == leptonShutter_auto) + lepton_ffcMode(false); + + //Disable auto mode and limits locked + autoMode = false; + limitsLocked = false; + + //Let the user choose the new limits + return tempLimitsPresets(); + } + //BACK + if (pressedButton == 2) + return false; + //GAIN + if ((leptonVersion == leptonVersion_3_5_shutter) + && (pressedButton == 3)) + { + changeGain(); + goto redraw; + } + } + } +} + +/* Switch the current display option item */ +void liveDispMenuString(int pos) +{ + char* text = (char*) ""; + switch (pos) + { + //Battery + case 0: + if (batteryEnabled) + text = (char*) "Battery On"; + else + text = (char*) "Battery Off"; + break; + //Time + case 1: + if (timeEnabled) + text = (char*) "Time On"; + else + text = (char*) "Time Off"; + break; + //Date + case 2: + if (dateEnabled) + text = (char*) "Date On"; + else + text = (char*) "Date Off"; + break; + //Spot + case 3: + if (spotEnabled) + text = (char*) "Spot On"; + else + text = (char*) "Spot Off"; + break; + //Colorbar + case 4: + if (colorbarEnabled) + text = (char*) "Bar On"; + else + text = (char*) "Bar Off"; + break; + //Storage + case 5: + if (storageEnabled) + text = (char*) "Storage On"; + else + text = (char*) "Storage Off"; + break; + //Filter + case 6: + if (filterType == filterType_box) + text = (char*) "Box-Filter"; + else if (filterType == filterType_gaussian) + text = (char*) "Gaus-Filter"; + else + text = (char*) "No Filter"; + break; + //Text Color + case 7: + if (textColor == textColor_black) + text = (char*) "Black Text"; + else if (textColor == textColor_red) + text = (char*) "Red Text"; + else if (textColor == textColor_green) + text = (char*) "Green Text"; + else if (textColor == textColor_blue) + text = (char*) "Blue Text"; + else + text = (char*) "White Text"; + break; + //Hottest or coldest + case 8: + if (minMaxPoints == minMaxPoints_disabled) + text = (char*) "No Cold/Hot"; + else if (minMaxPoints == minMaxPoints_min) + text = (char*) "Coldest"; + else if (minMaxPoints == minMaxPoints_max) + text = (char*) "Hottest"; + else + text = (char*) "Both C/H"; + break; + + } + mainMenuSelection(text); +} + +/* Change the live display options */ +bool liveDispMenu() +{ + //Save the current position inside the menu + static byte displayOptionsPos = 0; + //Background + mainMenuBackground(); + //Title + mainMenuTitle((char*) "Live Disp. Options"); + //Draw the selection menu + drawSelectionMenu(); + //Rename OK button + buttons_relabelButton(3, (char*) "Switch", true); + //Draw the current item + liveDispMenuString(displayOptionsPos); + while (true) + { + //Touch screen pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //SELECT + if (pressedButton == 3) + { + changeDisplayOptions(&displayOptionsPos); + } + //BACK + if (pressedButton == 2) + { + return false; + } + //BACKWARD + else if (pressedButton == 0) + { + if (displayOptionsPos > 0) + displayOptionsPos--; + else if (displayOptionsPos == 0) + displayOptionsPos = 8; + } + //FORWARD + else if (pressedButton == 1) + { + if (displayOptionsPos < 8) + displayOptionsPos++; + else if (displayOptionsPos == 8) + displayOptionsPos = 0; + } + //Change the menu name + liveDispMenuString(displayOptionsPos); + } + } +} + +/* Switch the current color scheme item */ +void colorMenuString(int pos) +{ + char* text = (char*) ""; + switch (pos) + { + case colorScheme_arctic: + text = (char*) "Arctic"; + break; + case colorScheme_blackHot: + text = (char*) "Black-Hot"; + break; + case colorScheme_blueRed: + text = (char*) "Blue-Red"; + break; + case colorScheme_coldest: + text = (char*) "Coldest"; + break; + case colorScheme_contrast: + text = (char*) "Contrast"; + break; + case colorScheme_doubleRainbow: + text = (char*) "Double-Rain"; + break; + case colorScheme_grayRed: + text = (char*) "Gray-Red"; + break; + case colorScheme_glowBow: + text = (char*) "Glowbow"; + break; + case colorScheme_grayscale: + text = (char*) "Grayscale"; + break; + case colorScheme_hottest: + text = (char*) "Hottest"; + break; + case colorScheme_ironblack: + text = (char*) "Ironblack"; + break; + case colorScheme_lava: + text = (char*) "Lava"; + break; + case colorScheme_medical: + text = (char*) "Medical"; + break; + case colorScheme_rainbow: + text = (char*) "Rainbow"; + break; + case colorScheme_wheel1: + text = (char*) "Wheel 1"; + break; + case colorScheme_wheel2: + text = (char*) "Wheel 2"; + break; + case colorScheme_wheel3: + text = (char*) "Wheel 3"; + break; + case colorScheme_whiteHot: + text = (char*) "White-Hot"; + break; + case colorScheme_yellow: + text = (char*) "Yellow"; + break; + } + mainMenuSelection(text); +} + +/* Choose the applied color scale */ +bool colorMenu() +{ + //Do not show in visual mode + if (displayMode == displayMode_visual) + { + showFullMessage((char*) "No use in visual mode", true); + delay(1000); + return false; + } + + //Save the current position inside the menu + byte changeColorPos = colorScheme; + //Background + mainMenuBackground(); + //Title + mainMenuTitle((char*) "Change Color"); + //Draw the selection menu + drawSelectionMenu(); + //Draw the current item + colorMenuString(changeColorPos); + while (true) + { + //Touch screen pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //SELECT + if (pressedButton == 3) + { + changeColorScheme(&changeColorPos); + return true; + } + //BACK + if (pressedButton == 2) + return false; + //BACKWARD + else if (pressedButton == 0) + { + if (changeColorPos > 0) + changeColorPos--; + else if (changeColorPos == 0) + changeColorPos = colorSchemeTotal - 1; + } + //FORWARD + else if (pressedButton == 1) + { + if (changeColorPos < (colorSchemeTotal - 1)) + changeColorPos++; + else if (changeColorPos == (colorSchemeTotal - 1)) + changeColorPos = 0; + } + //Change the menu name + colorMenuString(changeColorPos); + } + } +} + +/* Choose the current display mode */ +bool modeMenu() +{ + //Background + mainMenuBackground(); + //Title + mainMenuTitle((char*) "Change Mode"); + //Draw the selection menu + buttons_deleteAllButtons(); + buttons_setTextFont(smallFont); + buttons_addButton(15, 45, 90, 122, (char*) "Thermal"); + buttons_addButton(115, 45, 90, 122, (char*) "Visual"); + buttons_addButton(215, 45, 90, 122, (char*) "Combined"); + buttons_addButton(15, 188, 120, 40, (char*) "Back"); + buttons_drawButtons(); + while (true) + { + //Touch screen pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //Thermal + if (pressedButton == 0) + { + //Show message + showFullMessage((char*) "Please wait..", true); + + //Set camera resolution to save + camera_setSaveRes(); + + //Set display mode to thermal + displayMode = displayMode_thermal; + EEPROM.write(eeprom_displayMode, displayMode_thermal); + return true; + } + + //Visual or combined + if ((pressedButton == 1) || (pressedButton == 2)) + { + //If the visual camera is not working + if (!checkDiagnostic(diag_camera)) + { + showFullMessage((char*) "Cam not connected", true); + delay(1000); + return false; + } + + //Show message + showFullMessage((char*) "Please wait..", true); + + //Set camera resolution to streaming + camera_setDisplayRes(); + + //Set display mode to visual + if (pressedButton == 1) + { + displayMode = displayMode_visual; + EEPROM.write(eeprom_displayMode, displayMode_visual); + } + //Set display mode to combined + else + { + displayMode = displayMode_combined; + EEPROM.write(eeprom_displayMode, displayMode_combined); + } + return true; + } + //Back + if (pressedButton == 3) + return false; + } + } +} + +/* HQ Resolution Menu */ +void hqResolutionMenu() +{ + drawTitle((char*) "HQ Resolution"); + buttons_deleteAllButtons(); + buttons_setTextFont(smallFont); + buttons_addButton(20, 60, 130, 70, (char*) "Off (160x120)"); + buttons_addButton(170, 60, 130, 70, (char*) "On (320x240)"); + buttons_addButton(20, 150, 280, 70, (char*) "Save"); + buttons_drawButtons(); + if (hqRes) + buttons_setActive(1); + else + buttons_setActive(0); + //Touch handler + while (true) + { + //touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(); + //Off + if (pressedButton == 0) + { + if (hqRes) + { + hqRes = false; + buttons_setActive(0); + buttons_setInactive(1); + } + } + //On + else if (pressedButton == 1) + { + if (!hqRes) + { + hqRes = true; + buttons_setActive(1); + buttons_setInactive(0); + } + } + //Save + else if (pressedButton == 2) + { + //Change camera resolution + if (displayMode == displayMode_thermal) + camera_setSaveRes(); + else + camera_setDisplayRes(); + + //Write new settings to EEPROM + EEPROM.write(eeprom_hqRes, hqRes); + break; + } + } + } +} + +/* Draws the content of the main menu*/ +void drawMainMenu(byte pos) +{ + //Border + drawMainMenuBorder(); + //Background + display_setColor(200, 200, 200); + display_fillRoundRect(6, 6, 314, 234); + //Buttons + buttons_deleteAllButtons(); + //First page + if (pos == 0) + { + buttons_addButton(23, 28, 80, 80, icon1Bitmap, icon1Colors); + buttons_addButton(120, 28, 80, 80, icon2Bitmap, icon2Colors); + buttons_addButton(217, 28, 80, 80, icon3Bitmap, icon3Colors); + } + //Second page + if (pos == 1) + { + buttons_addButton(23, 28, 80, 80, icon4Bitmap, icon4Colors); + if (teensyVersion == teensyVersion_old) + buttons_addButton(120, 28, 80, 80, icon5Bitmap_1, icon5Colors); + else + buttons_addButton(120, 28, 80, 80, icon5Bitmap_2, icon5Colors); + buttons_addButton(217, 28, 80, 80, icon6Bitmap, icon6Colors); + } + //Third page + if (pos == 2) + { + buttons_addButton(23, 28, 80, 80, icon7Bitmap, icon7Colors); + if (teensyVersion == teensyVersion_old) + buttons_addButton(120, 28, 80, 80, icon8Bitmap_1, icon8Colors); + else + buttons_addButton(120, 28, 80, 80, icon8Bitmap_2, icon8Colors); + buttons_addButton(217, 28, 80, 80, icon9Bitmap, icon9Colors); + } + //Fourth page + if (pos == 3) + { + buttons_addButton(23, 28, 80, 80, icon10Bitmap, icon10Colors); + if (displayMode == displayMode_thermal) + buttons_addButton(120, 28, 80, 80, icon11_1Bitmap, icon11_1Colors); + else + buttons_addButton(120, 28, 80, 80, icon11_2Bitmap, icon11_2Colors); + buttons_addButton(217, 28, 80, 80, icon12Bitmap, icon12Colors); + } + buttons_addButton(23, 132, 80, 80, iconBWBitmap, iconBWColors); + buttons_addButton(120, 132, 80, 80, iconReturnBitmap, iconReturnColors); + buttons_addButton(217, 132, 80, 80, iconFWBitmap, iconFWColors); + buttons_drawButtons(); +} + +/* Select the action when the select button is pressed */ +bool mainMenuSelect(byte pos, byte page) +{ + //First page + if (page == 0) + { + //Change color + if (pos == 0) + { + return colorMenu(); + } + //Change mode + if (pos == 1) + { + return modeMenu(); + } + //Temperature limits + if (pos == 2) + { + return tempLimits(); + } + } + //Second page + if (page == 1) + { + //Load menu + if (pos == 0) + { + loadFiles(); + } + //File Transfer or Shutter + if (pos == 1) + { + if (teensyVersion == teensyVersion_old) + massStorage(); + else + lepton_ffc(true); + } + //Settings + if (pos == 2) + { + settingsMenu(); + settingsMenuHandler(); + } + } + //Third page + if (page == 2) + { + //Display options + if (pos == 0) + { + return liveDispMenu(); + } + //Laser or HQ resolution + if (pos == 1) + { + if (teensyVersion == teensyVersion_old) + toggleLaser(true); + else + hqResolutionMenu(); + } + //Toggle display + if (pos == 2) + { + toggleDisplay(); + } + } + //Fourth page + if (page == 3) + { + //Calibration + if (pos == 0) + { + return calibration(); + } + //Isotherm or adjust visual + if (pos == 1) + { + if (displayMode == displayMode_thermal) + return hotColdMenu(); + else + return adjustCombinedMenu(); + } + //Points + if (pos == 2) + { + return tempPointsMenu(); + } + } + return false; +} + +/* Touch Handler for the Live Menu */ +void mainMenuHandler(byte* pos) +{ + //Main loop + while (true) + { + //Check for screen sleep + if (screenOffCheck()) + drawMainMenu(*pos); + + //Touch screen pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //FIRST BUTTON + if (pressedButton == 0) + { + //Leave menu + if (mainMenuSelect(0, *pos)) + break; + } + //SECOND BUTTON + if (pressedButton == 1) + { + //Leave menu + if (mainMenuSelect(1, *pos)) + break; + } + //THIRD BUTTON + if (pressedButton == 2) + { + //Leave menu + if (mainMenuSelect(2, *pos)) + break; + } + //BACKWARD + else if (pressedButton == 3) + { + if (*pos > 0) + *pos = *pos - 1; + else if (*pos == 0) + *pos = 3; + } + //EXIT + if (pressedButton == 4) + { + showFullMessage((char*) "Please wait..", true); + return; + } + //FORWARD + else if (pressedButton == 5) + { + if (*pos < 3) + *pos = *pos + 1; + else if (*pos == 3) + *pos = 0; + } + drawMainMenu(*pos); + } + } +} + +/* Start live menu */ +void mainMenu() +{ + //Set show menu to opened + showMenu = showMenu_opened; + + //Position in the main menu + static byte mainMenuPos = 0; + + //Draw content + drawMainMenu(mainMenuPos); + + //Touch handler - return true if exit to Main menu, otherwise false + mainMenuHandler(&mainMenuPos); + + //Restore old fonts + display_setFont(smallFont); + buttons_setTextFont(smallFont); + + //Delete the old buttons + buttons_deleteAllButtons(); + + //Wait a short time + delay(500); + + //Clear serial buffer + Serial.clear(); + + //Disable menu marker + showMenu = showMenu_disabled; +} diff --git a/Firmware_V2/src/gui/mainmenu.h b/Firmware_V2/src/gui/mainmenu.h new file mode 100644 index 0000000..439cb13 --- /dev/null +++ b/Firmware_V2/src/gui/mainmenu.h @@ -0,0 +1,53 @@ +/* +* +* MAIN MENU - Display the main menu with icons +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef MAINMENU_H +#define MAINMENU_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +bool calibrationRepeat(); +void calibrationScreen(bool firstStart = false); +bool colorMenu(); +void colorMenuString(int pos); +void drawMainMenu(byte pos); +void drawSelectionMenu(); +void hotColdChooserHandler(); +void hotColdChooser(); +bool hotColdColorMenu(); +void hotColdColorMenuString(int pos); +bool hotColdMenu(); +void hqResolutionMenu(); +bool liveDispMenu(); +void liveDispMenuString(int pos); +void mainMenuBackground(); +void mainMenuHandler(byte* pos); +bool mainMenuSelect(byte pos, byte page); +void mainMenuSelection(char* selection); +void mainMenuTitle(char* title); +void mainMenu(); +bool massStoragePrompt(); +bool modeMenu(); +bool tempLimits(); +bool tempLimitsManualHandler(); +void tempLimitsManual(); +bool tempLimitsPresetSaveMenu(); +void tempLimitsPresetSaveString(int pos); +bool tempLimitsPresets(); +void tempLimitsPresetsString(int pos); +bool tempPointsMenu(); + +#endif /* MAINMENU_H */ + diff --git a/Firmware_V2/src/gui/settingsmenu.cpp b/Firmware_V2/src/gui/settingsmenu.cpp new file mode 100644 index 0000000..3ee5b0b --- /dev/null +++ b/Firmware_V2/src/gui/settingsmenu.cpp @@ -0,0 +1,1330 @@ +/* +* +* SETTINGS MENU - Adjust different on-device settings +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Draw the GUI elements for the adjust combined menu */ +void adjustCombinedGUI() { + //Color and font + changeTextColor(); + display_setFont(bigFont); + display_setBackColor(VGA_TRANSPARENT); + + //Down arrow + display_drawLine(149, 225, 159, 235); + display_drawLine(150, 225, 160, 235); + display_drawLine(151, 225, 161, 235); + display_drawLine(159, 235, 169, 225); + display_drawLine(160, 235, 170, 225); + display_drawLine(161, 235, 171, 225); + //Left arrow + display_drawLine(15, 109, 5, 119); + display_drawLine(15, 110, 5, 120); + display_drawLine(15, 111, 5, 121); + display_drawLine(5, 119, 15, 129); + display_drawLine(5, 120, 15, 130); + display_drawLine(5, 121, 15, 131); + //Up arrow + display_drawLine(149, 15, 159, 5); + display_drawLine(150, 15, 160, 5); + display_drawLine(151, 15, 161, 5); + display_drawLine(159, 5, 169, 15); + display_drawLine(160, 5, 170, 15); + display_drawLine(161, 5, 171, 15); + //Right arrow + display_drawLine(305, 109, 315, 119); + display_drawLine(305, 110, 315, 120); + display_drawLine(305, 111, 315, 121); + display_drawLine(315, 119, 305, 129); + display_drawLine(315, 120, 305, 130); + display_drawLine(315, 121, 305, 131); + //Decrease + display_drawLine(5, 224, 25, 224); + display_drawLine(5, 225, 25, 225); + display_drawLine(5, 226, 25, 226); + //Increase + display_drawLine(5, 14, 25, 14); + display_drawLine(5, 15, 25, 15); + display_drawLine(5, 16, 25, 16); + display_drawLine(14, 5, 14, 25); + display_drawLine(15, 5, 15, 25); + display_drawLine(16, 5, 16, 25); + + //Alpha level + display_print('A', 300, 5); + + //Color for confirm + display_setColor(0, 255, 0); + //Confirm button + display_drawLine(294, 225, 304, 235); + display_drawLine(295, 225, 305, 235); + display_drawLine(296, 225, 306, 235); + display_drawLine(304, 235, 314, 215); + display_drawLine(305, 235, 315, 215); + display_drawLine(306, 235, 316, 215); + + //Restore old font + display_setFont(smallFont); +} + +/* Refresh the screen content in adjust combined menu */ +void adjustCombinedRefresh() { + //Create the combined image + createVisCombImg(); + + //Display it on the screen + displayBuffer(); + + //Display the GUI + adjustCombinedGUI(); +} + +/* Shows on the screen that is refreshes */ +void adjustCombinedLoading() { + //Set Text Color + changeTextColor(); + //set Background transparent + display_setBackColor(VGA_TRANSPARENT); + //Give the user a hint that it tries to save + display_setFont(bigFont); + //Show text + display_print((char*) "Please wait..", CENTER, 110); + //Return to small font + display_setFont(smallFont); +} + +/* Switch the current preset menu item */ +void adjustCombinedPresetSaveString(int pos) { + char* text = (char*) ""; + switch (pos) { + case 0: + text = (char*) "Temporary"; + break; + case 1: + text = (char*) "Preset 1"; + break; + case 2: + text = (char*) "Preset 2"; + break; + case 3: + text = (char*) "Preset 3"; + break; + } + mainMenuSelection(text); +} + +/* Menu to save the adjust combined settings to a preset */ +bool adjustCombinedPresetSaveMenu() { + //Save the current position inside the menu + byte menuPos = 1; + //Border + drawMainMenuBorder(); + //Background + mainMenuBackground(); + //Title + mainMenuTitle((char*) "Select Preset"); + //Draw the selection menu + drawSelectionMenu(); + //Draw the current item + adjustCombinedPresetSaveString(menuPos); + //Save the current position inside the menu + while (true) { + //Touch screen pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(true); + //SELECT + if (pressedButton == 3) { + switch (menuPos) { + //Temporary + case 0: + EEPROM.write(eeprom_adjCombPreset, adjComb_temporary); + break; + //Preset 1 + case 1: + EEPROM.write(eeprom_adjComb1Left, adjCombLeft); + EEPROM.write(eeprom_adjComb1Right, adjCombRight); + EEPROM.write(eeprom_adjComb1Up, adjCombUp); + EEPROM.write(eeprom_adjComb1Down, adjCombDown); + EEPROM.write(eeprom_adjComb1Alpha, round(adjCombAlpha * 100.0)); + EEPROM.write(eeprom_adjComb1Factor, round(adjCombFactor * 100.0)); + EEPROM.write(eeprom_adjComb1Set, eeprom_setValue); + EEPROM.write(eeprom_adjCombPreset, adjComb_preset1); + break; + //Preset 2 + case 2: + EEPROM.write(eeprom_adjComb2Left, adjCombLeft); + EEPROM.write(eeprom_adjComb2Right, adjCombRight); + EEPROM.write(eeprom_adjComb2Up, adjCombUp); + EEPROM.write(eeprom_adjComb2Down, adjCombDown); + EEPROM.write(eeprom_adjComb2Alpha, round(adjCombAlpha * 100.0)); + EEPROM.write(eeprom_adjComb2Factor, round(adjCombFactor * 100.0)); + EEPROM.write(eeprom_adjComb2Set, eeprom_setValue); + EEPROM.write(eeprom_adjCombPreset, adjComb_preset2); + break; + //Preset 3 + case 3: + EEPROM.write(eeprom_adjComb3Left, adjCombLeft); + EEPROM.write(eeprom_adjComb3Right, adjCombRight); + EEPROM.write(eeprom_adjComb3Up, adjCombUp); + EEPROM.write(eeprom_adjComb3Down, adjCombDown); + EEPROM.write(eeprom_adjComb3Alpha, round(adjCombAlpha * 100.0)); + EEPROM.write(eeprom_adjComb3Factor, round(adjCombFactor * 100.0)); + EEPROM.write(eeprom_adjComb3Set, eeprom_setValue); + EEPROM.write(eeprom_adjCombPreset, adjComb_preset3); + break; + } + return true; + } + //BACKWARD + else if (pressedButton == 0) { + if (menuPos > 0) + menuPos--; + else if (menuPos == 0) + menuPos = 3; + } + //FORWARD + else if (pressedButton == 1) { + if (menuPos < 3) + menuPos++; + else if (menuPos == 3) + menuPos = 0; + } + //BACK + else if (pressedButton == 2) + return false; + //Change the menu name + adjustCombinedPresetSaveString(menuPos); + } + } +} + +/* Touch handler for the new adjust combined menu */ +void adjustCombinedNewMenuHandler(bool firstStart) { + //Touch handler + while (true) { + //Touch pressed + if (touch_touched() == true) { + //Get coordinates + TS_Point p = touch_getPoint(); + uint16_t x = p.x; + uint16_t y = p.y; + //Increase + if ((x >= 0) && (x <= 50) && (y >= 0) && (y <= 50)) { + if (adjCombFactor < 1.0) { + adjustCombinedLoading(); + adjCombFactor += 0.02; + adjustCombinedRefresh(); + } + } + //Decrease + else if ((x >= 0) && (x <= 50) && (y >= 190) && (y <= 240)) { + if (adjCombFactor > 0.70) { + adjustCombinedLoading(); + adjCombFactor -= 0.02; + adjustCombinedRefresh(); + } + } + //Left + else if ((x >= 0) && (x <= 50) && (y >= 95) && (y <= 145)) { + if (adjCombLeft < 10) { + adjustCombinedLoading(); + if (adjCombRight > 0) + adjCombRight -= 1; + else + adjCombLeft += 1; + adjustCombinedRefresh(); + } + } + //Right + else if ((x >= 270) && (x <= 320) && (y >= 95) && (y <= 145)) { + if (adjCombRight < 10) { + adjustCombinedLoading(); + if (adjCombLeft > 0) + adjCombLeft -= 1; + else + adjCombRight += 1; + adjustCombinedRefresh(); + } + } + //Up + else if ((x >= 135) && (x <= 185) && (y >= 0) && (y <= 50)) { + if (adjCombUp < 10) { + adjustCombinedLoading(); + if (adjCombDown > 0) + adjCombDown -= 1; + else + adjCombUp += 1; + adjustCombinedRefresh(); + } + } + //Down + else if ((x >= 135) && (x <= 185) && (y >= 190) && (y <= 240)) { + if (adjCombDown < 10) { + adjustCombinedLoading(); + if (adjCombUp > 0) + adjCombUp -= 1; + else + adjCombDown += 1; + adjustCombinedRefresh(); + } + } + //OK + else if ((x >= 270) && (x <= 320) && (y >= 190) && (y <= 240)) { + //Preset chooser + if (!firstStart) { + if (adjustCombinedPresetSaveMenu()) + return; + else + adjustCombinedRefresh(); + } + //First start, save as preset 1 + else { + EEPROM.write(eeprom_adjComb1Left, adjCombLeft); + EEPROM.write(eeprom_adjComb1Right, adjCombRight); + EEPROM.write(eeprom_adjComb1Up, adjCombUp); + EEPROM.write(eeprom_adjComb1Down, adjCombDown); + EEPROM.write(eeprom_adjComb1Alpha, round(adjCombAlpha * 100.0)); + EEPROM.write(eeprom_adjComb1Factor, round(adjCombFactor * 100.0)); + EEPROM.write(eeprom_adjComb1Set, eeprom_setValue); + EEPROM.write(eeprom_adjCombPreset, adjComb_preset1); + return; + } + } + //Refresh + else if ((x >= 50) && (x <= 270) && (y >= 50) && (y <= 210)) { + adjustCombinedLoading(); + adjustCombinedRefresh(); + } + //Change alpha + else if ((x >= 270) && (x <= 320) && (y >= 0) && (y <= 50)) { + char buffer[20]; + if (adjCombAlpha < 0.7) + adjCombAlpha += 0.1; + else + adjCombAlpha = 0.3; + sprintf(buffer, "Alpha set to %.1f", adjCombAlpha); + display_setFont(bigFont); + changeTextColor(); + display_print(buffer, CENTER, 110); + display_setFont(smallFont); + adjustCombinedRefresh(); + } + } + } +} + +/* Adjust combined new menu */ +void adjustCombinedNewMenu(bool firstStart) { + //Show loading message + showFullMessage((char*)"Please wait.."); + //Prepare the preview image + byte displayMode_old = displayMode; + displayMode = displayMode_combined; + //Load the defaults + adjCombDown = 0; + adjCombUp = 0; + adjCombLeft = 0; + adjCombRight = 0; + adjCombAlpha = 0.5; + adjCombFactor = 1.0; + //Show the preview image + adjustCombinedRefresh(); + //Run the handler and + adjustCombinedNewMenuHandler(firstStart); + //Show message + showFullMessage((char*) "Please wait.."); + //Restore the old mode + displayMode = displayMode_old; +} + +/* Switch the current temperature menu item */ +void adjustCombinedString(int pos) { + char* text = (char*) ""; + switch (pos) { + case 0: + text = (char*) "New"; + break; + case 1: + text = (char*) "Preset 1"; + break; + case 2: + text = (char*) "Preset 2"; + break; + case 3: + text = (char*) "Preset 3"; + break; + } + mainMenuSelection(text); +} + +/* Menu to save the adjust combined settings to a preset */ +bool adjustCombinedMenu() { + //Save the current position inside the menu + byte adjCombMenuPos = 0; + //Background + mainMenuBackground(); + //Title + mainMenuTitle((char*) "Adjust Combined"); + //Draw the selection menu + drawSelectionMenu(); + //Draw the current item + adjustCombinedString(adjCombMenuPos); + //Save the current position inside the menu + while (true) { + //Touch screen pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(true); + //SELECT + if (pressedButton == 3) { + switch (adjCombMenuPos) { + //New + case 0: + adjustCombinedNewMenu(); + return true; + //Load Preset 1 + case 1: + EEPROM.write(eeprom_adjCombPreset, adjComb_preset1); + break; + //Load Preset 2 + case 2: + EEPROM.write(eeprom_adjCombPreset, adjComb_preset2); + break; + //Load Preset 3 + case 3: + EEPROM.write(eeprom_adjCombPreset, adjComb_preset3); + break; + } + //Read config from EEPROM + readAdjustCombined(); + return true; + } + //BACKWARD + else if (pressedButton == 0) { + if (adjCombMenuPos > 0) + adjCombMenuPos--; + else if (adjCombMenuPos == 0) + adjCombMenuPos = 3; + } + //FORWARD + else if (pressedButton == 1) { + if (adjCombMenuPos < 3) + adjCombMenuPos++; + else if (adjCombMenuPos == 3) + adjCombMenuPos = 0; + } + //BACK + else if (pressedButton == 2) + return false; + + //Change the menu name + adjustCombinedString(adjCombMenuPos); + } + } +} + + +/* Second Menu */ +void secondMenu(bool firstStart) { + drawTitle((char*) "Second", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 70, 70, (char*) "-"); + buttons_addButton(230, 60, 70, 70, (char*) "+"); + buttons_addButton(20, 150, 280, 70, (char*) "Back"); + buttons_drawButtons(); + drawCenterElement(second()); + //Touch handler + while (true) { + //touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(true); + //Minus + if (pressedButton == 0) { + if (second() >= 0) { + if (second() > 0) + setTime(hour(), minute(), second() - 1, day(), month(), + year()); + else if (second() == 0) + setTime(hour(), minute(), 59, day(), month(), year()); + drawCenterElement(second()); + } + } + //Plus + else if (pressedButton == 1) { + if (second() <= 59) { + if (second() < 59) + setTime(hour(), minute(), second() + 1, day(), month(), + year()); + else if (second() == 59) + setTime(hour(), minute(), 0, day(), month(), year()); + drawCenterElement(second()); + } + } + //Back + else if (pressedButton == 2) { + Teensy3Clock.set(now()); + timeMenu(firstStart); + break; + } + } + } +} + +/* Minute Menu */ +void minuteMenu(bool firstStart) { + drawTitle((char*) "Minute", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 70, 70, (char*) "-"); + buttons_addButton(230, 60, 70, 70, (char*) "+"); + buttons_addButton(20, 150, 280, 70, (char*) "Back"); + buttons_drawButtons(); + drawCenterElement(minute()); + //Touch handler + while (true) { + //touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(true); + //Minus + if (pressedButton == 0) { + if (minute() >= 0) { + if (minute() > 0) + setTime(hour(), minute() - 1, second(), day(), month(), + year()); + else if (minute() == 0) + setTime(hour(), 59, second(), day(), month(), year()); + drawCenterElement(minute()); + } + } + //Plus + else if (pressedButton == 1) { + if (minute() <= 59) { + if (minute() < 59) + setTime(hour(), minute() + 1, second(), day(), month(), + year()); + else if (minute() == 59) + setTime(hour(), 0, second(), day(), month(), year()); + drawCenterElement(minute()); + } + } + //Back + else if (pressedButton == 2) { + Teensy3Clock.set(now()); + timeMenu(firstStart); + break; + } + } + } +} + +/* Hour menu */ +void hourMenu(bool firstStart) { + drawTitle((char*) "Hour", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 70, 70, (char*) "-"); + buttons_addButton(230, 60, 70, 70, (char*) "+"); + buttons_addButton(20, 150, 280, 70, (char*) "Back"); + buttons_drawButtons(); + drawCenterElement(hour()); + //Touch handler + while (true) { + //touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(true); + //Minus + if (pressedButton == 0) { + if (hour() >= 0) { + if (hour() > 0) + setTime(hour() - 1, minute(), second(), day(), month(), + year()); + else if (hour() == 0) + setTime(23, minute(), second(), day(), month(), year()); + drawCenterElement(hour()); + } + } + //Plus + else if (pressedButton == 1) { + if (hour() <= 23) { + if (hour() < 23) + setTime(hour() + 1, minute(), second(), day(), month(), + year()); + else if (hour() == 23) + setTime(0, minute(), second(), day(), month(), year()); + drawCenterElement(hour()); + } + } + //Back + else if (pressedButton == 2) { + Teensy3Clock.set(now()); + timeMenu(firstStart); + break; + } + } + } +} + +/* Day Menu */ +void dayMenu(bool firstStart) { + drawTitle((char*) "Day", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 70, 70, (char*) "-"); + buttons_addButton(230, 60, 70, 70, (char*) "+"); + buttons_addButton(20, 150, 280, 70, (char*) "Back"); + buttons_drawButtons(); + drawCenterElement(day()); + //Touch handler + while (true) { + //touch press + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(true); + //Minus + if (pressedButton == 0) { + if (day() >= 1) { + if (day() > 1) + setTime(hour(), minute(), second(), day() - 1, month(), + year()); + else if (day() == 1) + setTime(hour(), minute(), second(), 31, month(), + year()); + drawCenterElement(day()); + } + } + //Plus + else if (pressedButton == 1) { + if (day() <= 31) { + if (day() < 31) + setTime(hour(), minute(), second(), day() + 1, month(), + year()); + else if (day() == 31) + setTime(hour(), minute(), second(), 1, month(), year()); + drawCenterElement(day()); + } + } + //Back + else if (pressedButton == 2) { + Teensy3Clock.set(now()); + dateMenu(firstStart); + break; + } + } + } +} + +/* Month Menu */ +void monthMenu(bool firstStart) { + drawTitle((char*) "Month", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 70, 70, (char*) "-"); + buttons_addButton(230, 60, 70, 70, (char*) "+"); + buttons_addButton(20, 150, 280, 70, (char*) "Back"); + buttons_drawButtons(); + drawCenterElement(month()); + //Touch handler + while (true) { + //touch press + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(true); + //Minus + if (pressedButton == 0) { + if (month() >= 1) { + if (month() > 1) + setTime(hour(), minute(), second(), day(), month() - 1, + year()); + else if (month() == 1) + setTime(hour(), minute(), second(), day(), 12, year()); + drawCenterElement(month()); + } + } + //Plus + else if (pressedButton == 1) { + if (month() <= 12) { + if (month() < 12) + setTime(hour(), minute(), second(), day(), month() + 1, + year()); + else if (month() == 12) + setTime(hour(), minute(), second(), day(), 1, year()); + drawCenterElement(month()); + } + } + //Back + else if (pressedButton == 2) { + Teensy3Clock.set(now()); + dateMenu(firstStart); + break; + } + } + } +} + +/* Year Menu */ +void yearMenu(bool firstStart) { + drawTitle((char*) "Year", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 70, 70, (char*) "-"); + buttons_addButton(230, 60, 70, 70, (char*) "+"); + buttons_addButton(20, 150, 280, 70, (char*) "Back"); + buttons_drawButtons(); + drawCenterElement(year()); + //Touch handler + while (true) { + //touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(true); + //Minus + if (pressedButton == 0) { + if (year() > 2017) { + setTime(hour(), minute(), second(), day(), month(), + year() - 1); + drawCenterElement(year()); + } + } + //Plus + else if (pressedButton == 1) { + if (year() < 2099) { + setTime(hour(), minute(), second(), day(), month(), + year() + 1); + drawCenterElement(year()); + } + } + //Back + else if (pressedButton == 2) { + Teensy3Clock.set(now()); + dateMenu(firstStart); + break; + } + } + } +} + +/* Calibrate the battery gauge */ +void batteryGauge() +{ + //Title & Background + drawTitle((char*) "Battery Gauge", true); + display_setColor(VGA_BLACK); + display_setFont(smallFont); + display_setBackColor(200, 200, 200); + display_print((char*)"Do you want to calibrate the", CENTER, 75); + display_print((char*)"battery gauge? Fully charge the", CENTER, 95); + display_print((char*)"battery first (LED green/blue).", CENTER, 115); + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(165, 160, 140, 55, (char*) "Yes"); + buttons_addButton(15, 160, 140, 55, (char*) "No"); + buttons_drawButtons(); + buttons_setTextFont(smallFont);; + //Touch handler + while (true) { + //If touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(true); + //YES + if (pressedButton == 0) { + //Calc the compensation value + checkBattery(false, true); + + //Show Message + showFullMessage((char*) "Battery gauge calibrated", true); + delay(1000); + + //Return + otherMenu(); + return; + } + //NO + else if (pressedButton == 1) { + otherMenu(); + return; + } + } + } +} + +/* Date Menu */ +void dateMenu(bool firstStart) { + drawTitle((char*) "Date", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 130, 70, (char*) "Day"); + buttons_addButton(170, 60, 130, 70, (char*) "Month"); + buttons_addButton(20, 150, 130, 70, (char*) "Year"); + if (firstStart) + buttons_addButton(170, 150, 130, 70, (char*) "Save"); + else + buttons_addButton(170, 150, 130, 70, (char*) "Back"); + buttons_drawButtons(); +} + +/* Date Menu Handler */ +void dateMenuHandler(bool firstStart) { + while (true) { + //touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(); + //Day + if (pressedButton == 0) { + dayMenu(firstStart); + } + //Month + else if (pressedButton == 1) { + monthMenu(firstStart); + } + //Year + else if (pressedButton == 2) { + yearMenu(firstStart); + } + //Back + else if (pressedButton == 3) { + if (!firstStart) + otherMenu(); + break; + } + } + } +} + +/* Time Menu */ +void timeMenu(bool firstStart) { + drawTitle((char*) "Time", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 130, 70, (char*) "Hour"); + buttons_addButton(170, 60, 130, 70, (char*) "Minute"); + buttons_addButton(20, 150, 130, 70, (char*) "Second"); + if (firstStart) + buttons_addButton(170, 150, 130, 70, (char*) "Save"); + else + buttons_addButton(170, 150, 130, 70, (char*) "Back"); + buttons_drawButtons(); +} + +/* Time Menu Handler */ +void timeMenuHandler(bool firstStart) { + while (true) { + //touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(); + //Hours + if (pressedButton == 0) { + hourMenu(firstStart); + } + //Minutes + else if (pressedButton == 1) { + minuteMenu(firstStart); + } + //Seconds + else if (pressedButton == 2) { + secondMenu(firstStart); + } + //Back + else if (pressedButton == 3) { + if (!firstStart) + otherMenu(); + break; + } + } + } +} + +/* Time & Date Menu */ +void otherMenu() { + drawTitle((char*) "Other Settings"); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 130, 70, (char*) "Time"); + buttons_addButton(170, 60, 130, 70, (char*) "Date"); + buttons_addButton(20, 150, 130, 70, (char*) "Battery Gauge"); + buttons_addButton(170, 150, 130, 70, (char*) "Back"); + buttons_drawButtons(); +} + +/* Time & Date Menu Handler */ +void otherMenuHandler() { + while (true) { + //touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(); + //Time + if (pressedButton == 0) { + timeMenu(); + timeMenuHandler(); + } + //Date + else if (pressedButton == 1) { + dateMenu(); + dateMenuHandler(); + } + //Battery Gauge + else if (pressedButton == 2) { + batteryGauge(); + } + //Back + else if (pressedButton == 3) { + settingsMenu(); + break; + } + } + } +} + + + +/* Visual image selection menu */ +void visualImageMenu(bool firstStart) { + drawTitle((char*) "Visual image", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 130, 70, (char*) "Disabled"); + buttons_addButton(170, 60, 130, 70, (char*) "Save JPEG"); + if (firstStart) { + buttons_addButton(20, 150, 280, 70, (char*) "Set"); + visualEnabled = true; + } + else + buttons_addButton(20, 150, 280, 70, (char*) "Back"); + buttons_drawButtons(); + if (!visualEnabled) + buttons_setActive(0); + else + buttons_setActive(1); + //Touch handler + while (true) { + //touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(); + //Disabled + if (pressedButton == 0) { + if (visualEnabled) { + visualEnabled = false; + buttons_setActive(0); + buttons_setInactive(1); + } + } + //Save JPEG, only when visual camera works + else if ((pressedButton == 1) && (checkDiagnostic(diag_camera))) { + if (!visualEnabled) { + visualEnabled = true; + buttons_setActive(1); + buttons_setInactive(0); + } + } + //Save + else if (pressedButton == 2) { + //Write new settings to EEPROM + EEPROM.write(eeprom_visualEnabled, visualEnabled); + if (firstStart) + return; + else { + storageMenu(); + } + break; + } + } + } +} + +/* Convert image selection menu */ +void convertImageMenu(bool firstStart) { + drawTitle((char*) "Convert image", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 130, 70, (char*) "DAT only"); + buttons_addButton(170, 60, 130, 70, (char*) "BMP & DAT"); + if (firstStart) { + buttons_addButton(20, 150, 280, 70, (char*) "Set"); + convertEnabled = false; + } + else + buttons_addButton(20, 150, 280, 70, (char*) "Back"); + buttons_drawButtons(); + if (!convertEnabled) + buttons_setActive(0); + else + buttons_setActive(1); + + //Touch handler + while (true) { + //touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(); + //DAT only + if (pressedButton == 0) { + if (convertEnabled) { + convertEnabled = false; + buttons_setActive(0); + buttons_setInactive(1); + } + } + //BMP & DAT + else if (pressedButton == 1) { + if (!convertEnabled) { + convertEnabled = true; + buttons_setActive(1); + buttons_setInactive(0); + } + } + + //Save + else if (pressedButton == 2) { + //Write new settings to EEPROM + EEPROM.write(eeprom_convertEnabled, convertEnabled); + if (firstStart) + return; + else { + storageMenu(); + } + break; + } + } + } +} + +/* Asks the user if he really wants to format */ +void formatStorage() { + //ThermocamV4 or DIY-Thermocam V2, check SD card + if ((mlx90614Version == mlx90614Version_old) || + (teensyVersion == teensyVersion_new)) { + showFullMessage((char*) "Checking SD card..", true); + if (!checkSDCard()) { + showFullMessage((char*) "Insert SD card", true); + delay(1000); + storageMenu(); + return; + } + } + //Title & Background + drawTitle((char*) "Storage"); + display_setColor(VGA_BLACK); + display_setFont(smallFont); + display_setBackColor(200, 200, 200); + display_print((char*)"Do you really want to format?", CENTER, 66); + display_print((char*)"This will delete all images", CENTER, 105); + display_print((char*)"and videos on the internal storage.", CENTER, 125); + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(165, 160, 140, 55, (char*) "Yes"); + buttons_addButton(15, 160, 140, 55, (char*) "No"); + buttons_drawButtons(); + buttons_setTextFont(smallFont); + //Touch handler + while (true) { + //If touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(true); + //YES + if (pressedButton == 0) { + showFullMessage((char*) "Format storage..", true); + formatCard(); + refreshFreeSpace(); + showFullMessage((char*) "Formatting finished", true); + delay(1000); + break; + } + //NO + if (pressedButton == 1) { + break; + } + } + } + //Go back to the storage menu + storageMenu(); +} + +/* Storage menu handler*/ +void storageMenuHandler() { + while (true) { + //touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(); + //Convert image + if (pressedButton == 0) { + convertImageMenu(); + } + //Visual image + else if (pressedButton == 1) { + visualImageMenu(); + } + //Format + else if (pressedButton == 2) { + formatStorage(); + } + //Back + else if (pressedButton == 3) { + settingsMenu(); + break; + } + } + } +} + +/* Storage menu */ +void storageMenu() { + drawTitle((char*) "Storage Settings"); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 130, 70, (char*) "Convert image"); + buttons_addButton(170, 60, 130, 70, (char*) "Visual image"); + buttons_addButton(20, 150, 130, 70, (char*) "Format"); + buttons_addButton(170, 150, 130, 70, (char*) "Back"); + buttons_drawButtons(); +} + +/* Temperature format menu */ +void tempFormatMenu(bool firstStart) { + drawTitle((char*) "Temp. Format", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 130, 70, (char*) "Celcius"); + buttons_addButton(170, 60, 130, 70, (char*) "Fahrenheit"); + buttons_addButton(20, 150, 280, 70, (char*) "Save"); + if (firstStart) { + buttons_relabelButton(2, (char*) "Set", false); + tempFormat = tempFormat_celcius; + } + buttons_drawButtons(); + if (tempFormat == tempFormat_celcius) + buttons_setActive(tempFormat_celcius); + else + buttons_setActive(tempFormat_fahrenheit); + //Touch handler + while (true) { + //touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(); + //Celcius + if (pressedButton == 0) { + if (tempFormat == tempFormat_fahrenheit) { + tempFormat = tempFormat_celcius; + buttons_setActive(0); + buttons_setInactive(1); + } + } + //Fahrenheit + else if (pressedButton == 1) { + if (tempFormat == tempFormat_celcius) { + tempFormat = tempFormat_fahrenheit; + buttons_setActive(1); + buttons_setInactive(0); + } + } + //Save + else if (pressedButton == 2) { + //Write new settings to EEPROM + EEPROM.write(eeprom_tempFormat, tempFormat); + if (firstStart) + return; + else { + displayMenu(); + } + break; + } + } + } +} + +/* Rotate display menu */ +void rotateDisplayMenu(bool firstStart) { + drawTitle((char*) "Disp. rotation", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 130, 70, (char*) "Rotation"); + buttons_addButton(170, 60, 130, 70, (char*) "Hor. Flip"); + buttons_addButton(20, 150, 130, 70, (char*) "Disabled"); + buttons_addButton(170, 150, 130, 70, (char*) "Save"); + if (firstStart) + buttons_relabelButton(3, (char*) "Set", false); + buttons_drawButtons(); + if (rotationVert) + buttons_setActive(0); + else if (rotationHorizont) + buttons_setActive(1); + else + buttons_setActive(2); + //Touch handler + while (true) { + //touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(); + //Rotate 180° + if (pressedButton == 0) { + if (!rotationVert) { + rotationVert = true; + rotationHorizont = false; + buttons_setActive(0); + buttons_setInactive(1); + buttons_setInactive(2); + } + } + //Mirror horizontally + else if (pressedButton == 1) { + if (!rotationHorizont) { + rotationHorizont = true; + rotationVert = false; + buttons_setActive(1); + buttons_setInactive(0); + buttons_setInactive(2); + } + } + //Disable + else if (pressedButton == 2) { + rotationVert = false; + rotationHorizont = false; + buttons_setActive(2); + buttons_setInactive(0); + buttons_setInactive(1); + } + //Save + else if (pressedButton == 3) { + //Write new settings to EEPROM + EEPROM.write(eeprom_rotationVert, rotationVert); + EEPROM.write(eeprom_rotationHorizont, rotationHorizont); + if (firstStart) + return; + //Set the rotation + setDisplayRotation(); + //Show the display menu + displayMenu(); + break; + } + } + } +} + +/* Screen timeout menu */ +void screenTimeoutMenu() { + drawTitle((char*) "Screen timeout"); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 130, 70, (char*) "Disabled"); + buttons_addButton(170, 60, 130, 70, (char*) "5 Min."); + buttons_addButton(20, 150, 130, 70, (char*) "20 Min."); + buttons_addButton(170, 150, 130, 70, (char*) "Back"); + buttons_drawButtons(); + //Set current one active + buttons_setActive(screenOffTime); + //Touch handler + while (true) { + //Touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(); + //Set to new color + if ((pressedButton == 0) || (pressedButton == 1) || (pressedButton == 2)) { + buttons_setInactive(screenOffTime); + screenOffTime = pressedButton; + buttons_setActive(screenOffTime); + } + //Save + else if (pressedButton == 3) { + //Write new settings to EEPROM + EEPROM.write(eeprom_screenOffTime, screenOffTime); + //Init timer + initScreenOffTimer(); + //Return to display menu + displayMenu(); + break; + } + } + } +} + + +/* Display menu handler*/ +void displayMenuHandler() { + while (true) { + //touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(); + //Temp. format + if (pressedButton == 0) { + tempFormatMenu(); + } + //Rotate display + else if (pressedButton == 1) { + rotateDisplayMenu(); + } + //Screen timeout + else if (pressedButton == 2) { + screenTimeoutMenu(); + } + //Back + else if (pressedButton == 3) { + settingsMenu(); + break; + } + } + } +} + +/* Display menu */ +void displayMenu() { + drawTitle((char*) "Display Settings"); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 130, 70, (char*) "Temp. format"); + buttons_addButton(170, 60, 130, 70, (char*) "Disp. rotation"); + buttons_addButton(20, 150, 130, 70, (char*) "Screen timeout"); + buttons_addButton(170, 150, 130, 70, (char*) "Back"); + buttons_drawButtons(); + +} + +/* Touch handler for the settings menu */ +void settingsMenuHandler() { + while (true) { + //touch press + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(); + //Display + if (pressedButton == 0) { + displayMenu(); + displayMenuHandler(); + } + //Storage + else if (pressedButton == 1) { + storageMenu(); + storageMenuHandler(); + } + //Other + else if (pressedButton == 2) { + otherMenu(); + otherMenuHandler(); + } + //Back + else if (pressedButton == 3) + break; + } + } +} + +/* Settings menu main screen */ +void settingsMenu() { + drawTitle((char*) "Settings"); + buttons_deleteAllButtons(); + buttons_setTextFont(smallFont); + buttons_addButton(20, 60, 130, 70, (char*) "Display"); + buttons_addButton(170, 60, 130, 70, (char*) "Storage"); + buttons_addButton(20, 150, 130, 70, (char*) "Other"); + buttons_addButton(170, 150, 130, 70, (char*) "Back"); + buttons_drawButtons(); +} diff --git a/Firmware_V2/src/gui/settingsmenu.h b/Firmware_V2/src/gui/settingsmenu.h new file mode 100644 index 0000000..afe68b8 --- /dev/null +++ b/Firmware_V2/src/gui/settingsmenu.h @@ -0,0 +1,56 @@ +/* +* +* SETTINGS MENU - Adjust different on-device settings +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef SETTINGSMENU_H +#define SETTINGSMENU_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void adjustCombinedGUI(); +void adjustCombinedLoading(); +bool adjustCombinedMenu(); +void adjustCombinedNewMenuHandler(bool firstStart = false); +void adjustCombinedNewMenu(bool firstStart = false); +bool adjustCombinedPresetSaveMenu(); +void adjustCombinedPresetSaveString(int pos); +void adjustCombinedRefresh(); +void adjustCombinedString(int pos); +void batteryGauge(); +void convertImageMenu(bool firstStart = false); +void dateMenuHandler(bool firstStart = false); +void dateMenu(bool firstStart = false); +void dayMenu(bool firstStart); +void displayMenuHandler(); +void displayMenu(); +void formatStorage(); +void hourMenu(bool firstStart); +void minuteMenu(bool firstStart); +void monthMenu(bool firstStart); +void otherMenuHandler(); +void otherMenu(); +void rotateDisplayMenu(bool firstStart = false); +void screenTimeoutMenu(); +void secondMenu(bool firstStart); +void settingsMenuHandler(); +void settingsMenu(); +void storageMenuHandler(); +void storageMenu(); +void tempFormatMenu(bool firstStart = false); +void timeMenuHandler(bool firstStart = false); +void timeMenu(bool firstStart = false); +void visualImageMenu(bool firstStart = false); +void yearMenu(bool firstStart); + +#endif /* SETTINGSMENU_H */ diff --git a/Firmware_V2/src/gui/videomenu.cpp b/Firmware_V2/src/gui/videomenu.cpp new file mode 100644 index 0000000..2481f2d --- /dev/null +++ b/Firmware_V2/src/gui/videomenu.cpp @@ -0,0 +1,452 @@ +/* +* +* VIDEO MENU - Record single frames or time interval videos +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################### STATIC DATA DECLARATIONS ##########################*/ + +//Video save interval in seconds +static int16_t videoInterval; + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Switch the video interval string*/ +void videoIntervalString(int pos) { + char* text = (char*) ""; + switch (pos) { + //1 second + case 0: + text = (char*) "1 second"; + break; + //5 seconds + case 1: + text = (char*) "5 seconds"; + break; + //10 seconds + case 2: + text = (char*) "10 seconds"; + break; + //20 seconds + case 3: + text = (char*) "20 seconds"; + break; + //30 seconds + case 4: + text = (char*) "30 seconds"; + break; + //1 minute + case 5: + text = (char*) "1 minute"; + break; + //5 minutes + case 6: + text = (char*) "5 minutes"; + break; + //10 minutes + case 7: + text = (char*) "10 minutes"; + break; + } + //Draws the current selection + mainMenuSelection(text); +} + +/* Touch Handler for the video interval chooser */ +bool videoIntervalHandler(byte* pos) { + //Main loop + while (true) { + //Touch screen pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(true); + //SELECT + if (pressedButton == 3) { + switch (*pos) { + //1 second + case 0: + videoInterval = 1; + break; + //5 seconds + case 1: + videoInterval = 5; + break; + //10 seconds + case 2: + videoInterval = 10; + break; + //20 seconds + case 3: + videoInterval = 20; + break; + //30 seconds + case 4: + videoInterval = 30; + break; + //1 minute + case 5: + videoInterval = 60; + break; + //5 minutes + case 6: + videoInterval = 300; + break; + //10 minutes + case 7: + videoInterval = 600; + break; + } + return true; + } + //BACK + else if (pressedButton == 2) { + return false; + } + //BACKWARD + else if (pressedButton == 0) { + if (*pos > 0) + *pos = *pos - 1; + else if (*pos == 0) + *pos = 7; + } + //FORWARD + else if (pressedButton == 1) { + if (*pos < 7) + *pos = *pos + 1; + else if (*pos == 7) + *pos = 0; + } + //Change the menu name + videoIntervalString(*pos); + } + } +} + +/* Start video menu to choose interval */ +bool videoIntervalChooser() { + bool rtn; + static byte videoIntervalPos = 0; + //Background + mainMenuBackground(); + //Title + mainMenuTitle((char*) "Choose interval"); + //Draw the selection menu + drawSelectionMenu(); + //Current choice name + videoIntervalString(videoIntervalPos); + //Touch handler - return true if exit to Main menu, otherwise false + rtn = videoIntervalHandler(&videoIntervalPos); + //Restore old fonts + display_setFont(smallFont); + buttons_setTextFont(smallFont); + //Delete the old buttons + buttons_deleteAllButtons(); + return rtn; +} + +/* Captures video frames in an interval */ +void videoCaptureInterval(int16_t* remainingTime, int* framesCaptured, char* dirname) { + char buffer[30]; + + //Measure time + long measure = millis(); + + //If there is no more time or the first frame + if ((*remainingTime <= 0) || (*framesCaptured == 0)) { + + //Send capture command to camera if activated and there is enough time + if ((visualEnabled && ((videoInterval >= 10) || + teensyVersion == teensyVersion_new) && checkDiagnostic(diag_camera)) && + ((*remainingTime == 0) || (*framesCaptured == 0))) + camera_capture(); + + //Save video raw frame + saveRawData(false, dirname, *framesCaptured); + } + + //Convert lepton data to RGB565 colors + convertColors(); + + //Display infos + displayInfos(); + + //Write to image buffer + display_writeToImage = true; + + //Display title + if ((teensyVersion == teensyVersion_new) && (hqRes)) + display_print((char*) "Interval capture", 105, 20); + else + display_print((char*) "Interval capture", 90, 20); + + //Show saving message + if ((*remainingTime <= 0) || (*framesCaptured == 0)) + sprintf(buffer, "Saving now.."); + //Show waiting time + else + sprintf(buffer, "Saving in %ds", *remainingTime); + + //Display message on buffer + if ((teensyVersion == teensyVersion_new) && (hqRes)) + display_print(buffer, 120, 200); + else + display_print(buffer, 105, 200); + + //Disable image buffer + display_writeToImage = false; + + //Draw thermal image on screen + displayBuffer(); + + //If there is no more time or the first frame + if ((*remainingTime <= 0) || (*framesCaptured == 0)) { + //Save visual image if activated and camera connected + if (visualEnabled && ((videoInterval >= 10) || + teensyVersion == teensyVersion_new) && checkDiagnostic(diag_camera)) { + + //Create filename to save data + frameFilename(saveFilename, *framesCaptured); + + //Save visual image + camera_get(camera_save, dirname); + + //Reset counter + int16_t elapsed = (millis() - measure) / 1000; + *remainingTime = videoInterval - elapsed; + } + else + *remainingTime = videoInterval; + + //Raise capture counter + *framesCaptured = *framesCaptured + 1; + } + else + { + //Wait rest of the time + measure = millis() - measure; + if (measure < 1000) + delay(1000 - measure); + + //Decrease remaining time by one + *remainingTime -= 1; + } +} + +/* Normal video capture */ +void videoCaptureNormal(char* dirname, int* framesCaptured) { + char buffer[30]; + + //Save video raw frame + saveRawData(false, dirname, *framesCaptured); + + //Convert the colors + convertColors(); + + //Display infos + displayInfos(); + + //Write to image buffer + display_writeToImage = true; + + //Display title + if ((teensyVersion == teensyVersion_new) && (hqRes)) + display_print((char*) "Video capture", 115, 20); + else + display_print((char*) "Video capture", 100, 20); + + //Raise capture counter + *framesCaptured = *framesCaptured + 1; + + //Display current frames captured + sprintf(buffer, "Frames captured: %5d", *framesCaptured); + if ((teensyVersion == teensyVersion_new) && (hqRes)) + display_print(buffer, 70, 200); + else + display_print(buffer, 55, 200); + + //Disable image buffer + display_writeToImage = false; + + //Refresh capture + displayBuffer(); + +} + +/* This screen is shown during the video capture */ +void videoCapture() { + //Help variables + char dirname[20]; + int16_t delayTime = videoInterval; + int framesCaptured = 0; + + //Show message + showFullMessage((char*)"Touch screen to turn it off"); + display_print((char*) "Press the button to abort", CENTER, 170); + delay(1500); + + //Create folder + createVideoFolder(dirname); + + //Switch to recording mode + videoSave = videoSave_recording; + + //Main loop + while (videoSave == videoSave_recording) { + + //Touch - turn display on or off + if (!digitalRead(pin_touch_irq)) { + digitalWrite(pin_lcd_backlight, !(checkScreenLight())); + while (!digitalRead(pin_touch_irq)); + } + + //Create the thermal image + createThermalImg(); + + //Video capture + if (videoInterval == 0) { + videoCaptureNormal(dirname, &framesCaptured); + } + //Interval capture + else { + videoCaptureInterval(&delayTime, &framesCaptured, dirname); + } + } + + //Turn the display on if it was off before + if (!checkScreenLight()) + enableScreenLight(); + + //Post processing for interval videos if enabled and wished + if ((framesCaptured > 0) && (convertPrompt())) + processVideoFrames(framesCaptured, dirname); + + //Show finished message + else { + showFullMessage((char*) "Video capture finished"); + delay(1000); + } + + //Refresh free space + refreshFreeSpace(); + + //Disable mode + videoSave = videoSave_disabled; +} + +/* Video mode, choose intervall or normal */ +void videoMode() { + + //Show message that video is only possible in thermal mode + if (displayMode != displayMode_thermal) { + //Show message + showFullMessage((char*) "Works only in thermal mode"); + delay(1000); + + //Disable mode and return + videoSave = videoSave_disabled; + return; + } + + //ThermocamV4 or DIY-Thermocam V2 - check SD card + if (!checkSDCard()) { + //Show message + showFullMessage((char*) "Waiting for card.."); + + //Wait for SD card + while (!checkSDCard()); + + //Redraw the last screen content + displayBuffer(); + } + + //Check if there is at least 1MB of space left + if (getSDSpace() < 1000) { + //Show message + showFullMessage((char*) "The SD card is full"); + delay(1000); + + //Disable mode and return + videoSave = videoSave_disabled; + return; + } + + //Border + drawMainMenuBorder(); + +redraw: + //Title & Background + mainMenuBackground(); + mainMenuTitle((char*)"Video Mode"); + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(15, 47, 140, 120, (char*) "Normal"); + buttons_addButton(165, 47, 140, 120, (char*) "Interval"); + buttons_addButton(15, 188, 140, 40, (char*) "Back"); + buttons_drawButtons(); + + //Touch handler + while (true) { + + //If touch pressed + if (touch_touched() == true) { + //Check which button has been pressed + int pressedButton = buttons_checkButtons(true); + + //Normal + if (pressedButton == 0) { + //Set video interval to zero, means normal + videoInterval = 0; + //Start capturing a video + videoCapture(); + break; + } + + //Interval + if (pressedButton == 1) { + //Choose the time interval + if (!videoIntervalChooser()) + //Redraw video mode if user pressed back + goto redraw; + //Start capturing a video + videoCapture(); + break; + } + + //Back + if (pressedButton == 2) { + //Disable mode and return + videoSave = videoSave_disabled; + return; + } + } + } +} diff --git a/Firmware_V2/src/gui/videomenu.h b/Firmware_V2/src/gui/videomenu.h new file mode 100644 index 0000000..25ce4c8 --- /dev/null +++ b/Firmware_V2/src/gui/videomenu.h @@ -0,0 +1,29 @@ +/* +* +* VIDEO MENU - Record single frames or time interval videos +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef VIDEOMENU_H +#define VIDEOMENU_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void videoCaptureInterval(int16_t* remainingTime, int* framesCaptured, char* dirname); +void videoCaptureNormal(char* dirname, int* framesCaptured); +void videoCapture(); +bool videoIntervalChooser(); +bool videoIntervalHandler(byte* pos); +void videoIntervalString(int pos); +void videoMode(); + +#endif /* VIDEOMENU_H */ diff --git a/Firmware_V2/src/hardware/battery.cpp b/Firmware_V2/src/hardware/battery.cpp new file mode 100644 index 0000000..bebf658 --- /dev/null +++ b/Firmware_V2/src/hardware/battery.cpp @@ -0,0 +1,108 @@ +/* + * + * BATTERY - Measure the lithium battery status + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* A method to calculate the lipo percentage out of its voltage */ +int getLipoPerc(float vol) { + if (vol >= 4.10) + return 100; + if (vol >= 4.00) + return 90; + if (vol >= 3.93) + return 80; + if (vol >= 3.88) + return 70; + if (vol >= 3.84) + return 60; + if (vol >= 3.80) + return 50; + if (vol >= 3.76) + return 40; + if (vol >= 3.73) + return 30; + if (vol >= 3.70) + return 20; + if (vol >= 3.66) + return 10; + if (vol >= 3.00) + return 0; + return -1; +} + +/* Measure the battery voltage and convert it to percent */ +void checkBattery(bool start, bool calibrate) { + //Read battery voltage + float vBat = (batMeasure->analogRead(pin_bat_measure) * 1.5 * 3.3) / batMeasure->getMaxValue(ADC_0); + + //Check if the device is charging + int vUSB = analogRead(pin_usb_measure); + //Battery is not working if no voltage measured and not connected to USB + if ((vBat == -1) && (vUSB <= 50)) + setDiagnostic(diag_bat); + + //If not charging, add some value to correct the voltage + if (vUSB <= 50) + { + //DIY-Thermocam V1 + if ((teensyVersion == teensyVersion_old) && (mlx90614Version == mlx90614Version_new)) + vBat += 0.05; + //Thermocam V4 + else if ((teensyVersion == teensyVersion_old) && (mlx90614Version == mlx90614Version_old)) + vBat += 0.25; + //DIY-Thermocam V2 + else + vBat += 0.15; + } + + //Recalibrate the battery gauge + if (calibrate) + { + //Calculate value to correct + float compensation = (4.15 - vBat) * 100; + batComp = (int8_t) round(compensation); + + //Save to EEPROM + EEPROM.write(eeprom_batComp, batComp); + } + + //At first launch, read value from EEPROM + if(start) + batComp = EEPROM.read(eeprom_batComp); + + //Correct voltage + if (batComp != 0) + vBat += (float) batComp / 100.0; + + //Calculate the percentage out of the voltage + batPercentage = getLipoPerc(vBat); + + //Show warning on startup if the battery is low + if ((batPercentage <= 20) && (batPercentage != -1) && (start)) { + showFullMessage((char*) "Battery almost empty, charge"); + delay(1000); + } +} diff --git a/Firmware_V2/src/hardware/battery.h b/Firmware_V2/src/hardware/battery.h new file mode 100644 index 0000000..623e601 --- /dev/null +++ b/Firmware_V2/src/hardware/battery.h @@ -0,0 +1,24 @@ +/* +* +* BATTERY - Measure the lithium battery status +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef BATTERY_H +#define BATTERY_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void checkBattery(bool start = false, bool calibrate = false); +int getLipoPerc(float vol); + +#endif /* BATTERY_H */ diff --git a/Firmware_V2/src/hardware/camera/camera.cpp b/Firmware_V2/src/hardware/camera/camera.cpp new file mode 100644 index 0000000..e1f062e --- /dev/null +++ b/Firmware_V2/src/hardware/camera/camera.cpp @@ -0,0 +1,414 @@ +/* +* +* Camera - Visual camera module +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +/*################# DATA TYPES, CONSTANTS & MACRO DEFINITIONS #################*/ + +//JPEG Decompressor structure +typedef struct { + const byte* jpic; + unsigned short jsize; + unsigned short joffset; +} IODEV; + +/*############################# PUBLIC VARIABLES ##############################*/ + +//The current camera resolution +byte camera_resolution; + +/*######################### STATIC DATA DECLARATIONS ##########################*/ + +//JPEG Decompressor +static void* camera_jdwork; +static JDEC camera_jd; +static IODEV camera_iodev; + +//Buffer to store the JPEG data +static uint8_t* camera_jpegData; + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Capture an image on the camera */ +void camera_capture(void) +{ + //Arducam-Mini + if (teensyVersion == teensyVersion_new) + ov2640_capture(); + //PTC-06 or PTC-08 + else + vc0706_capture(); +} + +/* Change the resolution of the camera */ +void camera_changeRes(byte camRes) +{ + //If camera not working, skip + if (!checkDiagnostic(diag_camera)) + return; + + //Change resolution + camera_resolution = camRes; + + //According to camera resolution set + switch (camera_resolution) + { + //Low resolution (160x120) + case camera_resLow: + //Arducam + if (teensyVersion == teensyVersion_new) + ov2640_setJPEGSize(OV2640_160x120); + //PTC-06 or PTC-08 + else + vc0706_changeCamRes(VC0706_160x120); + break; + //Middle resolution (320x240) + case camera_resMiddle: + // Arducam + if (teensyVersion == teensyVersion_new) + ov2640_setJPEGSize(OV2640_320x240); + //PTC-06 or PTC-08 + else + vc0706_changeCamRes(VC0706_320x240); + break; + //High resolution (640x480) + case camera_resHigh: + // Arducam + if (teensyVersion == teensyVersion_new) + ov2640_setJPEGSize(OV2640_640x480); + //PTC-06 or PTC-08 + else + vc0706_changeCamRes(VC0706_640x480); + break; + } + + //For OV2640, capture one frame and discard + if (teensyVersion == teensyVersion_new) + camera_capture(); +} + +/* Set the resolution to saving */ +void camera_setSaveRes() +{ + camera_changeRes(camera_resHigh); +} + +/* Set the resolution to streaming over serial */ +void camera_setStreamRes() +{ + camera_changeRes(camera_resMiddle); +} + +/* Set the resolution to display */ +void camera_setDisplayRes() +{ + //Set camera resolution to low + if ((teensyVersion == teensyVersion_old) || (!hqRes)) + camera_changeRes(camera_resLow); + + //Set to middle for HQ on Teensy 3.6 + else + camera_changeRes(camera_resMiddle); +} + +/* Init the camera module */ +void camera_init(void) +{ + boolean init; + + //Allocate space for the decompressor + camera_jdwork = malloc(3100); + + //Init Arducam-Mini + if (teensyVersion == teensyVersion_new) + init = ov2640_init(); + //Init PTC-06 or PTC-08 + else + init = vc0706_init(); + + //If init failed, set diagnostic + if (!init) { + setDiagnostic(diag_camera); + return; + } + + //Change resolution to save + camera_setSaveRes(); +} + +/* Normal output function for the JPEG Decompressor - Teensy 3.6 */ +unsigned int camera_decompOutNormal(JDEC * jd, void * bitmap, JRECT * rect) +{ + unsigned short * bmp = (unsigned short *)bitmap; + unsigned short x, y; + uint32_t count = 0; + uint32_t imagepos; + + //Go through the image vertically + for (y = rect->top; y <= rect->bottom; y++) { + + //Check if we draw inside the screen on y position + if (((y + (5 * adjCombDown) <= 239) && (y - (5 * adjCombUp) >= 0))) { + + //Go through the image horizontally + for (x = rect->left; x <= rect->right; x++) { + + //Check if we draw inside the screen on x position + if (((x + (5 * adjCombRight) <= 319) && (x - (5 * adjCombLeft) >= 0))) { + + //Get the image position + imagepos = x + (y * 320); + + //Do the visual alignment + imagepos += 5 * adjCombRight; + imagepos -= 5 * adjCombLeft; + imagepos += 5 * adjCombDown * 320; + imagepos -= 5 * adjCombUp * 320; + + //Do not use zero + if (bmp[count] == 0) + bmp[count] = 1; + + //Write to image buffer, rotated + if (rotationVert) + bigBuffer[76799 - imagepos] = bmp[count]; + //Write to image buffer, normal + else + bigBuffer[imagepos] = bmp[count];; + } + + //Raise counter + count++; + } + } + } + return 1; +} + +/* Combined output function for the JPEG Decompressor - Teensy 3.1 / 3.2 only */ +unsigned int camera_decompOutCombined(JDEC * jd, void * bitmap, JRECT * rect) { + //Help Variables + byte redV, greenV, blueV, redT, greenT, blueT, red, green, blue, xPos; + unsigned short pixel, x, y, imagepos, count; + unsigned short * bmp = (unsigned short *)bitmap; + count = 0; + //Go through the visual image + for (y = rect->top; y <= rect->bottom; y++) { + + //Check if we draw inside the screen on y position + if (((y + (5 * adjCombDown) <= 119) && (y - (5 * adjCombUp) >= 0))) { + + for (x = rect->left; x <= rect->right; x++) { + + //Mirror visual image for old HW + if (mlx90614Version == mlx90614Version_old) + xPos = (159 - x); + else + xPos = x; + + //Check if we draw inside the screen on x position + if (((xPos + (5 * adjCombRight) <= 159) && (xPos - (5 * adjCombLeft) >= 0))) { + + //Get the image position + imagepos = xPos + (y * 160); + //Do the visual alignment + imagepos += 5 * adjCombRight; + imagepos -= 5 * adjCombLeft; + imagepos += 5 * adjCombDown * 160; + imagepos -= 5 * adjCombUp * 160; + + //Create combined pixel out of thermal and visual + if (displayMode == displayMode_combined) { + //Get the visual image color + pixel = bmp[count++]; + //And extract the RGB values out of it + redV = (pixel & 0xF800) >> 8; + greenV = (pixel & 0x7E0) >> 3; + blueV = (pixel & 0x1F) << 3; + + //Get the thermal image color, rotated + if (rotationVert) + pixel = smallBuffer[19199 - imagepos]; + //Get the thermal image color, normal + else + pixel = smallBuffer[imagepos]; + //And extract the RGB values out of it + redT = (pixel & 0xF800) >> 8; + greenT = (pixel & 0x7E0) >> 3; + blueT = (pixel & 0x1F) << 3; + + //Mix both + red = redT * (1 - adjCombAlpha) + redV * adjCombAlpha; + green = greenT * (1 - adjCombAlpha) + greenV * adjCombAlpha; + blue = blueT * (1 - adjCombAlpha) + blueV * adjCombAlpha; + + //Set the pixel to the calculated RGB565 value + pixel = (((red & 248) | green >> 5) << 8) + | ((green & 28) << 3 | blue >> 3); + + } + + //Set pixel to visual image only + else + pixel = bmp[count++]; + + //Write to image buffer, rotated + if (rotationVert) + smallBuffer[19199 - imagepos] = pixel; + //Write to image buffer, normal + else + smallBuffer[imagepos] = pixel; + } + //Raise counter if it is not inside the image + else + count++; + } + } + } + return 1; +} + +/* Help function to insert the JPEG data into the decompressor */ +unsigned int camera_decompIn(JDEC * jd, byte* buff, unsigned int ndata) { + IODEV * dev = (IODEV *)jd->device; + ndata = (unsigned int)dev->jsize - dev->joffset > ndata ? + ndata : dev->jsize - dev->joffset; + if (buff) + memcpy(buff, dev->jpic + dev->joffset, ndata); + dev->joffset += ndata; + return ndata; +} + +/* Transfer, decompress or save the visual image */ +void camera_get(byte mode, char* dirname) +{ + uint32_t jpegLen; + + //Get length from Arducam for streaming + if (teensyVersion == teensyVersion_new) { + //Wait for image to be there + while (!ov2640_getBit(ARDUCHIP_TRIG, CAP_DONE_MASK)); + //Get JPEG length + jpegLen = ov2640_readFifoLength(); + } + + //Get length from PTC-06 or PTC-08 + else + jpegLen = vc0706_frameLength(); + + //Buffer for Teensy 3.1 / 3.2 + if (teensyVersion == teensyVersion_old) + { + //When streaming, allocate bytes + if (mode == camera_stream) + camera_jpegData = (uint8_t*)malloc(jpegLen); + //For saving and serial, do write directly + else + camera_jpegData = NULL; + } + //Buffer for Teensy 3.6 + else + { + //If rotated and not streaming, add EXIF header + if ((rotationVert) && (mode != camera_stream)) + camera_jpegData = (uint8_t*)malloc(jpegLen + 100); + //Otherwise allocate byte for JPEG data only + else + camera_jpegData = (uint8_t*)malloc(jpegLen); + } + + //Arducam + if (teensyVersion == teensyVersion_new) { + //Stream + if (mode == camera_stream) + ov2640_transfer(camera_jpegData, 1, &jpegLen); + + //Save + else if (mode == camera_save) { + //Transfer from camera + ov2640_transfer(camera_jpegData, 0, &jpegLen); + + //Start SD transmission + startAltClockline(); + + //Create JPEG file + createJPEGFile(dirname); + + //Write to SD file, EXIF included if rotated + sdFile.write(camera_jpegData, jpegLen); + + //Close file + sdFile.close(); + endAltClockline(); + + //Free buffer + free(camera_jpegData); + } + + //Serial transfer + else if (mode == camera_serial) + { + //Transfer from camera + ov2640_transfer(camera_jpegData, 0, &jpegLen); + + //Send length + Serial.write((jpegLen & 0xFF00) >> 8); + Serial.write(jpegLen & 0x00FF); + + //Send JPEG bytestream to serial port + Serial.write(camera_jpegData, jpegLen); + + //Free buffer + free(camera_jpegData); + } + } + //PTC-06 or PTC-08 + else + vc0706_transfer(camera_jpegData, jpegLen, mode, dirname); + + //For streaming, decompress data and capture next frame + if (mode == camera_stream) { + + //Decompress the image if not saving + camera_iodev.jpic = camera_jpegData; + camera_iodev.jsize = jpegLen; + + //the offset is zero + camera_iodev.joffset = 0; + + //Prepare the image for convertion to RGB565 + jd_prepare(&camera_jd, camera_decompIn, camera_jdwork, 3100, &camera_iodev); + + //Decompress into 320x240 buffer + if ((teensyVersion == teensyVersion_new) && (hqRes)) + jd_decomp(&camera_jd, camera_decompOutNormal, 0); + + //Decompress into 160x120 buffer, also with transparency + else + jd_decomp(&camera_jd, camera_decompOutCombined, 0); + + //Free the jpeg data array + free(camera_jpegData); + } +} diff --git a/Firmware_V2/src/hardware/camera/camera.h b/Firmware_V2/src/hardware/camera/camera.h new file mode 100644 index 0000000..a62338e --- /dev/null +++ b/Firmware_V2/src/hardware/camera/camera.h @@ -0,0 +1,41 @@ +/* +* +* Camera - Visual camera module +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef CAMERA_H +#define CAMERA_H + +/*################################# INCLUDES ##################################*/ + +#include + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +//The current camera resolution +extern byte camera_resolution; + +/*########################## PUBLIC PROCEDURES ################################*/ + +void camera_capture(void); +void camera_changeRes(byte camRes); +unsigned int camera_decompIn(JDEC * jd, byte* buff, unsigned int ndata); +unsigned int camera_decompOutCombined(JDEC * jd, void * bitmap, JRECT * rect); +unsigned int camera_decompOutNormal(JDEC * jd, void * bitmap, JRECT * rect); +void camera_get(byte mode, char* dirname = NULL); +void camera_init(void); +void camera_setDisplayRes(); +void camera_setSaveRes(); +void camera_setStreamRes(); + +#endif /* CAMERA_H */ diff --git a/Firmware_V2/src/hardware/camera/ov2640.cpp b/Firmware_V2/src/hardware/camera/ov2640.cpp new file mode 100644 index 0000000..12cb2a1 --- /dev/null +++ b/Firmware_V2/src/hardware/camera/ov2640.cpp @@ -0,0 +1,465 @@ +/* +* +* OV2640 - Driver for the Arducam camera module +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*################# DATA TYPES, CONSTANTS & MACRO DEFINITIONS #################*/ + +/* Sensor related definitions */ + +#define SPISPEED 4000000 +#define BMP 0 +#define JPEG 1 +#define OV2640_ADDRESS 0x60 +#define OV2640_CHIPID_HIGH 0x0A +#define OV2640_CHIPID_LOW 0x0B + +/* I2C control definition */ + +#define I2C_ADDR_8BIT 0 +#define I2C_ADDR_16BIT 1 +#define I2C_REG_8BIT 0 +#define I2C_REG_16BIT 1 +#define I2C_DAT_8BIT 0 +#define I2C_DAT_16BIT 1 + +/* Register initialization tables for SENSORs */ + +#define SENSOR_REG_TERM_8BIT 0xFF +#define SENSOR_REG_TERM_16BIT 0xFFFF +#define SENSOR_VAL_TERM_8BIT 0xFF +#define SENSOR_VAL_TERM_16BIT 0xFFFF +#define MAX_FIFO_SIZE 0x5FFFF + +/* ArduChip registers definition */ + +#define RWBIT 0x80 +#define ARDUCHIP_TEST1 0x00 +#define ARDUCHIP_MODE 0x02 +#define MCU2LCD_MODE 0x00 +#define CAM2LCD_MODE 0x01 +#define LCD2MCU_MODE 0x02 +#define ARDUCHIP_TIM 0x03 +#define ARDUCHIP_FIFO 0x04 +#define FIFO_CLEAR_MASK 0x01 +#define FIFO_START_MASK 0x02 +#define FIFO_RDPTR_RST_MASK 0x10 +#define FIFO_WRPTR_RST_MASK 0x20 +#define ARDUCHIP_GPIO 0x06 +#define GPIO_RESET_MASK 0x01 +#define GPIO_PWDN_MASK 0x02 +#define GPIO_PWREN_MASK 0x04 +#define BURST_FIFO_READ 0x3C +#define SINGLE_FIFO_READ 0x3D +#define ARDUCHIP_REV 0x40 +#define VER_LOW_MASK 0x3F +#define VER_HIGH_MASK 0xC0 +#define VSYNC_MASK 0x01 +#define SHUTTER_MASK 0x02 + +#define FIFO_SIZE1 0x42 +#define FIFO_SIZE2 0x43 +#define FIFO_SIZE3 0x44 + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* I2C Write 8bit address, 8bit data */ +byte ov2640_wrSensorReg8_8(int regID, int regDat) { + Wire.beginTransmission(0x60 >> 1); + Wire.write(regID & 0x00FF); + Wire.write(regDat & 0x00FF); + + if (Wire.endTransmission()) + return 0; + + delay(1); + + return 1; +} + +/* I2C Read 8bit address, 8bit data */ +byte ov2640_rdSensorReg8_8(uint8_t regID, uint8_t* regDat) { + Wire.beginTransmission(0x60 >> 1); + Wire.write(regID & 0x00FF); + Wire.endTransmission(); + + Wire.requestFrom((0x60 >> 1), 1); + if (Wire.available()) + *regDat = Wire.read(); + + delay(1); + return (1); +} + +/* I2C Array Write 8bit address, 8bit data */ +int ov2640_wrSensorRegs8_8(const struct sensor_reg* reglist) { + uint16_t reg_addr = 0; + uint16_t reg_val = 0; + const struct sensor_reg *next = reglist; + while ((reg_addr != 0xff) | (reg_val != 0xff)) + { + reg_addr = pgm_read_word(&next->reg); + reg_val = pgm_read_word(&next->val); + ov2640_wrSensorReg8_8(reg_addr, reg_val); + next++; + } + + return 1; +} + +/* SPI write operation */ +void ov2640_busWrite(int address, int value) { + startAltClockline(); + CORE_PIN8_CONFIG = PORT_PCR_MUX(2); + CORE_PIN12_CONFIG = PORT_PCR_MUX(1); + //Take the SS pin low to select the chip + SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE0)); + digitalWriteFast(pin_cam_cs, LOW); + //Send in the address and value via SPI + SPI.transfer(address); + SPI.transfer(value); + //Take the SS pin high to de-select the chip + digitalWriteFast(pin_cam_cs, HIGH); + SPI.endTransaction(); + CORE_PIN8_CONFIG = PORT_PCR_MUX(1); + CORE_PIN12_CONFIG = PORT_PCR_MUX(2); + endAltClockline(); +} + +/* SPI read operation */ +uint8_t ov2640_busRead(int address) { + startAltClockline(); + CORE_PIN8_CONFIG = PORT_PCR_MUX(2); + CORE_PIN12_CONFIG = PORT_PCR_MUX(1); + //Take the SS pin low to select the chip + SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE0)); + digitalWriteFast(pin_cam_cs, LOW); + //Send in the address and value via SPI + SPI.transfer(address); + uint8_t value = SPI.transfer(0x00); + //Take the SS pin high to de-select the chip + digitalWriteFast(pin_cam_cs, HIGH); + SPI.endTransaction(); + CORE_PIN8_CONFIG = PORT_PCR_MUX(1); + CORE_PIN12_CONFIG = PORT_PCR_MUX(2); + endAltClockline(); + //Return value + return value; +} + +/* Read ArduChip internal registers */ +uint8_t ov2640_readReg(uint8_t addr) { + uint8_t data; + data = ov2640_busRead(addr & 0x7F); + return data; +} + +/* Write ArduChip internal registers */ +void ov2640_writeReg(uint8_t addr, uint8_t data) { + ov2640_busWrite(addr | 0x80, data); +} + +/* Init the camera */ +bool ov2640_init(void) { + bool retVal = true; + uint8_t vid, pid; + + //Test SPI connection first + ov2640_writeReg(ARDUCHIP_TEST1, 0x55); + uint8_t rtnVal = ov2640_readReg(ARDUCHIP_TEST1); + if (rtnVal != 0x55) + retVal = false; + + //Test I2C connection second + ov2640_wrSensorReg8_8(0xff, 0x01); + ov2640_rdSensorReg8_8(OV2640_CHIPID_HIGH, &vid); + ov2640_rdSensorReg8_8(OV2640_CHIPID_LOW, &pid); + if ((vid != 0x26) || ((pid != 0x42) && (pid != 0x41))) + retVal = false; + + //Init registers + ov2640_wrSensorReg8_8(0xff, 0x01); + ov2640_wrSensorReg8_8(0x12, 0x80); + + //Wait some time + delay(100); + + //Set format to JPEG + ov2640_wrSensorRegs8_8(OV2640_JPEG_INIT); + ov2640_wrSensorRegs8_8(OV2640_YUV422); + ov2640_wrSensorRegs8_8(OV2640_JPEG); + ov2640_wrSensorReg8_8(0xff, 0x01); + ov2640_wrSensorReg8_8(0x15, 0x00); + ov2640_wrSensorRegs8_8(OV2640_320x240_JPEG); + + //Return status + return retVal; +} + +/* Set the format to JPEG or BMP */ +void ov2640_setFormat(byte fmt) { + //BMP + if (fmt == BMP) { + ov2640_wrSensorRegs8_8(OV2640_QVGA); + } + //JPEG + else { + ov2640_wrSensorRegs8_8(OV2640_JPEG_INIT); + ov2640_wrSensorRegs8_8(OV2640_YUV422); + ov2640_wrSensorRegs8_8(OV2640_JPEG); + ov2640_wrSensorReg8_8(0xff, 0x01); + ov2640_wrSensorReg8_8(0x15, 0x00); + ov2640_wrSensorRegs8_8(OV2640_320x240_JPEG); + } +} + +/* Set the JPEG pixel size of the image */ +void ov2640_setJPEGSize(uint8_t size) { + switch (size) + { + case OV2640_160x120: + ov2640_wrSensorRegs8_8(OV2640_160x120_JPEG); + break; + case OV2640_176x144: + ov2640_wrSensorRegs8_8(OV2640_176x144_JPEG); + break; + case OV2640_320x240: + ov2640_wrSensorRegs8_8(OV2640_320x240_JPEG); + break; + case OV2640_352x288: + ov2640_wrSensorRegs8_8(OV2640_352x288_JPEG); + break; + case OV2640_640x480: + ov2640_wrSensorRegs8_8(OV2640_640x480_JPEG); + break; + case OV2640_800x600: + ov2640_wrSensorRegs8_8(OV2640_800x600_JPEG); + break; + case OV2640_1024x768: + ov2640_wrSensorRegs8_8(OV2640_1024x768_JPEG); + break; + case OV2640_1280x1024: + ov2640_wrSensorRegs8_8(OV2640_1280x1024_JPEG); + break; + case OV2640_1600x1200: + ov2640_wrSensorRegs8_8(OV2640_1600x1200_JPEG); + break; + default: + ov2640_wrSensorRegs8_8(OV2640_320x240_JPEG); + break; + } +} + +/* Read Write FIFO length */ +uint32_t ov2640_readFifoLength(void) { + uint32_t len1, len2, len3, length; + len1 = ov2640_readReg(FIFO_SIZE1); + len2 = ov2640_readReg(FIFO_SIZE2); + len3 = ov2640_readReg(FIFO_SIZE3) & 0x7f; + length = ((len3 << 16) | (len2 << 8) | len1) & 0x07fffff; + return length; +} + +/* Start the FIFO burst mode */ +void ov2640_startFifoBurst(void) { + CORE_PIN8_CONFIG = PORT_PCR_MUX(2); + CORE_PIN12_CONFIG = PORT_PCR_MUX(1); + startAltClockline(); + SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE0)); + digitalWriteFast(pin_cam_cs, LOW); + SPI.transfer(BURST_FIFO_READ); +} + +/* End the FIFO burst mode */ +void ov2640_endFifoBurst(void) { + digitalWriteFast(pin_cam_cs, HIGH); + SPI.endTransaction(); + CORE_PIN8_CONFIG = PORT_PCR_MUX(1); + CORE_PIN12_CONFIG = PORT_PCR_MUX(2); + endAltClockline(); +} + +/* Set corresponding bit */ +void ov2640_setBit(uint8_t addr, uint8_t bit) { + uint8_t temp; + temp = ov2640_readReg(addr); + ov2640_writeReg(addr, temp | bit); +} + +/* Clear corresponding bit */ +void ov2640_clearBit(uint8_t addr, uint8_t bit) { + uint8_t temp; + temp = ov2640_readReg(addr); + ov2640_writeReg(addr, temp & (~bit)); +} + +/* Get corresponding bit status */ +uint8_t ov2640_getBit(uint8_t addr, uint8_t bit) { + uint8_t temp; + temp = ov2640_readReg(addr); + temp = temp & bit; + return temp; +} + +/* Set ArduCAM working mode */ +void ov2640_setMode(uint8_t mode) { + switch (mode) + { + //MCU2LCD_MODE: MCU writes the LCD screen GRAM + case MCU2LCD_MODE: + ov2640_writeReg(ARDUCHIP_MODE, MCU2LCD_MODE); + break; + //CAM2LCD_MODE: Camera takes control of the LCD screen + case CAM2LCD_MODE: + ov2640_writeReg(ARDUCHIP_MODE, CAM2LCD_MODE); + break; + //LCD2MCU_MODE: MCU read the LCD screen GRAM + case LCD2MCU_MODE: + ov2640_writeReg(ARDUCHIP_MODE, LCD2MCU_MODE); + break; + //Default is MCU2LCD_MODE + default: + ov2640_writeReg(ARDUCHIP_MODE, MCU2LCD_MODE); + break; + } +} + +/* Reset the FIFO pointer to zero */ +void ov2640_flushFifo(void) { + ov2640_writeReg(ARDUCHIP_FIFO, FIFO_CLEAR_MASK); +} + +/* Send capture command */ +void ov2640_startCapture(void) { + ov2640_writeReg(ARDUCHIP_FIFO, FIFO_START_MASK); +} + +/* Clear FIFO Complete flag */ +void ov2640_clearFifoFlag(void) { + ov2640_writeReg(ARDUCHIP_FIFO, FIFO_CLEAR_MASK); +} + +/* Read FIFO single */ +uint8_t ov2640_readFifo(void) { + uint8_t data; + data = ov2640_busRead(SINGLE_FIFO_READ); + return data; +} + +/* Send the capture command to the camera */ +void ov2640_capture(void) { + //Flush the FIFO + ov2640_flushFifo(); + //Clear the capture done flag + ov2640_clearFifoFlag(); + //Start capture + ov2640_startCapture(); +} + +/* Transfer the JPEG data from the OV2640 */ +void ov2640_transfer(uint8_t * jpegData, boolean stream, uint32_t* length) +{ +repeat: + + //Count variable + uint32_t counter = 0; + + //Start FIFO Burst + ov2640_startFifoBurst(); + + //Transfer data + uint8_t temp = 0xff, temp_last; + boolean is_header = 0; + + //Repeat as long as needed + while (counter < *length) + { + //If main menu should be entered + if (showMenu == showMenu_desired) + { + //Stop FIFO Burst + ov2640_endFifoBurst(); + //Go back + return; + } + + //Save last byte + temp_last = temp; + //Get new byte over SPI + temp = SPI.transfer(0x00); + + //Normal bytestream + if (is_header) + { + jpegData[counter] = temp; + counter++; + } + + //Start byte sqeuence + else if ((temp == 0xD8) & (temp_last == 0xFF)) + { + is_header = 1; + jpegData[0] = temp_last; + jpegData[1] = temp; + counter = 2; + } + + //If rotation enabled and saving / sending, include EXIF header + if ((counter == 20) && (!stream) && (rotationVert)) + { + for (uint8_t i = 0; i < 100; i++) + { + jpegData[counter + i] = exifHeader_rotated[i]; + } + counter += 100; + } + + //End byte sequence + if (((temp == 0xD9) && (temp_last == 0xFF))) + { + //Stop FIFO Burst + ov2640_endFifoBurst(); + + //Save length + *length = counter; + + //Everything was OK + return; + } + } + + //Stop FIFO Burst + ov2640_endFifoBurst(); + + //Get a new frame for stream + if (stream) + { + //Send capture command + camera_capture(); + + //Repeat frame receive + goto repeat; + } +} diff --git a/Firmware_V2/src/hardware/camera/ov2640.h b/Firmware_V2/src/hardware/camera/ov2640.h new file mode 100644 index 0000000..22b27e9 --- /dev/null +++ b/Firmware_V2/src/hardware/camera/ov2640.h @@ -0,0 +1,66 @@ +/* +* +* OV2640 - Driver for the Arducam camera module +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef OV2640_H +#define OV2640_H + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +#define OV2640_160x120 0 +#define OV2640_176x144 1 +#define OV2640_320x240 2 +#define OV2640_352x288 3 +#define OV2640_640x480 4 +#define OV2640_800x600 5 +#define OV2640_1024x768 6 +#define OV2640_1280x1024 7 +#define OV2640_1600x1200 8 + +#define CAP_DONE_MASK 0x08 +#define ARDUCHIP_TRIG 0x41 + +struct sensor_reg +{ + uint16_t reg; + uint16_t val; +}; + +/*########################## PUBLIC PROCEDURES ################################*/ + +uint8_t ov2640_busRead(int address); +void ov2640_busWrite(int address, int value); +void ov2640_capture(void); +void ov2640_clearBit(uint8_t addr, uint8_t bit); +void ov2640_clearFifoFlag(void); +void ov2640_endFifoBurst(void); +void ov2640_flushFifo(void); +uint8_t ov2640_getBit(uint8_t addr, uint8_t bit); +bool ov2640_init(void); +byte ov2640_rdSensorReg8_8(uint8_t regID, uint8_t* regDat); +uint32_t ov2640_readFifoLength(void); +uint8_t ov2640_readFifo(void); +uint8_t ov2640_readReg(uint8_t addr); +void ov2640_setBit(uint8_t addr, uint8_t bit); +void ov2640_setFormat(byte fmt); +void ov2640_setJPEGSize(uint8_t size); +void ov2640_setMode(uint8_t mode); +void ov2640_startCapture(void); +void ov2640_startFifoBurst(void); +void ov2640_transfer(uint8_t * jpegData, boolean stream, uint32_t* length); +void ov2640_writeReg(uint8_t addr, uint8_t data); +byte ov2640_wrSensorReg8_8(int regID, int regDat); +int ov2640_wrSensorRegs8_8(const struct sensor_reg* reglist); + +#endif /* OV2640_H */ diff --git a/Firmware_V2/src/hardware/camera/ov2640regs.cpp b/Firmware_V2/src/hardware/camera/ov2640regs.cpp new file mode 100644 index 0000000..7bb7b6b --- /dev/null +++ b/Firmware_V2/src/hardware/camera/ov2640regs.cpp @@ -0,0 +1,882 @@ +/* + * + * OV2640Regs - Register for the Arducam camera module + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include + +/*############################# PUBLIC VARIABLES ##############################*/ + +//EXIF header for 180 degree rotation +const uint8_t exifHeader_rotated[] = +{ 0xFF,0xE1,0x00,0x62,0x45,0x78,0x69,0x66,0x00,0x00,0x4D,0x4D,0x00,0x2A, + 0x00,0x00,0x00,0x08,0x00,0x05,0x01,0x12,0x00,0x03,0x00,0x00,0x00,0x01, + 0x00,0x03,0x00,0x00,0x01,0x1A,0x00,0x05,0x00,0x00,0x00,0x01,0x00,0x00, + 0x00,0x4A,0x01,0x1B,0x00,0x05,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x52, + 0x01,0x28,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x02,0x00,0x00,0x02,0x13, + 0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x01 }; + +const struct sensor_reg OV2640_QVGA[] PROGMEM = +{ + { 0xff, 0x0 }, + { 0x2c, 0xff }, + { 0x2e, 0xdf }, + { 0xff, 0x1 }, + { 0x3c, 0x32 }, + { 0x11, 0x0 }, + { 0x9, 0x2 }, + { 0x4, 0xa8 }, + { 0x13, 0xe5 }, + { 0x14, 0x48 }, + { 0x2c, 0xc }, + { 0x33, 0x78 }, + { 0x3a, 0x33 }, + { 0x3b, 0xfb }, + { 0x3e, 0x0 }, + { 0x43, 0x11 }, + { 0x16, 0x10 }, + { 0x39, 0x2 }, + { 0x35, 0x88 }, + + { 0x22, 0xa }, + { 0x37, 0x40 }, + { 0x23, 0x0 }, + { 0x34, 0xa0 }, + { 0x6, 0x2 }, + { 0x6, 0x88 }, + { 0x7, 0xc0 }, + { 0xd, 0xb7 }, + { 0xe, 0x1 }, + { 0x4c, 0x0 }, + { 0x4a, 0x81 }, + { 0x21, 0x99 }, + { 0x24, 0x40 }, + { 0x25, 0x38 }, + { 0x26, 0x82 }, + { 0x5c, 0x0 }, + { 0x63, 0x0 }, + { 0x46, 0x22 }, + { 0xc, 0x3a }, + { 0x5d, 0x55 }, + { 0x5e, 0x7d }, + { 0x5f, 0x7d }, + { 0x60, 0x55 }, + { 0x61, 0x70 }, + { 0x62, 0x80 }, + { 0x7c, 0x5 }, + { 0x20, 0x80 }, + { 0x28, 0x30 }, + { 0x6c, 0x0 }, + { 0x6d, 0x80 }, + { 0x6e, 0x0 }, + { 0x70, 0x2 }, + { 0x71, 0x94 }, + { 0x73, 0xc1 }, + { 0x3d, 0x34 }, + { 0x12, 0x4 }, + { 0x5a, 0x57 }, + { 0x4f, 0xbb }, + { 0x50, 0x9c }, + { 0xff, 0x0 }, + { 0xe5, 0x7f }, + { 0xf9, 0xc0 }, + { 0x41, 0x24 }, + { 0xe0, 0x14 }, + { 0x76, 0xff }, + { 0x33, 0xa0 }, + { 0x42, 0x20 }, + { 0x43, 0x18 }, + { 0x4c, 0x0 }, + { 0x87, 0xd0 }, + { 0x88, 0x3f }, + { 0xd7, 0x3 }, + { 0xd9, 0x10 }, + { 0xd3, 0x82 }, + { 0xc8, 0x8 }, + { 0xc9, 0x80 }, + { 0x7c, 0x0 }, + { 0x7d, 0x0 }, + { 0x7c, 0x3 }, + { 0x7d, 0x48 }, + { 0x7d, 0x48 }, + { 0x7c, 0x8 }, + { 0x7d, 0x20 }, + { 0x7d, 0x10 }, + { 0x7d, 0xe }, + { 0x90, 0x0 }, + { 0x91, 0xe }, + { 0x91, 0x1a }, + { 0x91, 0x31 }, + { 0x91, 0x5a }, + { 0x91, 0x69 }, + { 0x91, 0x75 }, + { 0x91, 0x7e }, + { 0x91, 0x88 }, + { 0x91, 0x8f }, + { 0x91, 0x96 }, + { 0x91, 0xa3 }, + { 0x91, 0xaf }, + { 0x91, 0xc4 }, + { 0x91, 0xd7 }, + { 0x91, 0xe8 }, + { 0x91, 0x20 }, + { 0x92, 0x0 }, + + { 0x93, 0x6 }, + { 0x93, 0xe3 }, + { 0x93, 0x3 }, + { 0x93, 0x3 }, + { 0x93, 0x0 }, + { 0x93, 0x2 }, + { 0x93, 0x0 }, + { 0x93, 0x0 }, + { 0x93, 0x0 }, + { 0x93, 0x0 }, + { 0x93, 0x0 }, + { 0x93, 0x0 }, + { 0x93, 0x0 }, + { 0x96, 0x0 }, + { 0x97, 0x8 }, + { 0x97, 0x19 }, + { 0x97, 0x2 }, + { 0x97, 0xc }, + { 0x97, 0x24 }, + { 0x97, 0x30 }, + { 0x97, 0x28 }, + { 0x97, 0x26 }, + { 0x97, 0x2 }, + { 0x97, 0x98 }, + { 0x97, 0x80 }, + { 0x97, 0x0 }, + { 0x97, 0x0 }, + { 0xa4, 0x0 }, + { 0xa8, 0x0 }, + { 0xc5, 0x11 }, + { 0xc6, 0x51 }, + { 0xbf, 0x80 }, + { 0xc7, 0x10 }, + { 0xb6, 0x66 }, + { 0xb8, 0xa5 }, + { 0xb7, 0x64 }, + { 0xb9, 0x7c }, + { 0xb3, 0xaf }, + { 0xb4, 0x97 }, + { 0xb5, 0xff }, + { 0xb0, 0xc5 }, + { 0xb1, 0x94 }, + { 0xb2, 0xf }, + { 0xc4, 0x5c }, + { 0xa6, 0x0 }, + { 0xa7, 0x20 }, + { 0xa7, 0xd8 }, + { 0xa7, 0x1b }, + { 0xa7, 0x31 }, + { 0xa7, 0x0 }, + { 0xa7, 0x18 }, + { 0xa7, 0x20 }, + { 0xa7, 0xd8 }, + { 0xa7, 0x19 }, + { 0xa7, 0x31 }, + { 0xa7, 0x0 }, + { 0xa7, 0x18 }, + { 0xa7, 0x20 }, + { 0xa7, 0xd8 }, + { 0xa7, 0x19 }, + { 0xa7, 0x31 }, + { 0xa7, 0x0 }, + { 0xa7, 0x18 }, + { 0x7f, 0x0 }, + { 0xe5, 0x1f }, + { 0xe1, 0x77 }, + { 0xdd, 0x7f }, + { 0xc2, 0xe }, + + { 0xff, 0x0 }, + { 0xe0, 0x4 }, + { 0xc0, 0xc8 }, + { 0xc1, 0x96 }, + { 0x86, 0x3d }, + { 0x51, 0x90 }, + { 0x52, 0x2c }, + { 0x53, 0x0 }, + { 0x54, 0x0 }, + { 0x55, 0x88 }, + { 0x57, 0x0 }, + + { 0x50, 0x92 }, + { 0x5a, 0x50 }, + { 0x5b, 0x3c }, + { 0x5c, 0x0 }, + { 0xd3, 0x4 }, + { 0xe0, 0x0 }, + + { 0xff, 0x0 }, + { 0x5, 0x0 }, + + { 0xda, 0x8 }, + { 0xd7, 0x3 }, + { 0xe0, 0x0 }, + + { 0x5, 0x0 }, + + + { 0xff,0xff } +}; + +const struct sensor_reg OV2640_JPEG_INIT[] PROGMEM = +{ + { 0xff, 0x00 }, + { 0x2c, 0xff }, + { 0x2e, 0xdf }, + { 0xff, 0x01 }, + { 0x3c, 0x32 }, + { 0x11, 0x00 }, + { 0x09, 0x02 }, + { 0x04, 0x28 }, + { 0x13, 0xe5 }, + { 0x14, 0x48 }, + { 0x2c, 0x0c }, + { 0x33, 0x78 }, + { 0x3a, 0x33 }, + { 0x3b, 0xfB }, + { 0x3e, 0x00 }, + { 0x43, 0x11 }, + { 0x16, 0x10 }, + { 0x39, 0x92 }, + { 0x35, 0xda }, + { 0x22, 0x1a }, + { 0x37, 0xc3 }, + { 0x23, 0x00 }, + { 0x34, 0xc0 }, + { 0x36, 0x1a }, + { 0x06, 0x88 }, + { 0x07, 0xc0 }, + { 0x0d, 0x87 }, + { 0x0e, 0x41 }, + { 0x4c, 0x00 }, + { 0x48, 0x00 }, + { 0x5B, 0x00 }, + { 0x42, 0x03 }, + { 0x4a, 0x81 }, + { 0x21, 0x99 }, + { 0x24, 0x40 }, + { 0x25, 0x38 }, + { 0x26, 0x82 }, + { 0x5c, 0x00 }, + { 0x63, 0x00 }, + { 0x61, 0x70 }, + { 0x62, 0x80 }, + { 0x7c, 0x05 }, + { 0x20, 0x80 }, + { 0x28, 0x30 }, + { 0x6c, 0x00 }, + { 0x6d, 0x80 }, + { 0x6e, 0x00 }, + { 0x70, 0x02 }, + { 0x71, 0x94 }, + { 0x73, 0xc1 }, + { 0x12, 0x40 }, + { 0x17, 0x11 }, + { 0x18, 0x43 }, + { 0x19, 0x00 }, + { 0x1a, 0x4b }, + { 0x32, 0x09 }, + { 0x37, 0xc0 }, + { 0x4f, 0x60 }, + { 0x50, 0xa8 }, + { 0x6d, 0x00 }, + { 0x3d, 0x38 }, + { 0x46, 0x3f }, + { 0x4f, 0x60 }, + { 0x0c, 0x3c }, + { 0xff, 0x00 }, + { 0xe5, 0x7f }, + { 0xf9, 0xc0 }, + { 0x41, 0x24 }, + { 0xe0, 0x14 }, + { 0x76, 0xff }, + { 0x33, 0xa0 }, + { 0x42, 0x20 }, + { 0x43, 0x18 }, + { 0x4c, 0x00 }, + { 0x87, 0xd5 }, + { 0x88, 0x3f }, + { 0xd7, 0x03 }, + { 0xd9, 0x10 }, + { 0xd3, 0x82 }, + { 0xc8, 0x08 }, + { 0xc9, 0x80 }, + { 0x7c, 0x00 }, + { 0x7d, 0x00 }, + { 0x7c, 0x03 }, + { 0x7d, 0x48 }, + { 0x7d, 0x48 }, + { 0x7c, 0x08 }, + { 0x7d, 0x20 }, + { 0x7d, 0x10 }, + { 0x7d, 0x0e }, + { 0x90, 0x00 }, + { 0x91, 0x0e }, + { 0x91, 0x1a }, + { 0x91, 0x31 }, + { 0x91, 0x5a }, + { 0x91, 0x69 }, + { 0x91, 0x75 }, + { 0x91, 0x7e }, + { 0x91, 0x88 }, + { 0x91, 0x8f }, + { 0x91, 0x96 }, + { 0x91, 0xa3 }, + { 0x91, 0xaf }, + { 0x91, 0xc4 }, + { 0x91, 0xd7 }, + { 0x91, 0xe8 }, + { 0x91, 0x20 }, + { 0x92, 0x00 }, + { 0x93, 0x06 }, + { 0x93, 0xe3 }, + { 0x93, 0x05 }, + { 0x93, 0x05 }, + { 0x93, 0x00 }, + { 0x93, 0x04 }, + { 0x93, 0x00 }, + { 0x93, 0x00 }, + { 0x93, 0x00 }, + { 0x93, 0x00 }, + { 0x93, 0x00 }, + { 0x93, 0x00 }, + { 0x93, 0x00 }, + { 0x96, 0x00 }, + { 0x97, 0x08 }, + { 0x97, 0x19 }, + { 0x97, 0x02 }, + { 0x97, 0x0c }, + { 0x97, 0x24 }, + { 0x97, 0x30 }, + { 0x97, 0x28 }, + { 0x97, 0x26 }, + { 0x97, 0x02 }, + { 0x97, 0x98 }, + { 0x97, 0x80 }, + { 0x97, 0x00 }, + { 0x97, 0x00 }, + { 0xc3, 0xed }, + { 0xa4, 0x00 }, + { 0xa8, 0x00 }, + { 0xc5, 0x11 }, + { 0xc6, 0x51 }, + { 0xbf, 0x80 }, + { 0xc7, 0x10 }, + { 0xb6, 0x66 }, + { 0xb8, 0xA5 }, + { 0xb7, 0x64 }, + { 0xb9, 0x7C }, + { 0xb3, 0xaf }, + { 0xb4, 0x97 }, + { 0xb5, 0xFF }, + { 0xb0, 0xC5 }, + { 0xb1, 0x94 }, + { 0xb2, 0x0f }, + { 0xc4, 0x5c }, + { 0xc0, 0x64 }, + { 0xc1, 0x4B }, + { 0x8c, 0x00 }, + { 0x86, 0x3D }, + { 0x50, 0x00 }, + { 0x51, 0xC8 }, + { 0x52, 0x96 }, + { 0x53, 0x00 }, + { 0x54, 0x00 }, + { 0x55, 0x00 }, + { 0x5a, 0xC8 }, + { 0x5b, 0x96 }, + { 0x5c, 0x00 }, + { 0xd3, 0x00 }, //{ 0xd3, 0x7f }, + { 0xc3, 0xed }, + { 0x7f, 0x00 }, + { 0xda, 0x00 }, + { 0xe5, 0x1f }, + { 0xe1, 0x67 }, + { 0xe0, 0x00 }, + { 0xdd, 0x7f }, + { 0x05, 0x00 }, + + { 0x12, 0x40 }, + { 0xd3, 0x04 }, //{ 0xd3, 0x7f }, + { 0xc0, 0x16 }, + { 0xC1, 0x12 }, + { 0x8c, 0x00 }, + { 0x86, 0x3d }, + { 0x50, 0x00 }, + { 0x51, 0x2C }, + { 0x52, 0x24 }, + { 0x53, 0x00 }, + { 0x54, 0x00 }, + { 0x55, 0x00 }, + { 0x5A, 0x2c }, + { 0x5b, 0x24 }, + { 0x5c, 0x00 }, + { 0xff, 0xff }, +}; + +const struct sensor_reg OV2640_YUV422[] PROGMEM = +{ + { 0xFF, 0x00 }, + { 0x05, 0x00 }, + { 0xDA, 0x10 }, + { 0xD7, 0x03 }, + { 0xDF, 0x00 }, + { 0x33, 0x80 }, + { 0x3C, 0x40 }, + { 0xe1, 0x77 }, + { 0x00, 0x00 }, + { 0xff, 0xff }, +}; + +const struct sensor_reg OV2640_JPEG[] PROGMEM = +{ + { 0xe0, 0x14 }, + { 0xe1, 0x77 }, + { 0xe5, 0x1f }, + { 0xd7, 0x03 }, + { 0xda, 0x10 }, + { 0xe0, 0x00 }, + { 0xFF, 0x01 }, + { 0x04, 0x08 }, + { 0xff, 0xff }, +}; + +/* JPG 160x120 */ +const struct sensor_reg OV2640_160x120_JPEG[] PROGMEM = +{ + { 0xff, 0x01 }, + { 0x12, 0x40 }, + { 0x17, 0x11 }, + { 0x18, 0x43 }, + { 0x19, 0x00 }, + { 0x1a, 0x4b }, + { 0x32, 0x09 }, + { 0x4f, 0xca }, + { 0x50, 0xa8 }, + { 0x5a, 0x23 }, + { 0x6d, 0x00 }, + { 0x39, 0x12 }, + { 0x35, 0xda }, + { 0x22, 0x1a }, + { 0x37, 0xc3 }, + { 0x23, 0x00 }, + { 0x34, 0xc0 }, + { 0x36, 0x1a }, + { 0x06, 0x88 }, + { 0x07, 0xc0 }, + { 0x0d, 0x87 }, + { 0x0e, 0x41 }, + { 0x4c, 0x00 }, + { 0xff, 0x00 }, + { 0xe0, 0x04 }, + { 0xc0, 0x64 }, + { 0xc1, 0x4b }, + { 0x86, 0x35 }, + { 0x50, 0x92 }, + { 0x51, 0xc8 }, + { 0x52, 0x96 }, + { 0x53, 0x00 }, + { 0x54, 0x00 }, + { 0x55, 0x00 }, + { 0x57, 0x00 }, + { 0x5a, 0x28 }, + { 0x5b, 0x1e }, + { 0x5c, 0x00 }, + { 0xe0, 0x00 }, + { 0xff, 0xff }, +}; + +/* JPG, 0x176x144 */ +const struct sensor_reg OV2640_176x144_JPEG[] PROGMEM = +{ + { 0xff, 0x01 }, + { 0x12, 0x40 }, + { 0x17, 0x11 }, + { 0x18, 0x43 }, + { 0x19, 0x00 }, + { 0x1a, 0x4b }, + { 0x32, 0x09 }, + { 0x4f, 0xca }, + { 0x50, 0xa8 }, + { 0x5a, 0x23 }, + { 0x6d, 0x00 }, + { 0x39, 0x12 }, + { 0x35, 0xda }, + { 0x22, 0x1a }, + { 0x37, 0xc3 }, + { 0x23, 0x00 }, + { 0x34, 0xc0 }, + { 0x36, 0x1a }, + { 0x06, 0x88 }, + { 0x07, 0xc0 }, + { 0x0d, 0x87 }, + { 0x0e, 0x41 }, + { 0x4c, 0x00 }, + { 0xff, 0x00 }, + { 0xe0, 0x04 }, + { 0xc0, 0x64 }, + { 0xc1, 0x4b }, + { 0x86, 0x35 }, + { 0x50, 0x92 }, + { 0x51, 0xc8 }, + { 0x52, 0x96 }, + { 0x53, 0x00 }, + { 0x54, 0x00 }, + { 0x55, 0x00 }, + { 0x57, 0x00 }, + { 0x5a, 0x2c }, + { 0x5b, 0x24 }, + { 0x5c, 0x00 }, + { 0xe0, 0x00 }, + { 0xff, 0xff }, +}; + +/* JPG 320x240 */ +const struct sensor_reg OV2640_320x240_JPEG[] PROGMEM = +{ + { 0xff, 0x01 }, + { 0x12, 0x40 }, + { 0x17, 0x11 }, + { 0x18, 0x43 }, + { 0x19, 0x00 }, + { 0x1a, 0x4b }, + { 0x32, 0x09 }, + { 0x4f, 0xca }, + { 0x50, 0xa8 }, + { 0x5a, 0x23 }, + { 0x6d, 0x00 }, + { 0x39, 0x12 }, + { 0x35, 0xda }, + { 0x22, 0x1a }, + { 0x37, 0xc3 }, + { 0x23, 0x00 }, + { 0x34, 0xc0 }, + { 0x36, 0x1a }, + { 0x06, 0x88 }, + { 0x07, 0xc0 }, + { 0x0d, 0x87 }, + { 0x0e, 0x41 }, + { 0x4c, 0x00 }, + { 0xff, 0x00 }, + { 0xe0, 0x04 }, + { 0xc0, 0x64 }, + { 0xc1, 0x4b }, + { 0x86, 0x35 }, + { 0x50, 0x89 }, + { 0x51, 0xc8 }, + { 0x52, 0x96 }, + { 0x53, 0x00 }, + { 0x54, 0x00 }, + { 0x55, 0x00 }, + { 0x57, 0x00 }, + { 0x5a, 0x50 }, + { 0x5b, 0x3c }, + { 0x5c, 0x00 }, + { 0xe0, 0x00 }, + { 0xff, 0xff }, +}; + +/* JPG 352x288 */ +const struct sensor_reg OV2640_352x288_JPEG[] PROGMEM = +{ + { 0xff, 0x01 }, + { 0x12, 0x40 }, + { 0x17, 0x11 }, + { 0x18, 0x43 }, + { 0x19, 0x00 }, + { 0x1a, 0x4b }, + { 0x32, 0x09 }, + { 0x4f, 0xca }, + { 0x50, 0xa8 }, + { 0x5a, 0x23 }, + { 0x6d, 0x00 }, + { 0x39, 0x12 }, + { 0x35, 0xda }, + { 0x22, 0x1a }, + { 0x37, 0xc3 }, + { 0x23, 0x00 }, + { 0x34, 0xc0 }, + { 0x36, 0x1a }, + { 0x06, 0x88 }, + { 0x07, 0xc0 }, + { 0x0d, 0x87 }, + { 0x0e, 0x41 }, + { 0x4c, 0x00 }, + { 0xff, 0x00 }, + { 0xe0, 0x04 }, + { 0xc0, 0x64 }, + { 0xc1, 0x4b }, + { 0x86, 0x35 }, + { 0x50, 0x89 }, + { 0x51, 0xc8 }, + { 0x52, 0x96 }, + { 0x53, 0x00 }, + { 0x54, 0x00 }, + { 0x55, 0x00 }, + { 0x57, 0x00 }, + { 0x5a, 0x58 }, + { 0x5b, 0x48 }, + { 0x5c, 0x00 }, + { 0xe0, 0x00 }, + { 0xff, 0xff }, +}; + +/* JPG 640x480 */ +const struct sensor_reg OV2640_640x480_JPEG[] PROGMEM = +{ + { 0xff, 0x01 }, + { 0x11, 0x01 }, + { 0x12, 0x00 }, // Bit[6:4]: Resolution selection//0x02 + { 0x17, 0x11 }, // HREFST[10:3] + { 0x18, 0x75 }, // HREFEND[10:3] + { 0x32, 0x36 }, // Bit[5:3]: HREFEND[2:0]; Bit[2:0]: HREFST[2:0] + { 0x19, 0x01 }, // VSTRT[9:2] + { 0x1a, 0x97 }, // VEND[9:2] + { 0x03, 0x0f }, // Bit[3:2]: VEND[1:0]; Bit[1:0]: VSTRT[1:0] + { 0x37, 0x40 }, + { 0x4f, 0xbb }, + { 0x50, 0x9c }, + { 0x5a, 0x57 }, + { 0x6d, 0x80 }, + { 0x3d, 0x34 }, + { 0x39, 0x02 }, + { 0x35, 0x88 }, + { 0x22, 0x0a }, + { 0x37, 0x40 }, + { 0x34, 0xa0 }, + { 0x06, 0x02 }, + { 0x0d, 0xb7 }, + { 0x0e, 0x01 }, + + { 0xff, 0x00 }, + { 0xe0, 0x04 }, + { 0xc0, 0xc8 }, + { 0xc1, 0x96 }, + { 0x86, 0x3d }, + { 0x50, 0x89 }, + { 0x51, 0x90 }, + { 0x52, 0x2c }, + { 0x53, 0x00 }, + { 0x54, 0x00 }, + { 0x55, 0x88 }, + { 0x57, 0x00 }, + { 0x5a, 0xa0 }, + { 0x5b, 0x78 }, + { 0x5c, 0x00 }, + { 0xd3, 0x04 }, + { 0xe0, 0x00 }, + + { 0xff, 0xff }, +}; + +/* JPG 800x600 */ +const struct sensor_reg OV2640_800x600_JPEG[] PROGMEM = +{ + { 0xff, 0x01 }, + { 0x11, 0x01 }, + { 0x12, 0x00 }, // Bit[6:4]: Resolution selection//0x02ä¸ºå½©æ¡ + { 0x17, 0x11 }, // HREFST[10:3] + { 0x18, 0x75 }, // HREFEND[10:3] + { 0x32, 0x36 }, // Bit[5:3]: HREFEND[2:0]; Bit[2:0]: HREFST[2:0] + { 0x19, 0x01 }, // VSTRT[9:2] + { 0x1a, 0x97 }, // VEND[9:2] + { 0x03, 0x0f }, // Bit[3:2]: VEND[1:0]; Bit[1:0]: VSTRT[1:0] + { 0x37, 0x40 }, + { 0x4f, 0xbb }, + { 0x50, 0x9c }, + { 0x5a, 0x57 }, + { 0x6d, 0x80 }, + { 0x3d, 0x34 }, + { 0x39, 0x02 }, + { 0x35, 0x88 }, + { 0x22, 0x0a }, + { 0x37, 0x40 }, + { 0x34, 0xa0 }, + { 0x06, 0x02 }, + { 0x0d, 0xb7 }, + { 0x0e, 0x01 }, + + { 0xff, 0x00 }, + { 0xe0, 0x04 }, + { 0xc0, 0xc8 }, + { 0xc1, 0x96 }, + { 0x86, 0x35 }, + { 0x50, 0x89 }, + { 0x51, 0x90 }, + { 0x52, 0x2c }, + { 0x53, 0x00 }, + { 0x54, 0x00 }, + { 0x55, 0x88 }, + { 0x57, 0x00 }, + { 0x5a, 0xc8 }, + { 0x5b, 0x96 }, + { 0x5c, 0x00 }, + { 0xd3, 0x02 }, + { 0xe0, 0x00 }, + + { 0xff, 0xff }, +}; + +/* JPG 1024x768 */ +const struct sensor_reg OV2640_1024x768_JPEG[] PROGMEM = +{ + { 0xff, 0x01 }, + { 0x11, 0x01 }, + { 0x12, 0x00 }, // Bit[6:4]: Resolution selection//0x02ä¸ºå½©æ¡ + { 0x17, 0x11 }, // HREFST[10:3] + { 0x18, 0x75 }, // HREFEND[10:3] + { 0x32, 0x36 }, // Bit[5:3]: HREFEND[2:0]; Bit[2:0]: HREFST[2:0] + { 0x19, 0x01 }, // VSTRT[9:2] + { 0x1a, 0x97 }, // VEND[9:2] + { 0x03, 0x0f }, // Bit[3:2]: VEND[1:0]; Bit[1:0]: VSTRT[1:0] + { 0x37, 0x40 }, + { 0x4f, 0xbb }, + { 0x50, 0x9c }, + { 0x5a, 0x57 }, + { 0x6d, 0x80 }, + { 0x3d, 0x34 }, + { 0x39, 0x02 }, + { 0x35, 0x88 }, + { 0x22, 0x0a }, + { 0x37, 0x40 }, + { 0x34, 0xa0 }, + { 0x06, 0x02 }, + { 0x0d, 0xb7 }, + { 0x0e, 0x01 }, + + { 0xff, 0x00 }, + { 0xc0, 0xC8 }, + { 0xc1, 0x96 }, + { 0x8c, 0x00 }, + { 0x86, 0x3D }, + { 0x50, 0x00 }, + { 0x51, 0x90 }, + { 0x52, 0x2C }, + { 0x53, 0x00 }, + { 0x54, 0x00 }, + { 0x55, 0x88 }, + { 0x5a, 0x00 }, + { 0x5b, 0xC0 }, + { 0x5c, 0x01 }, + { 0xd3, 0x02 }, + + + { 0xff, 0xff }, +}; + +/* JPG 1280x1024 */ +const struct sensor_reg OV2640_1280x1024_JPEG[] PROGMEM = +{ + { 0xff, 0x01 }, + { 0x11, 0x01 }, + { 0x12, 0x00 }, // Bit[6:4]: Resolution selection//0x02 + { 0x17, 0x11 }, // HREFST[10:3] + { 0x18, 0x75 }, // HREFEND[10:3] + { 0x32, 0x36 }, // Bit[5:3]: HREFEND[2:0]; Bit[2:0]: HREFST[2:0] + { 0x19, 0x01 }, // VSTRT[9:2] + { 0x1a, 0x97 }, // VEND[9:2] + { 0x03, 0x0f }, // Bit[3:2]: VEND[1:0]; Bit[1:0]: VSTRT[1:0] + { 0x37, 0x40 }, + { 0x4f, 0xbb }, + { 0x50, 0x9c }, + { 0x5a, 0x57 }, + { 0x6d, 0x80 }, + { 0x3d, 0x34 }, + { 0x39, 0x02 }, + { 0x35, 0x88 }, + { 0x22, 0x0a }, + { 0x37, 0x40 }, + { 0x34, 0xa0 }, + { 0x06, 0x02 }, + { 0x0d, 0xb7 }, + { 0x0e, 0x01 }, + + { 0xff, 0x00 }, + { 0xe0, 0x04 }, + { 0xc0, 0xc8 }, + { 0xc1, 0x96 }, + { 0x86, 0x3d }, + { 0x50, 0x00 }, + { 0x51, 0x90 }, + { 0x52, 0x2c }, + { 0x53, 0x00 }, + { 0x54, 0x00 }, + { 0x55, 0x88 }, + { 0x57, 0x00 }, + { 0x5a, 0x40 }, + { 0x5b, 0xf0 }, + { 0x5c, 0x01 }, + { 0xd3, 0x02 }, + { 0xe0, 0x00 }, + + { 0xff, 0xff }, +}; + +/* JPG 1600x1200 */ +const struct sensor_reg OV2640_1600x1200_JPEG[] PROGMEM = +{ + { 0xff, 0x01 }, + { 0x11, 0x01 }, + { 0x12, 0x00 }, // Bit[6:4]: Resolution selection//0x02为 + { 0x17, 0x11 }, // HREFST[10:3] + { 0x18, 0x75 }, // HREFEND[10:3] + { 0x32, 0x36 }, // Bit[5:3]: HREFEND[2:0]; Bit[2:0]: HREFST[2:0] + { 0x19, 0x01 }, // VSTRT[9:2] + { 0x1a, 0x97 }, // VEND[9:2] + { 0x03, 0x0f }, // Bit[3:2]: VEND[1:0]; Bit[1:0]: VSTRT[1:0] + { 0x37, 0x40 }, + { 0x4f, 0xbb }, + { 0x50, 0x9c }, + { 0x5a, 0x57 }, + { 0x6d, 0x80 }, + { 0x3d, 0x34 }, + { 0x39, 0x02 }, + { 0x35, 0x88 }, + { 0x22, 0x0a }, + { 0x37, 0x40 }, + { 0x34, 0xa0 }, + { 0x06, 0x02 }, + { 0x0d, 0xb7 }, + { 0x0e, 0x01 }, + + { 0xff, 0x00 }, + { 0xe0, 0x04 }, + { 0xc0, 0xc8 }, + { 0xc1, 0x96 }, + { 0x86, 0x3d }, + { 0x50, 0x00 }, + { 0x51, 0x90 }, + { 0x52, 0x2c }, + { 0x53, 0x00 }, + { 0x54, 0x00 }, + { 0x55, 0x88 }, + { 0x57, 0x00 }, + { 0x5a, 0x90 }, + { 0x5b, 0x2C }, + { 0x5c, 0x05 }, //bit2->1;bit[1:0]->1 + { 0xd3, 0x02 }, + { 0xe0, 0x00 }, + + { 0xff, 0xff }, +}; diff --git a/Firmware_V2/src/hardware/camera/ov2640regs.h b/Firmware_V2/src/hardware/camera/ov2640regs.h new file mode 100644 index 0000000..b0dd57f --- /dev/null +++ b/Firmware_V2/src/hardware/camera/ov2640regs.h @@ -0,0 +1,36 @@ +/* + * + * OV2640Regs - Register for the Arducam camera module + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +#ifndef OV2640_REGS_H +#define OV2640_REGS_H + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +extern const uint8_t exifHeader_rotated[]; +extern const struct sensor_reg OV2640_QVGA[] PROGMEM; +extern const struct sensor_reg OV2640_JPEG_INIT[] PROGMEM; +extern const struct sensor_reg OV2640_YUV422[] PROGMEM; +extern const struct sensor_reg OV2640_JPEG[] PROGMEM; +extern const struct sensor_reg OV2640_160x120_JPEG[] PROGMEM; +extern const struct sensor_reg OV2640_176x144_JPEG[] PROGMEM; +extern const struct sensor_reg OV2640_320x240_JPEG[] PROGMEM; +extern const struct sensor_reg OV2640_352x288_JPEG[] PROGMEM; +extern const struct sensor_reg OV2640_640x480_JPEG[] PROGMEM; +extern const struct sensor_reg OV2640_800x600_JPEG[] PROGMEM; +extern const struct sensor_reg OV2640_1024x768_JPEG[] PROGMEM; +extern const struct sensor_reg OV2640_1280x1024_JPEG[] PROGMEM; +extern const struct sensor_reg OV2640_1600x1200_JPEG[] PROGMEM; + +#endif /* OV2640_REGS_H */ diff --git a/Firmware_V2/src/hardware/camera/vc0706.cpp b/Firmware_V2/src/hardware/camera/vc0706.cpp new file mode 100644 index 0000000..e09955b --- /dev/null +++ b/Firmware_V2/src/hardware/camera/vc0706.cpp @@ -0,0 +1,379 @@ +/* +* +* VC0706 - Driver for the PTC-06 or PTC-08 camera module +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include + +/*######################### STATIC DATA DECLARATIONS ##########################*/ + +static uint8_t serialNum = 0; +static uint8_t camerabuff[101]; +static uint8_t bufferLen = 0; +static uint16_t frameptr = 0; + +//EXIF header for horizontal mirror in ThermocamV4 +static const uint8_t exifHeader_mirror[] = +{ 0xFF, 0xE1, 0x00, 0x62, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, +0x00, 0x05, 0x01, 0x12, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x01, 0x1A, 0x00, 0x05, +0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x4A, 0x01, 0x1B, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, +0x00, 0x52, 0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x02, 0x13, 0x00, 0x03, +0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, +0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01 }; + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Send a specific command */ +void vc0706_sendCommand(uint8_t cmd, uint8_t args[], uint8_t argn) { + Serial1.print(0x56, 0); + Serial1.print(serialNum, 0); + Serial1.print(cmd, 0); + + for (uint8_t i = 0; i < argn; i++) { + Serial1.print(args[i], 0); + } +} + +/* Read the response from the camera */ +uint8_t vc0706_readResponse(uint8_t numbytes, uint8_t timeout) { + uint8_t counter = 0; + bufferLen = 0; + int avail; + while ((timeout != counter) && (bufferLen != numbytes)) { + avail = Serial1.available(); + if (avail <= 0) { + delay(1); + counter++; + continue; + } + counter = 0; + camerabuff[bufferLen++] = Serial1.read(); + } + return bufferLen; +} + +/* Verify the response from the camera */ +boolean vc0706_verifyResponse(uint8_t command) { + if ((camerabuff[0] != 0x76) || + (camerabuff[1] != serialNum) || + (camerabuff[2] != command) || + (camerabuff[3] != 0x0)) + return 0; + return 1; +} + +/* Run a specific command */ +boolean vc0706_runCommand(uint8_t cmd, uint8_t *args, uint8_t argn, uint8_t resplen, boolean flushflag) { + if (flushflag) { + vc0706_readResponse(100, 10); + } + vc0706_sendCommand(cmd, args, argn); + if (vc0706_readResponse(resplen, 200) != resplen) + return 0; + if (!vc0706_verifyResponse(cmd)) + return 0; + return 1; +} + +/* Reset the connection */ +boolean vc0706_reset(void) +{ + uint8_t args[] = { 0x0 }; + return vc0706_runCommand(0x26, args, 1, 5); +} + +/* End the connection */ +boolean vc0706_end() { + uint8_t args[] = { 0x01, 0x03 }; + return vc0706_runCommand(0x36, args, sizeof(args), 5); +} + + +/* Change the baudrate */ +boolean vc0706_changeBaudRate() { + uint8_t args[] = { 0x03, 0x01, 0x0D, 0xA6 }; + return vc0706_runCommand(0x24, args, sizeof(args), 5); +} + +/* Set the image size */ +boolean vc0706_setImageSize(uint8_t x) { + uint8_t args[] = { 0x05, 0x04, 0x01, 0x00, 0x19, x }; + return vc0706_runCommand(0x31, args, sizeof(args), 5); +} + +/* Get the image size */ +uint8_t vc0706_getImageSize() { + uint8_t args[] = { 0x4, 0x4, 0x1, 0x00, 0x19 }; + if (!vc0706_runCommand(0x30, args, sizeof(args), 6)) + return -1; + return camerabuff[5]; +} + +/* Set the compression ratio */ +boolean vc0706_setCompression(uint8_t c) { + uint8_t args[] = { 0x5, 0x1, 0x1, 0x12, 0x04, c }; + return vc0706_runCommand(0x31, args, sizeof(args), 5); +} + +/* Transfer a package from the VC0706 camera */ +void vc0706_transPackage(byte bytesToRead, uint8_t* buffer) +{ + int avail; + //Send counter and buffer length to zero + uint8_t counter = 0; + uint8_t bufferLen = 0; + //As long as no timeout and not all bytes read + while ((10 != counter) && (bufferLen != (bytesToRead + 5))) { + //Check how many bytes are available + avail = Serial1.available(); + //If there are none, raise timeout counter + if (avail <= 0) { + delay(1); + counter++; + continue; + } + //Reset timeout counter if there is a packet + counter = 0; + //Add the data to the buffer + buffer[bufferLen++] = Serial1.read(); + } +} + +/* Control the framebuffer */ +boolean vc0706_cameraFrameBuffCtrl(uint8_t command) { + uint8_t args[] = { 0x1, command }; + return vc0706_runCommand(0x36, args, sizeof(args), 5); +} + +/* Take a picture */ +boolean vc0706_capture() { + frameptr = 0; + return vc0706_cameraFrameBuffCtrl(0x0); +} + +/* Get the JPEG frame length */ +uint32_t vc0706_frameLength(void) { + uint8_t args[] = { 0x01, 0x00 }; + if (!vc0706_runCommand(0x34, args, sizeof(args), 9)) + return 0; + uint32_t len; + len = camerabuff[5]; + len <<= 8; + len |= camerabuff[6]; + len <<= 8; + len |= camerabuff[7]; + len <<= 8; + len |= camerabuff[8]; + return len; +} + +/* Check if data is available */ +uint8_t vc0706_available(void) { + return bufferLen; +} + +/* Read the JPEG picture */ +boolean vc0706_readPicture(uint8_t n) { + uint8_t args[] = { 0x0C, 0x0, 0x0A, + 0, 0, (uint8_t)(frameptr >> 8), (uint8_t)(frameptr & 0xFF), + 0, 0, 0, n, + 10 >> 8, 10 & 0xFF }; + + if (!vc0706_runCommand(0x32, args, sizeof(args), 5, 0)) + return 0; + frameptr += n; + return 1; +} + +/* Transfer the JPEG bytestream*/ +void vc0706_transfer(uint8_t* jpegData, uint16_t jpegLen, byte mode, char* dirname) +{ + //Count variable + uint16_t counter = 0; + + //Create the buffer + uint8_t* buffer = (uint8_t*)malloc(128 + 5); + + //For serial transfer, send frame length + if (mode == camera_serial) + { + //When rotation is enabled or using the ThermocamV4, send EXIF + if ((rotationVert && (mlx90614Version == mlx90614Version_new)) || (mlx90614Version == mlx90614Version_old)) + { + Serial.write(((jpegLen + 100) & 0xFF00) >> 8); + Serial.write((jpegLen + 100) & 0x00FF); + } + //Send JPEG Bytestream only + else + { + Serial.write((jpegLen & 0xFF00) >> 8); + Serial.write(jpegLen & 0x00FF); + } + } + + //For saving to SD card + if (mode == camera_save) + { + //Start alternative clock line + startAltClockline(); + //Create JPEG file + createJPEGFile(dirname); + } + + //Transfer data + while (jpegLen > 0) { + //Calculate the bytes left to read + uint8_t bytesToRead = min(jpegLen, 128); + + //Send the read command + if (!vc0706_readPicture(bytesToRead)) + continue; + + //Transfer a package + vc0706_transPackage(bytesToRead, buffer); + + //For streaming, add it to the jpeg data buffer + if (mode == camera_stream) + { + for (int i = 0; i < bytesToRead; i++) { + jpegData[counter] = buffer[i]; + counter++; + } + } + + //For serial transfer + if (mode == camera_serial) + { + //Rotation on DIY-Thermocam V1 or V2 + if ((counter == 0) && rotationVert && (mlx90614Version == mlx90614Version_new)) + { + Serial.write(buffer, 40); + Serial.write(exifHeader_rotated, 100); + Serial.write(&buffer[40], (bytesToRead - 40)); + counter++; + } + //Mirror on ThermocamV4 + else if ((counter == 0) && (mlx90614Version == mlx90614Version_old)) + { + Serial.write(buffer, 40); + Serial.write(exifHeader_mirror, sizeof(exifHeader_mirror)); + Serial.write(&buffer[40], (bytesToRead - 40)); + counter++; + } + //No EXIF + else + Serial.write(buffer, bytesToRead); + } + + //For saving to SD card + if (mode == camera_save) + { + //Rotation on DIY-Thermocam V1 or V2 + if ((counter == 0) && rotationVert && (mlx90614Version == mlx90614Version_new)) + { + sdFile.write(buffer, 40); + sdFile.write(exifHeader_rotated, 100); + sdFile.write(&buffer[40], (bytesToRead - 40)); + counter++; + } + //Mirror on ThermocamV4 + else if ((counter == 0) && (mlx90614Version == mlx90614Version_old)) + { + sdFile.write(buffer, 40); + sdFile.write(exifHeader_mirror, sizeof(exifHeader_mirror)); + sdFile.write(&buffer[40], (bytesToRead - 40)); + counter++; + } + //No EXIF + else + sdFile.write(buffer, bytesToRead); + } + + //Substract transfered bytes from total + jpegLen -= bytesToRead; + } + + //Free the buffer + free(buffer); + + //End transmission + vc0706_end(); + + //For saving to SD, close file + if (mode == camera_save) + { + //Close the file + sdFile.close(); + //End SD Transmission + endAltClockline(); + } +} + +/* Start connecting to the camera */ +void vc0706_begin(void) { + //Start with 38400 to send new baudrate + Serial1.begin(38400); + //Wait + delay(15); + //Change baudrate + vc0706_changeBaudRate(); + //Reconnect using 115.2k + Serial1.begin(115200); + //Wait + delay(15); +} + +/* Init the camera module */ +boolean vc0706_init(void) +{ + //Start connection at 115.2k Baud + vc0706_begin(); + //Test if the camera works + if (!vc0706_capture()) { + //Try it again after 100ms + delay(100); + //Reset + vc0706_reset(); + //Restart connection at 115.2k Baud + vc0706_begin(); + //Try to take a picture again + if (!vc0706_capture()) + return 0; + } + //Set compression + vc0706_setCompression(95); + //Skip the picture + vc0706_end(); + //Everything working + return 1; +} + +/* Change the resolution of the device */ +void vc0706_changeCamRes(uint8_t size) { + //Change resolution + vc0706_setImageSize(size); + //Reset the device to change the resolution + vc0706_reset(); + //Wait some time + delay(300); + //Re-establish the connection to the device + vc0706_begin(); +} diff --git a/Firmware_V2/src/hardware/camera/vc0706.h b/Firmware_V2/src/hardware/camera/vc0706.h new file mode 100644 index 0000000..aec3e59 --- /dev/null +++ b/Firmware_V2/src/hardware/camera/vc0706.h @@ -0,0 +1,56 @@ +/* +* +* VC0706 - Driver for the PTC-06 or PTC-08 camera module +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef VC0706_H +#define VC0706_H + +#ifdef __cplusplus +extern "C" { +#endif + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +#define VC0706_640x480 0x00 +#define VC0706_320x240 0x11 +#define VC0706_160x120 0x22 + +/*########################## PUBLIC PROCEDURES ################################*/ + +uint8_t vc0706_available(void); +void vc0706_begin(void); +boolean vc0706_cameraFrameBuffCtrl(uint8_t command); +boolean vc0706_capture(); +boolean vc0706_changeBaudRate(); +void vc0706_changeCamRes(uint8_t size); +boolean vc0706_end(); +uint32_t vc0706_frameLength(void); +uint8_t vc0706_getImageSize(); +boolean vc0706_init(void); +boolean vc0706_readPicture(uint8_t n); +uint8_t vc0706_readResponse(uint8_t numbytes, uint8_t timeout); +boolean vc0706_reset(void); +boolean vc0706_runCommand(uint8_t cmd, uint8_t *args, uint8_t argn, uint8_t resplen, boolean flushflag = 1); +void vc0706_sendCommand(uint8_t cmd, uint8_t args[] = 0, uint8_t argn = 0); +boolean vc0706_setCompression(uint8_t c); +boolean vc0706_setImageSize(uint8_t x); +void vc0706_transfer(uint8_t* jpegData, uint16_t jpegLen, byte mode, char* dirname); +void vc0706_transPackage(byte bytesToRead, uint8_t* buffer); +boolean vc0706_verifyResponse(uint8_t command); + +#ifdef __cplusplus +} +#endif + +#endif /* VC0706_H */ diff --git a/Firmware_V2/src/hardware/connection.cpp b/Firmware_V2/src/hardware/connection.cpp new file mode 100644 index 0000000..2d30a19 --- /dev/null +++ b/Firmware_V2/src/hardware/connection.cpp @@ -0,0 +1,1232 @@ +/* +* +* CONNECTION - Communication protocol for the USB serial data transmission +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################### STATIC DATA DECLARATIONS ##########################*/ + +//Command, default send frame +static byte sendCmd = FRAME_NORMAL; + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Get integer out of a text string */ +int getInt(String text) +{ + char temp[6]; + text.toCharArray(temp, 5); + int x = atoi(temp); + return x; +} + +/* Enter the serial connection mode if no display attached */ +bool checkNoDisplay() +{ + //No connection to ILI9341 and touch screen -> go to USB serial + if (!checkDiagnostic(diag_display) && !checkDiagnostic(diag_touch)) + return true; + //Display connected + return false; +} + +/* Send the lepton raw limits */ +void sendRawLimits() { + //Send min + Serial.write((minValue & 0xFF00) >> 8); + Serial.write(minValue & 0x00FF); + //Send max + Serial.write((maxValue & 0xFF00) >> 8); + Serial.write(maxValue & 0x00FF); +} + +/* Send the lepton raw data*/ +void sendRawData(bool color) { + uint16_t result; + + //For the Lepton2.x sensor, write 4800 raw values + if ((leptonVersion != leptonVersion_3_0_shutter) && (leptonVersion != leptonVersion_3_5_shutter) && (!color)) { + for (int line = 0; line < 60; line++) { + for (int column = 0; column < 80; column++) { + result = smallBuffer[(line * 2 * 160) + (column * 2)]; + Serial.write((result & 0xFF00) >> 8); + Serial.write(result & 0x00FF); + } + } + } + //For the Lepton3 sensor, write 19200 raw values + else { + for (int i = 0; i < 19200; i++) { + Serial.write((smallBuffer[i] & 0xFF00) >> 8); + Serial.write(smallBuffer[i] & 0x00FF); + } + } +} + +/* Sends the framebuffer */ +void sendFramebuffer() +{ + //160x120 + if ((teensyVersion == teensyVersion_old) || (!hqRes)) + { + for (int i = 0; i < 19200; i++) + { + Serial.write((smallBuffer[i] & 0xFF00) >> 8); + Serial.write(smallBuffer[i] & 0x00FF); + } + } + + //320x240 + else + { + for (uint32_t i = 0; i < 76800; i++) + { + Serial.write((bigBuffer[i] & 0xFF00) >> 8); + Serial.write(bigBuffer[i] & 0x00FF); + } + } +} + +/* Sends the configuration data */ +void sendConfigData() { + //Lepton version + if (leptonVersion == leptonVersion_2_5_shutter) + Serial.write(leptonVersion_2_0_shutter); + else if (leptonVersion == leptonVersion_3_5_shutter) + Serial.write(leptonVersion_3_0_shutter); + else + Serial.write(leptonVersion); + //Rotation + Serial.write(rotationVert); + //Send color scheme + Serial.write(colorScheme); + //Send the temperature format + Serial.write(tempFormat); + //Send the show spot attribute + Serial.write(spotEnabled); + //Send the show colorbar attribute + Serial.write(colorbarEnabled); + //Send the show hottest / coldest attribute + Serial.write(minMaxPoints); + //Send the text color + Serial.write(textColor); + //Send the filter type + Serial.write(filterType); + //Send adjust limits allowed + Serial.write((autoMode) && (!limitsLocked)); +} + +/* Sends the calibration status */ +void sendCalStatus() { + //Send status byte + Serial.write(calStatus); + //Send remaining seconds left + if (calStatus == cal_warmup) + Serial.write((byte)abs(30 - ((millis() - calTimer) / 1000))); + else + Serial.write(0); +} + +/* Sends the visual image in low or high quality */ +void sendVisualImg() +{ + bool high; + + //If not enough data available, leave + if (Serial.available() < 1) + { + Serial.write(CMD_INVALID); + return; + } + + //Read byte from serial port + byte read = Serial.read(); + //Check if it has a valid number + if ((read >= 0) && (read <= 1)) + high = read; + //Send invalid + else + { + Serial.write(CMD_INVALID); + return; + } + + //Only when camera is working + if (checkDiagnostic(diag_camera)) { + + //Change resolution to 320x240 for serial streaming + if ((!high) && (camera_resolution != camera_resMiddle)) + camera_setStreamRes(); + + //Change resolution to 640x480 for snapshots + if ((high) && (camera_resolution != camera_resHigh)) + camera_setSaveRes(); + + //Capture a frame + camera_capture(); + + //Send the data over the serial port + camera_get(camera_serial); + } + + //Send invalid + else + Serial.write(CMD_INVALID); +} + +/* Sends the calibration data */ +void sendCalibrationData() { + uint8_t farray[4]; + + //Send the calibration offset first + floatToBytes(farray, (float)calOffset); + for (int i = 0; i < 4; i++) + Serial.write(farray[i]); + //Send the calibration slope + floatToBytes(farray, (float)calSlope); + for (int i = 0; i < 4; i++) + Serial.write(farray[i]); +} + +/* Sends the spot temp*/ +void sendSpotTemp() { + //Array to store the byte-converted float value + uint8_t farray[4]; + + //Convert float to bytes + floatToBytes(farray, spotTemp); + + //Send the four bytes out + for (int i = 0; i < 4; i++) + Serial.write(farray[i]); +} + +/* Sets the time */ +void setTime() { + //Wait for time string, maximum 1 second + uint32_t timer = millis(); + while (!Serial.available() && ((millis() - timer) < 1000)); + + //If there was no timestring + if (Serial.available() == 0) + { + //Send ACK and return + Serial.write(CMD_SET_TIME); + return; + } + + //Read time + String dateIn = Serial.readString(); + + //Check if valid + if (getInt(dateIn.substring(0, 4) >= 2017)) { + //Set the clock + setTime(getInt(dateIn.substring(11, 13)), getInt(dateIn.substring(14, 16)), getInt(dateIn.substring(17, 19)), + getInt(dateIn.substring(8, 10)), getInt(dateIn.substring(5, 7)), getInt(dateIn.substring(0, 4))); + //Set the RTC + Teensy3Clock.set(now()); + } + + //Send ACK + Serial.write(CMD_SET_TIME); +} + +/* Sets the calibration offset */ +void setCalOffset() { + uint8_t farray[4]; + + //If not enough data available or RAD, leave + if ((Serial.available() < 4) || (leptonVersion == leptonVersion_2_5_shutter) || (leptonVersion == leptonVersion_3_5_shutter)) + { + Serial.write(CMD_INVALID); + return; + } + + //Read calibration offset + for (int i = 0; i < 4; i++) + farray[i] = Serial.read(); + calOffset = bytesToFloat(farray); + + //Store to EEPROM + storeCalibration(); + + //Send ACK + Serial.write(CMD_SET_CALOFFSET); +} + +/* Sets the calibration slope */ +void setCalSlope() { + uint8_t farray[4]; + + //If not enough data available or RAD, leave + if ((Serial.available() < 4) || (leptonVersion == leptonVersion_2_5_shutter) || (leptonVersion == leptonVersion_3_5_shutter)) + { + Serial.write(CMD_INVALID); + return; + } + + //Read calibration slope + for (int i = 0; i < 4; i++) + farray[i] = Serial.read(); + calSlope = bytesToFloat(farray); + + //Store to EEPROM + storeCalibration(); + + //Send ACK + Serial.write(CMD_SET_CALSLOPE); +} + +/* Send the temperature points */ +void sendTempPoints() { + for (byte i = 0; i < 96; i++) { + //Send index value + Serial.write((tempPoints[i][0] & 0xFF00) >> 8); + Serial.write(tempPoints[i][0] & 0x00FF); + //Send raw value + Serial.write((tempPoints[i][1] & 0xFF00) >> 8); + Serial.write(tempPoints[i][1] & 0x00FF); + } +} + +/* Send the laser state */ +void sendLaserState() { + Serial.write(laserEnabled); +} + +/* Send the shutter mode */ +void sendShutterMode() { + Serial.write(leptonShutter); +} + +/* Send the battery status in percentage */ +void sendBatteryStatus() { + Serial.write(batPercentage); +} + +/* Send the current firmware version */ +void sendFWVersion() { + Serial.write(fwVersion); +} + +/* Set the temperature limits */ +void setLimits() +{ + //If not enough data available, leave + if (Serial.available() < 1) + { + Serial.write(CMD_INVALID); + return; + } + + //Read byte from serial port + byte read = Serial.read(); + + //Check if it has a valid number + if ((read >= 0) && (read <= 1)) + { + //Lock limits + if (read == 0) + limitsLocked = true; + + //Auto mode + else { + //Enable auto mode + autoMode = true; + //Disable limits locked + limitsLocked = false; + } + } + //Send invalid + else + { + Serial.write(CMD_INVALID); + return; + } + + //Send ACK + Serial.write(CMD_SET_LIMITS); +} + +/* Set the text color */ +void setTextColor() +{ + //If not enough data available, leave + if (Serial.available() < 1) + { + Serial.write(CMD_INVALID); + return; + } + + //Read byte from serial port + byte read = Serial.read(); + + //Check if read result is valid + if ((read >= textColor_white) && (read <= textColor_blue)) + { + //Set text color to input + textColor = read; + //Change it + changeTextColor(); + //Save to EEPROM + EEPROM.write(eeprom_textColor, textColor); + } + //Send invalid + else + { + Serial.write(CMD_INVALID); + return; + } + + //Send ACK + Serial.write(CMD_SET_TEXTCOLOR); +} + +/* Set the color scheme */ +void setColorScheme() +{ + //If not enough data available, leave + if (Serial.available() < 1) + { + Serial.write(CMD_INVALID); + return; + } + + //Read byte from serial port + byte read = Serial.read(); + + //Check if it has a valid number + if ((read >= 0) && (read <= (colorSchemeTotal - 1))) + { + //Set color scheme to input + colorScheme = read; + //Select right color scheme + selectColorScheme(); + //Save to EEPROM + EEPROM.write(eeprom_colorScheme, colorScheme); + } + //Send invalid + else + { + Serial.write(CMD_INVALID); + return; + } + + //Send ACK + Serial.write(CMD_SET_COLORSCHEME); +} + + +/* Set the temperature format */ +void setTempFormat() +{ + //If not enough data available, leave + if (Serial.available() < 1) + { + Serial.write(CMD_INVALID); + return; + } + + //Read byte from serial port + byte read = Serial.read(); + + //Check if it has a valid number + if ((read >= 0) && (read <= 1)) + { + //Set temperature format to input + tempFormat = read; + //Save to EEPROM + EEPROM.write(eeprom_tempFormat, tempFormat); + } + //Send invalid + else + { + Serial.write(CMD_INVALID); + return; + } + + //Send ACK + Serial.write(CMD_SET_TEMPFORMAT); +} + +/* Set the show spot information */ +void setShowSpot() +{ + //If not enough data available, leave + if (Serial.available() < 1) + { + Serial.write(CMD_INVALID); + return; + } + + //Read byte from serial port + byte read = Serial.read(); + + //Check if it has a valid number + if ((read >= 0) && (read <= 1)) + { + //Set show spot to input + spotEnabled = read; + //Save to EEPROM + EEPROM.write(eeprom_spotEnabled, spotEnabled); + } + //Send invalid + else + { + Serial.write(CMD_INVALID); + return; + } + + //Send ACK + Serial.write(CMD_SET_SHOWSPOT); +} + +/* Set the show colorbar information */ +void setShowColorbar() +{ + //If not enough data available, leave + if (Serial.available() < 1) + { + Serial.write(CMD_INVALID); + return; + } + + //Read byte from serial port + byte read = Serial.read(); + + //Check if it has a valid number + if ((read >= 0) && (read <= 1)) + { + //Set show colorbar to input + colorbarEnabled = read; + //Save to EEPROM + EEPROM.write(eeprom_colorbarEnabled, colorbarEnabled); + } + //Send invalid + else + { + Serial.write(CMD_INVALID); + return; + } + + //Send ACK + Serial.write(CMD_SET_SHOWCOLORBAR); +} + +/* Set the show colorbar information */ +void setMinMax() +{ + //If not enough data available, leave + if (Serial.available() < 1) + { + Serial.write(CMD_INVALID); + return; + } + + //Read byte from serial port + byte read = Serial.read(); + + //Check if it has a valid number + if ((read >= minMaxPoints_disabled) && (read <= minMaxPoints_both)) + { + //Set show colorbar to input + minMaxPoints = read; + //Save to EEPROM + EEPROM.write(eeprom_minMaxPoints, minMaxPoints); + } + //Send invalid + else + { + Serial.write(CMD_INVALID); + return; + } + + //Send ACK + Serial.write(CMD_SET_SHOWMINMAX); +} + +/* Set the shutter mode */ +void setShutterMode() +{ + //If not enough data available, leave + if (Serial.available() < 1) + { + Serial.write(CMD_INVALID); + return; + } + + //Read byte from serial port + byte read = Serial.read(); + + //Check if it has a valid number + if ((read >= 0) && (read <= 1)) + //Set lepton shutter mode + lepton_ffcMode(read); + //Send invalid + else + { + Serial.write(CMD_INVALID); + return; + } + + //Send ACK + Serial.write(CMD_SET_SHUTTERMODE); +} + +/* Set the fitler type */ +void setFilterType() +{ + //If not enough data available, leave + if (Serial.available() < 1) + { + Serial.write(CMD_INVALID); + return; + } + + //Read byte from serial port + byte read = Serial.read(); + + //Check if it has a valid number + if ((read >= filterType_none) && (read <= filterType_box)) + { + //Set filter type to input + filterType = read; + //Save to EEPROM + EEPROM.write(eeprom_filterType, filterType); + } + //Send invalid + else + { + Serial.write(CMD_INVALID); + return; + } + + //Send ACK + Serial.write(CMD_SET_FILTERTYPE); +} + +/* Set the rotation */ +void setRotation() +{ + //If not enough data available, leave + if (Serial.available() < 1) + { + Serial.write(CMD_INVALID); + return; + } + + //Read byte from serial port + byte read = Serial.read(); + + //Check if it has a valid number + if ((read >= 0) && (read <= 1)) + { + //Set rotation to input + rotationVert = read; + //Apply to display + setDisplayRotation(); + //Save to EEPROM + EEPROM.write(eeprom_rotationVert, rotationVert); + } + //Send invalid + else + { + Serial.write(CMD_INVALID); + return; + } + + //Send ACK + Serial.write(CMD_SET_ROTATION); +} + +/* Send the hardware version */ +void sendHardwareVersion() +{ + //Send hardware version + Serial.write(teensyVersion); +} + +/* Send the diagnostic information */ +void sendDiagnostic() +{ + //Send the diag byte + Serial.write(diagnostic); +} + +/* Send the HQ Resolution information */ +void sendHQResolution() +{ + //For the DIY-Thermocam V1, send false + if (teensyVersion == teensyVersion_old) + Serial.write(0); + //For the DIY-Thermocam V2, send the hqRes byte + else + Serial.write(hqRes); +} + +/* Set temperature points array */ +void setTempPoints() +{ + //If not enough data available, leave + if (Serial.available() < 384) + { + Serial.write(CMD_INVALID); + return; + } + + //Go through the temp points array + for (byte i = 0; i < 96; i++) { + //Read index + tempPoints[i][0] = (Serial.read() << 8) + Serial.read(); + + //Correct old not_set marker + if (tempPoints[i][0] == 65535) + tempPoints[i][0] = 0; + + //Read value + tempPoints[i][1] = (Serial.read() << 8) + Serial.read(); + } + + //Send ACK + Serial.write(CMD_SET_TEMPPOINTS); +} + +/* Sends a raw or color frame */ +void sendFrame(bool color) { + //Send type of frame response + Serial.write(sendCmd); + Serial.flush(); + + //Send frame + if (sendCmd == FRAME_NORMAL) { + //Clear all serial buffers + Serial.clear(); + //Convert to colors + if (color) { + //Apply low-pass filter + if (filterType == filterType_box) + boxFilter(); + else if (filterType == filterType_gaussian) + gaussianFilter(); + //Convert to RGB565 + convertColors(true); + } + //Send raw data + sendRawData(color); + + //Send limits + sendRawLimits(); + //Send spot temp + sendSpotTemp(); + //Send calibration data + sendCalibrationData(); + } + //Switch back to send frame the next time + else + sendCmd = FRAME_NORMAL; +} + +/* Saves a frame to the internal sd card*/ +void saveFrame() { + //ThermocamV4 or DIY-Thermocam V2 - check SD card + if (!checkSDCard() || (getSDSpace() < 1000)) { + Serial.write(CMD_INVALID); + return; + } + + //Build save filename from the current time & date + createSDName(saveFilename); + + //Capture visual image if enabled + if (visualEnabled && (checkDiagnostic(diag_camera))) + { + //Capture visual frame + camera_capture(); + //Short delay + delay(100); + //Save visual image in full-res + camera_get(camera_save); + } + + //Enable image save marker + imgSave = imgSave_create; + + //Create image and save raw file + createThermalImg(); + + //Save Bitmap image if activated + if (convertEnabled) { + displayInfos(); + saveBuffer(saveFilename); + } + + //Refresh free space + refreshFreeSpace(); + + //Disable image save marker + imgSave = imgSave_disabled; + + //Send ACK + Serial.write(CMD_FRAME_SAVE); +} + +/* Sends the display content as frame */ +void sendDisplayFrame() { + //Send type of frame response + Serial.write(sendCmd); + Serial.flush(); + + //Send frame + if (sendCmd == FRAME_NORMAL) { + //Find min / max position + if (minMaxPoints != minMaxPoints_disabled) + refreshMinMax(); + + //Apply low-pass filter + if (filterType == filterType_box) + boxFilter(); + else if (filterType == filterType_gaussian) + gaussianFilter(); + + //Teensy 3.6 - Resize to big buffer when HQRes + if ((teensyVersion == teensyVersion_new) && (hqRes)) + smallToBigBuffer(); + + //Convert lepton data to RGB565 colors + convertColors(); + + //Display additional information + imgSave = imgSave_create; + displayInfos(); + imgSave = imgSave_disabled; + + //Send the framebuffer + sendFramebuffer(); + } + + //Switch back to send frame the next time + else + sendCmd = FRAME_NORMAL; +} + +/* Evaluates commands from the serial port*/ +bool serialHandler() { + //Read command from Serial Port + byte recCmd = Serial.read(); + + //Decide what to do + switch (recCmd) { + //Send raw limits + case CMD_GET_RAWLIMITS: + sendRawLimits(); + break; + //Send raw data + case CMD_GET_RAWDATA: + sendRawData(); + break; + //Send config data + case CMD_GET_CONFIGDATA: + sendConfigData(); + break; + //Send the calibration status + case CMD_GET_CALSTATUS: + sendCalStatus(); + break; + //Send calibration data + case CMD_GET_CALIBDATA: + sendCalibrationData(); + break; + //Send spot temp + case CMD_GET_SPOTTEMP: + sendSpotTemp(); + break; + //Change time + case CMD_SET_TIME: + setTime(); + break; + //Send temperature points + case CMD_GET_TEMPPOINTS: + sendTempPoints(); + break; + //Toggle laser + case CMD_SET_LASER: + toggleLaser(); + //Send ACK + Serial.write(CMD_SET_LASER); + break; + //Send laser state + case CMD_GET_LASER: + sendLaserState(); + break; + //Run the shutter + case CMD_SET_SHUTTERRUN: + lepton_ffc(); + //Send ACK + Serial.write(CMD_SET_SHUTTERRUN); + break; + //Set shutter mode + case CMD_SET_SHUTTERMODE: + setShutterMode(); + break; + //Set the filter type + case CMD_SET_FILTERTYPE: + setFilterType(); + break; + //Get the shutter mode + case CMD_GET_SHUTTERMODE: + sendShutterMode(); + break; + //Send battery status + case CMD_GET_BATTERYSTATUS: + sendBatteryStatus(); + break; + //Set calibration offset + case CMD_SET_CALOFFSET: + setCalOffset(); + break; + //Set calibration slope + case CMD_SET_CALSLOPE: + setCalSlope(); + break; + //Send visual image + case CMD_GET_VISUALIMG: + sendVisualImg(); + break; + //Send firmware version + case CMD_GET_FWVERSION: + sendFWVersion(); + break; + //Set limits + case CMD_SET_LIMITS: + setLimits(); + break; + //Set limits to locked + case CMD_SET_TEXTCOLOR: + setTextColor(); + break; + //Change colorscheme + case CMD_SET_COLORSCHEME: + setColorScheme(); + break; + //Set temperature format + case CMD_SET_TEMPFORMAT: + setTempFormat(); + break; + //Set show spot temp + case CMD_SET_SHOWSPOT: + setShowSpot(); + break; + //Set show color bar + case CMD_SET_SHOWCOLORBAR: + setShowColorbar(); + break; + //Set show min max + case CMD_SET_SHOWMINMAX: + setMinMax(); + break; + //Set temperature points + case CMD_SET_TEMPPOINTS: + setTempPoints(); + break; + //Get hardware version + case CMD_GET_HWVERSION: + sendHardwareVersion(); + break; + //Set rotation + case CMD_SET_ROTATION: + setRotation(); + break; + //Run calibration + case CMD_SET_CALIBRATION: + calibrationProcess(true, false); + break; + //Get diagnostic information + case CMD_GET_DIAGNOSTIC: + sendDiagnostic(); + break; + //Get HQ resolution information + case CMD_GET_HQRESOLUTION: + sendHQResolution(); + break; + //Send raw frame + case CMD_FRAME_RAW: + sendFrame(false); + break; + //Send color frame + case CMD_FRAME_COLOR: + sendFrame(true); + break; + //Send display frame + case CMD_FRAME_DISPLAY: + sendDisplayFrame(); + break; + //Save display frame + case CMD_FRAME_SAVE: + saveFrame(); + break; + //End connection + case CMD_END: + return true; + //Start connection, send ACK + case CMD_START: + Serial.write(CMD_START); + break; + //Invalid command + default: + Serial.write(CMD_INVALID); + break; + } + Serial.flush(); + return false; +} + +/* Evaluate button presses */ +void buttonHandler() { + //Count the time to choose selection + long startTime = millis(); + delay(10); + long endTime = millis() - startTime; + + //As long as the button is pressed + while (extButtonPressed() && (endTime <= 1000)) + endTime = millis() - startTime; + + //Short press - request to save a thermal image + if (endTime < 1000) { + sendCmd = FRAME_CAPTURE_THERMAL; + } + + //Long press - request to start or stop a video + else { + sendCmd = FRAME_CAPTURE_VIDEO; + //Wait until button release + while (extButtonPressed()); + } +} + +/* Evaluate touch presses */ +bool touchHandler() +{ + //Count the time to choose selection + long startTime = millis(); + delay(10); + long endTime = millis() - startTime; + + //Wait for touch release, but not longer than a second + if (touch_capacitive) { + while ((touch_touched()) && (endTime <= 1000)) + endTime = millis() - startTime; + } + else { + while ((!digitalRead(pin_touch_irq)) && (endTime <= 1000)) + endTime = millis() - startTime; + } + endTime = millis() - startTime; + + //Short press - take visual image + if (endTime < 1000) + { + sendCmd = FRAME_CAPTURE_VISUAL; + return false; + } + + //Long press + return true; +} + + +/* Check for serial connection */ +void checkSerial() { + //If start command received + if ((Serial.available() > 0) && (Serial.read() == CMD_START)) { + serialMode = true; + serialConnect(); + serialMode = false; + } + + //Another command received, discard it + else if ((Serial.available() > 0)) + Serial.read(); +} + +/* Check for updater requests */ +void checkForUpdater() +{ + //We received something + if (Serial.available() > 0) { + //Read command from Serial Port + byte recCmd = Serial.read(); + //Decide what to do + switch (recCmd) { + //Send firmware version + case CMD_GET_FWVERSION: + sendFWVersion(); + break; + //Get hardware version + case CMD_GET_HWVERSION: + sendHardwareVersion(); + break; + //Get diagnostic information + case CMD_GET_DIAGNOSTIC: + sendDiagnostic(); + break; + //Get HQ resolution information + case CMD_GET_HQRESOLUTION: + sendHQResolution(); + break; + //Send the calibration status + case CMD_GET_CALSTATUS: + sendCalStatus(); + break; + //Send battery status + case CMD_GET_BATTERYSTATUS: + sendBatteryStatus(); + break; + + //Start connection, send ACK + case CMD_START: + Serial.write(CMD_START); + break; + } + Serial.flush(); + } +} + +/* Go into video output mode and wait for connected module */ +void serialOutput() { + //Send the frames + while (true) { + + //Abort transmission when touched long or save visual when short + if (touch_touched() && checkDiagnostic(diag_touch)) + if (touchHandler()) + break; + + //Check warmup status + checkWarmup(); + + //Get the temps + if (checkDiagnostic(diag_lep_data)) + lepton_getRawValues(); + + //Get the spot temperature + getSpotTemp(); + + //Compensate calibration with MLX90614 for non-radiometric Lepton + if ((leptonVersion != leptonVersion_2_5_shutter) && (leptonVersion != leptonVersion_3_5_shutter)) + compensateCalib(); + + //Refresh the temp points + refreshTempPoints(); + + //Find min and max if not in manual mode and limits not locked + if ((autoMode) && (!limitsLocked)) + limitValues(); + + //Check button press if not in terminal mode + if (extButtonPressed()) + buttonHandler(); + + //Check for serial commands + if (Serial.available() > 0) { + //Check for exit + if (serialHandler()) + break; + } + } +} + +/* Method to init some basic values in case no display is used */ +void serialInit() +{ + //Read all settings from EEPROM + readEEPROM(); + + //Select color scheme + selectColorScheme(); + + //Clear show temp array + clearTempPoints(); + + //Receive and send commands over serial port + while (true) + serialOutput(); +} + +/* Tries to establish a connection to a thermal viewer or video output module*/ +void serialConnect() { + //Show message + showFullMessage((char*)"Serial connection detected"); + display_print((char*) "Touch screen long to return", CENTER, 170); + delay(1000); + + //Disable screen backlight + disableScreenLight(); + + //Turn laser off if enabled + if (laserEnabled) + toggleLaser(); + + //Set visual camera resolution to save + camera_setSaveRes(); + + //Send ACK for Start + Serial.write(CMD_START); + + //Go to the serial output + serialOutput(); + + //Send ACK for End + Serial.write(CMD_END); + + //Re-Enable display backlight + enableScreenLight(); + + //Show message + showFullMessage((char*)"Connection ended, return.."); + delay(1000); + + //Clear all serial buffers + Serial.clear(); + + //Change camera resolution back + if (displayMode == displayMode_thermal) + camera_setSaveRes(); + else + camera_setDisplayRes(); + + //Turn laser off if enabled + if (laserEnabled) + toggleLaser(); +} diff --git a/Firmware_V2/src/hardware/connection.h b/Firmware_V2/src/hardware/connection.h new file mode 100644 index 0000000..46c75c8 --- /dev/null +++ b/Firmware_V2/src/hardware/connection.h @@ -0,0 +1,118 @@ +/* +* +* CONNECTION - Communication protocol for the USB serial data transmission +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef CONNECTION_H +#define CONNECTION_H + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +//Start & Stop command +#define CMD_START 100 +#define CMD_END 200 +#define CMD_INVALID 0 + +//Serial terminal commands +#define CMD_GET_RAWLIMITS 110 +#define CMD_GET_RAWDATA 111 +#define CMD_GET_CONFIGDATA 112 +#define CMD_GET_CALSTATUS 113 +#define CMD_GET_CALIBDATA 114 +#define CMD_GET_SPOTTEMP 115 +#define CMD_SET_TIME 116 +#define CMD_GET_TEMPPOINTS 117 +#define CMD_SET_LASER 118 +#define CMD_GET_LASER 119 +#define CMD_SET_SHUTTERRUN 120 +#define CMD_SET_SHUTTERMODE 121 +#define CMD_SET_FILTERTYPE 122 +#define CMD_GET_SHUTTERMODE 123 +#define CMD_GET_BATTERYSTATUS 124 +#define CMD_SET_CALSLOPE 125 +#define CMD_SET_CALOFFSET 126 +#define CMD_GET_DIAGNOSTIC 127 +#define CMD_GET_VISUALIMG 128 +#define CMD_GET_FWVERSION 129 +#define CMD_SET_LIMITS 130 +#define CMD_SET_TEXTCOLOR 131 +#define CMD_SET_COLORSCHEME 132 +#define CMD_SET_TEMPFORMAT 133 +#define CMD_SET_SHOWSPOT 134 +#define CMD_SET_SHOWCOLORBAR 135 +#define CMD_SET_SHOWMINMAX 136 +#define CMD_SET_TEMPPOINTS 137 +#define CMD_GET_HWVERSION 138 +#define CMD_SET_ROTATION 139 +#define CMD_SET_CALIBRATION 140 +#define CMD_GET_HQRESOLUTION 141 + +//Serial frame commands +#define CMD_FRAME_RAW 150 +#define CMD_FRAME_COLOR 151 +#define CMD_FRAME_DISPLAY 152 +#define CMD_FRAME_SAVE 153 + +//Types of raw frame responses +#define FRAME_CAPTURE_THERMAL 180 +#define FRAME_CAPTURE_VISUAL 181 +#define FRAME_CAPTURE_VIDEO 182 +#define FRAME_NORMAL 183 + +/*########################## PUBLIC PROCEDURES ################################*/ + +void buttonHandler(); +void checkForUpdater(); +bool checkNoDisplay(); +void checkSerial(); +int getInt(String text); +void saveFrame(); +void sendBatteryStatus(); +void sendCalibrationData(); +void sendCalStatus(); +void sendConfigData(); +void sendDiagnostic(); +void sendDisplayFrame(); +void sendFramebuffer(); +void sendFrame(bool color); +void sendFWVersion(); +void sendHardwareVersion(); +void sendHQResolution(); +void sendLaserState(); +void sendRawData(bool color = false); +void sendRawLimits(); +void sendShutterMode(); +void sendSpotTemp(); +void sendTempPoints(); +void sendVisualImg(); +void serialConnect(); +bool serialHandler(); +void serialInit(); +void serialOutput(); +void setCalOffset(); +void setCalSlope(); +void setColorScheme(); +void setFilterType(); +void setLimits(); +void setMinMax(); +void setRotation(); +void setShowColorbar(); +void setShowSpot(); +void setShutterMode(); +void setTempFormat(); +void setTempPoints(); +void setTextColor(); +void setTime(); +bool touchHandler(); + +#endif /* CONNECTION_H */ diff --git a/Firmware_V2/src/hardware/display/display.cpp b/Firmware_V2/src/hardware/display/display.cpp new file mode 100644 index 0000000..344a160 --- /dev/null +++ b/Firmware_V2/src/hardware/display/display.cpp @@ -0,0 +1,1605 @@ +/* +* +* Display - ILI9341 SPI Display Module +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include + +/*################# DATA TYPES, CONSTANTS & MACRO DEFINITIONS #################*/ + +#define SPICLOCK 30000000 + +#define LED 22 +#define CS 21 +#define DC 6 + +#define ILI9341_TFTWIDTH 240 +#define ILI9341_TFTHEIGHT 320 + +#define ILI9341_NOP 0x00 +#define ILI9341_SWRESET 0x01 +#define ILI9341_RDDID 0x04 +#define ILI9341_RDDST 0x09 + +#define ILI9341_SLPIN 0x10 +#define ILI9341_SLPOUT 0x11 +#define ILI9341_PTLON 0x12 +#define ILI9341_NORON 0x13 + +#define ILI9341_RDMODE 0x0A +#define ILI9341_RDMADCTL 0x0B +#define ILI9341_RDPIXFMT 0x0C +#define ILI9341_RDIMGFMT 0x0A +#define ILI9341_RDSELFDIAG 0x0F + +#define ILI9341_INVOFF 0x20 +#define ILI9341_INVON 0x21 +#define ILI9341_GAMMASET 0x26 +#define ILI9341_DISPOFF 0x28 +#define ILI9341_DISPON 0x29 + +#define ILI9341_CASET 0x2A +#define ILI9341_PASET 0x2B +#define ILI9341_RAMWR 0x2C +#define ILI9341_RAMRD 0x2E + +#define ILI9341_PTLAR 0x30 +#define ILI9341_MADCTL 0x36 +#define ILI9341_PIXFMT 0x3A + +#define ILI9341_FRMCTR1 0xB1 +#define ILI9341_FRMCTR2 0xB2 +#define ILI9341_FRMCTR3 0xB3 +#define ILI9341_INVCTR 0xB4 +#define ILI9341_DFUNCTR 0xB6 + +#define ILI9341_PWCTR1 0xC0 +#define ILI9341_PWCTR2 0xC1 +#define ILI9341_PWCTR3 0xC2 +#define ILI9341_PWCTR4 0xC3 +#define ILI9341_PWCTR5 0xC4 +#define ILI9341_VMCTR1 0xC5 +#define ILI9341_VMCTR2 0xC7 + +#define ILI9341_RDID1 0xDA +#define ILI9341_RDID2 0xDB +#define ILI9341_RDID3 0xDC +#define ILI9341_RDID4 0xDD + +#define ILI9341_GMCTRP1 0xE0 +#define ILI9341_GMCTRN1 0xE1 + +#define MADCTL_MY 0x80 +#define MADCTL_MX 0x40 +#define MADCTL_MV 0x20 +#define MADCTL_ML 0x10 +#define MADCTL_RGB 0x00 +#define MADCTL_BGR 0x08 +#define MADCTL_MH 0x04 + +#define swap(type, i, j) {type t = i; i = j; j = t;} +#define fontbyte(x) cfont.font[x] + +struct current_font +{ + uint8_t* font; + uint8_t x_size; + uint8_t y_size; + uint8_t offset; + uint8_t numchars; +}; + +/*######################### STATIC DATA DECLARATIONS ##########################*/ + +static uint8_t pcs_data, pcs_command, rotation; +static current_font cfont; +static boolean transparent; +static byte fch, fcl, bch, bcl, orient; +static uint16_t imageX, imageY; + +static const uint8_t init_commands[] = { + 4, 0xEF, 0x03, 0x80, 0x02, + 4, 0xCF, 0x00, 0XC1, 0X30, + 5, 0xED, 0x64, 0x03, 0X12, 0X81, + 4, 0xE8, 0x85, 0x00, 0x78, + 6, 0xCB, 0x39, 0x2C, 0x00, 0x34, 0x02, + 2, 0xF7, 0x20, + 3, 0xEA, 0x00, 0x00, + 2, ILI9341_PWCTR1, 0x23, // Power control + 2, ILI9341_PWCTR2, 0x10, // Power control + 3, ILI9341_VMCTR1, 0x3e, 0x28, // VCM control + 2, ILI9341_VMCTR2, 0x86, // VCM control2 + 2, ILI9341_MADCTL, 0x48, // Memory Access Control + 2, ILI9341_PIXFMT, 0x55, + 3, ILI9341_FRMCTR1, 0x00, 0x18, + 4, ILI9341_DFUNCTR, 0x08, 0x82, 0x27, // Display Function Control + 2, 0xF2, 0x00, // Gamma Function Disable + 2, ILI9341_GAMMASET, 0x01, // Gamma curve selected + 16, ILI9341_GMCTRP1, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, + 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00, // Set Gamma + 16, ILI9341_GMCTRN1, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, + 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F, // Set Gamma + 3, 0xb1, 0x00, 0x10, // FrameRate Control 119Hz + 0 +}; + +/*############################# PUBLIC VARIABLES ##############################*/ + +boolean display_writeToImage; + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +void display_waitFifoNotFull() +{ + uint32_t sr; + do { + sr = KINETISK_SPI0.SR; + if (sr & 0xF0) KINETISK_SPI0.POPR; + } while ((sr & (15 << 12)) > (3 << 12)); +} + +void display_waitFifoEmpty() +{ + uint32_t sr; + do { + sr = KINETISK_SPI0.SR; + if (sr & 0xF0) KINETISK_SPI0.POPR; + } while ((sr & 0xF0F0) > 0); +} + +void display_waitTransmitComplete() +{ + while (!(KINETISK_SPI0.SR & SPI_SR_TCF)); + KINETISK_SPI0.POPR; +} + +void display_waitTransmitComplete(uint32_t mcr) +{ + while (1) { + uint32_t sr = KINETISK_SPI0.SR; + if (sr & SPI_SR_EOQF) break; + if (sr & 0xF0) KINETISK_SPI0.POPR; + } + KINETISK_SPI0.SR = SPI_SR_EOQF; + SPI0_MCR = mcr; + while (KINETISK_SPI0.SR & 0xF0) { + KINETISK_SPI0.POPR; + } +} + +void display_writecommand_cont(uint8_t c) +{ + KINETISK_SPI0.PUSHR = c | (pcs_command << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT; + display_waitFifoNotFull(); +} + +void display_writedata8_cont(uint8_t c) +{ + KINETISK_SPI0.PUSHR = c | (pcs_data << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT; + display_waitFifoNotFull(); +} + +void display_writedata16_cont(uint16_t d) +{ + KINETISK_SPI0.PUSHR = d | (pcs_data << 16) | SPI_PUSHR_CTAS(1) | SPI_PUSHR_CONT; + display_waitFifoNotFull(); +} + +void display_writecommand_last(uint8_t c) +{ + uint32_t mcr = SPI0_MCR; + KINETISK_SPI0.PUSHR = c | (pcs_command << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_EOQ; + display_waitTransmitComplete(mcr); +} + +void display_writedata8_last(uint8_t c) +{ + uint32_t mcr = SPI0_MCR; + KINETISK_SPI0.PUSHR = c | (pcs_data << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_EOQ; + display_waitTransmitComplete(mcr); +} + +void display_writedata16_last(uint16_t d) +{ + uint32_t mcr = SPI0_MCR; + KINETISK_SPI0.PUSHR = d | (pcs_data << 16) | SPI_PUSHR_CTAS(1) | SPI_PUSHR_EOQ; + display_waitTransmitComplete(mcr); +} + +void display_setAddr(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) +{ + display_writecommand_cont(ILI9341_CASET); + display_writedata16_cont(x0); + display_writedata16_cont(x1); + display_writecommand_cont(ILI9341_PASET); + display_writedata16_cont(y0); + display_writedata16_cont(y1); +} + +/* Read 8-bit command from the screen */ +uint8_t display_readcommand8(uint8_t c, uint8_t index) +{ + uint16_t wTimeout = 0xffff; + uint8_t r = 0; + + SPI.beginTransaction(SPISettings(SPICLOCK, MSBFIRST, SPI_MODE0)); + while (((KINETISK_SPI0.SR) & (15 << 12)) && (--wTimeout)); + + KINETISK_SPI0.SR = SPI_SR_TCF; + wTimeout = 0xffff; + while (!((KINETISK_SPI0.SR) & SPI_SR_TCF) && (--wTimeout)); + + wTimeout = 0x10; + while ((((KINETISK_SPI0.SR) >> 4) & 0xf) && (--wTimeout)) { + r = KINETISK_SPI0.POPR; + } + + KINETISK_SPI0.PUSHR = 0xD9 | (pcs_command << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT; + KINETISK_SPI0.PUSHR = (0x10 + index) | (pcs_data << 16) | SPI_PUSHR_CTAS(0); + KINETISK_SPI0.PUSHR = c | (pcs_command << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT; + + KINETISK_SPI0.PUSHR = 0 | (pcs_data << 16) | SPI_PUSHR_CTAS(0); + + wTimeout = 0xffff; + while (((KINETISK_SPI0.SR) & (15 << 12)) && (--wTimeout)); + + KINETISK_SPI0.SR = SPI_SR_TCF; + wTimeout = 0xffff; + while (!((KINETISK_SPI0.SR) & SPI_SR_TCF) && (--wTimeout)); + + wTimeout = 0x10; + while ((((KINETISK_SPI0.SR) >> 4) & 0xf) && (--wTimeout)) { + r = KINETISK_SPI0.POPR; + } + + SPI.endTransaction(); + return r; +} + +/* Set display rotation */ +void display_setRotation(uint8_t m) +{ + SPI.beginTransaction(SPISettings(SPICLOCK, MSBFIRST, SPI_MODE0)); + display_writecommand_cont(ILI9341_MADCTL); + rotation = m % 4; + switch (rotation) { + case 0: + display_writedata8_last(MADCTL_MX | MADCTL_BGR); + orient = LANDSCAPE; + break; + case 1: + display_writedata8_last(MADCTL_MV | MADCTL_BGR); + orient = PORTRAIT; + break; + case 2: + display_writedata8_last(MADCTL_MY | MADCTL_BGR); + orient = LANDSCAPE; + break; + case 3: + display_writedata8_last(MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR); + orient = PORTRAIT; + break; + } + SPI.endTransaction(); +} + +/* Init the hardware LCD */ +byte display_InitLCD() +{ + //Set CS pin + if (SPI.pinIsChipSelect(CS, DC)) { + pcs_data = SPI.setCS(CS); + pcs_command = pcs_data | SPI.setCS(DC); + } + else { + pcs_data = 0; + pcs_command = 0; + return 0; + } + + //Read the self-diagnostic flag + byte diag = display_readcommand8(ILI9341_RDSELFDIAG); + + //Send the init commands + SPI.beginTransaction(SPISettings(SPICLOCK, MSBFIRST, SPI_MODE0)); + const uint8_t *addr = init_commands; + while (1) { + uint8_t count = *addr++; + if (count-- == 0) + break; + display_writecommand_cont(*addr++); + while (count-- > 0) { + display_writedata8_cont(*addr++); + } + } + display_writecommand_last(ILI9341_SLPOUT); + SPI.endTransaction(); + + //Wait a short time + delay(120); + + //Turn the display on + SPI.beginTransaction(SPISettings(SPICLOCK, MSBFIRST, SPI_MODE0)); + display_writecommand_last(ILI9341_DISPON); + SPI.endTransaction(); + + //Init font & transparency + cfont.font = 0; + transparent = 0; + + //Set the display rotation + display_setRotation(45); + + //Disable write to image + display_writeToImage = 0; + + //Return the diagnostic info + return diag; +} + +/* Init the display module */ +void display_init() +{ + byte count = 0; + //Init the display + byte check = display_InitLCD(); + + //Status not okay, try again 10 times + while ((check != 0xE0) && (count < 10)) { + delay(10); + check = display_InitLCD(); + count++; + } + //If it failed after 10 attemps, show diag + if (check != 0xE0) + setDiagnostic(diag_display); + + //Read 180° rotation + byte read = EEPROM.read(eeprom_rotationVert); + if ((read == 0) || (read == 1)) + rotationVert = read; + else + rotationVert = 0; +} + +/* Set the xy coordinates */ +void display_setXY(word x1, word y1, word x2, word y2) +{ + if (orient == LANDSCAPE) { + swap(word, x1, y1); + swap(word, x2, y2); + y1 = 239 - y1; + y2 = 239 - y2; + swap(word, y1, y2); + } + + //Write to the display + if (!display_writeToImage) { + SPI.beginTransaction(SPISettings(SPICLOCK, MSBFIRST, SPI_MODE0)); + display_setAddr(x1, y1, x2, y2); + display_writecommand_last(ILI9341_RAMWR); // write to RAM + SPI.endTransaction(); + } + //Write to the image buffer + else { + imageX = x1; + imageY = y1; + } +} + +/* Clear the xy coordinates */ +void display_clrXY() +{ + if (orient == PORTRAIT) + display_setXY(0, 0, 319, 239); + else + display_setXY(0, 0, 239, 319); +} + +/* Clear the screen */ +void display_clrScr() +{ + display_setXY(0, 0, 239, 319); +} + + +/* Draw a pixel */ +void display_drawPixel(int x, int y) +{ + //Out of borders, return + if ((x < 0) || (x >= 320) || (y < 0) || (y >= 240)) + return; + //Send pixel coordinates and color to screen + SPI.beginTransaction(SPISettings(SPICLOCK, MSBFIRST, SPI_MODE0)); + display_setAddr(x, y, x, y); + display_writecommand_cont(ILI9341_RAMWR); + display_writedata16_last(fch << 8 | fcl); + SPI.endTransaction(); +} + +/* Draw a horizontal line */ +void display_drawHLine(int x, int y, int l) +{ + //Clipping + if ((x >= 320) || (y >= 240)) + return; + if ((x + l - 1) >= 320) + l = 320 - x; + + SPI.beginTransaction(SPISettings(SPICLOCK, MSBFIRST, SPI_MODE0)); + display_setAddr(x, y, x + l - 1, y); + display_writecommand_cont(ILI9341_RAMWR); + word color = (fch << 8 | fcl); + while (l-- > 1) { + display_writedata16_cont(color); + } + display_writedata16_last(color); + SPI.endTransaction(); +} + +/* Draw a vertical line */ +void display_drawVLine(int x, int y, int l) +{ + //Clipping + if ((x >= 320) || (y >= 240)) + return; + if ((y + l - 1) >= 240) + l = 240 - y; + + SPI.beginTransaction(SPISettings(SPICLOCK, MSBFIRST, SPI_MODE0)); + display_setAddr(x, y, x, y + l - 1); + display_writecommand_cont(ILI9341_RAMWR); + word color = (fch << 8 | fcl); + while (l-- > 1) { + display_writedata16_cont(color); + } + display_writedata16_last(color); + SPI.endTransaction(); +} + +/* Set a specific pixel in that color */ +void display_setPixel(word color) +{ + uint32_t pos; + + //Write to display + if (!display_writeToImage) { + SPI.beginTransaction(SPISettings(SPICLOCK, MSBFIRST, SPI_MODE0)); + display_writedata16_last(color); + SPI.endTransaction(); + } + //Write to buffer directly + else + { + //320x240 for Teensy 3.6 + if ((teensyVersion == teensyVersion_new) && hqRes) + { + pos = ((imageY) * 320) + imageX; + if(pos < 76800) + bigBuffer[pos] = color; + } + + //160x120 for Teensy 3.1 / 3.2 + else + { + pos = ((imageY) * 160) + imageX; + if(pos < 19200) + smallBuffer[pos] = color; + } + } +} + +/* Write the data to the LCD */ +void display_LCD_Write_DATA(char VH, char VL) +{ + display_setPixel((VH << 8) | VL); +} + +/* Draw a line */ +void display_drawLine(int x1, int y1, int x2, int y2) +{ + //For buffer display on Teensy 3.1 / 3.2, half coordinates + if ((display_writeToImage) && ((teensyVersion == teensyVersion_old) || (!hqRes))) { + x1 = x1 / 2; + y1 = y1 / 2; + x2 = x2 / 2; + y2 = y2 / 2; + } + + //Write to screen + if ((y1 == y2) && (!display_writeToImage)) + display_drawHLine(x1, y1, x2 - x1); + else if ((x1 == x2) && (!display_writeToImage)) + display_drawVLine(x1, y1, y2 - y1); + //Write to image buffer directly + else { + unsigned int dx = (x2 > x1 ? x2 - x1 : x1 - x2); + short xstep = x2 > x1 ? 1 : -1; + unsigned int dy = (y2 > y1 ? y2 - y1 : y1 - y2); + short ystep = y2 > y1 ? 1 : -1; + int col = x1, row = y1; + + if (dx < dy) { + int t = -(dy >> 1); + while (1) { + display_setXY(col, row, col, row); + display_LCD_Write_DATA(fch, fcl); + if (row == y2) + return; + row += ystep; + t += dx; + if (t >= 0) { + col += xstep; + t -= dy; + } + } + } + else { + int t = -(dx >> 1); + while (1) { + display_setXY(col, row, col, row); + display_LCD_Write_DATA(fch, fcl); + if (col == x2) + return; + col += xstep; + t += dy; + if (t >= 0) { + row += ystep; + t -= dx; + } + } + } + } + display_clrXY(); +} + +/* Fill the screen by RGB565 color */ +void display_fillScr(word color) +{ + int x = 0; + int y = 0; + SPI.beginTransaction(SPISettings(SPICLOCK, MSBFIRST, SPI_MODE0)); + display_setAddr(x, y, x + 319, y + 239); + display_writecommand_cont(ILI9341_RAMWR); + for (y = 240; y > 0; y--) { + for (x = 320; x > 1; x--) { + display_writedata16_cont(color); + } + display_writedata16_last(color); + } + SPI.endTransaction(); +} + +/* Fill the screen by separate RGB value */ +void display_fillScr(byte r, byte g, byte b) +{ + word color = ((r & 248) << 8 | (g & 252) << 3 | (b & 248) >> 3); + display_fillScr(color); +} + +/* Draw an empty rectangle */ +void display_drawRect(int x1, int y1, int x2, int y2) +{ + if (x1 > x2) { + swap(int, x1, x2); + } + if (y1 > y2) { + swap(int, y1, y2); + } + + display_drawHLine(x1, y1, x2 - x1); + display_drawHLine(x1, y2, x2 - x1); + display_drawVLine(x1, y1, y2 - y1); + display_drawVLine(x2, y1, y2 - y1); +} + +/* Fill a rectangle */ +void display_fillRect(int x1, int y1, int x2, int y2) +{ + if (x1 > x2) { + swap(int, x1, x2); + } + if (y1 > y2) { + swap(int, y1, y2); + } + + int w = x2 - x1; + int h = y2 - y1; + + //Clipping + if ((x1 >= 320) || (y1 >= 240)) + return; + if ((x1 + w - 1) >= 320) + w = 320 - x1; + if ((y1 + h - 1) >= 240) + h = 240 - y1; + + //Send to display + SPI.beginTransaction(SPISettings(SPICLOCK, MSBFIRST, SPI_MODE0)); + display_setAddr(x1, y1, x1 + w - 1, y1 + h - 1); + display_writecommand_cont(ILI9341_RAMWR); + word color = (fch << 8 | fcl); + for (int y = h; y > 0; y--) { + for (int x = w; x > 1; x--) { + display_writedata16_cont(color); + } + display_writedata16_last(color); + } + SPI.endTransaction(); +} + +/* Draw an empty round rectangle */ +void display_drawRoundRect(int x1, int y1, int x2, int y2) +{ + if (x1 > x2) { + swap(int, x1, x2); + } + if (y1 > y2) { + swap(int, y1, y2); + } + if ((x2 - x1) > 4 && (y2 - y1) > 4) { + display_drawPixel(x1 + 1, y1 + 1); + display_drawPixel(x2 - 1, y1 + 1); + display_drawPixel(x1 + 1, y2 - 1); + display_drawPixel(x2 - 1, y2 - 1); + display_drawHLine(x1 + 2, y1, x2 - x1 - 4); + display_drawHLine(x1 + 2, y2, x2 - x1 - 4); + display_drawVLine(x1, y1 + 2, y2 - y1 - 4); + display_drawVLine(x2, y1 + 2, y2 - y1 - 4); + } +} + +/* Fill a round rectangle */ +void display_fillRoundRect(int x1, int y1, int x2, int y2) +{ + if (x1 > x2) { + swap(int, x1, x2); + } + if (y1 > y2) { + swap(int, y1, y2); + } + + if ((x2 - x1) > 4 && (y2 - y1) > 4) { + for (int i = 0; i < ((y2 - y1) / 2) + 1; i++) { + switch (i) { + case 0: + display_drawHLine(x1 + 2, y1 + i, x2 - x1 - 4); + display_drawHLine(x1 + 2, y2 - i, x2 - x1 - 4); + break; + case 1: + display_drawHLine(x1 + 1, y1 + i, x2 - x1 - 2); + display_drawHLine(x1 + 1, y2 - i, x2 - x1 - 2); + break; + default: + display_drawHLine(x1, y1 + i, x2 - x1); + display_drawHLine(x1, y2 - i, x2 - x1); + } + } + } +} + +/* Draw an empty circle */ +void display_drawCircle(int x, int y, int radius) +{ + //For buffer display on Teensy 3.1 / 3.2, half coordinates + if ((display_writeToImage) && ((teensyVersion == teensyVersion_old) || (!hqRes))) { + x = x / 2; + y = y / 2; + radius = radius / 2; + } + + int f = 1 - radius; + int ddF_x = 1; + int ddF_y = -2 * radius; + int x1 = 0; + int y1 = radius; + + display_setXY(x, y + radius, x, y + radius); + display_LCD_Write_DATA(fch, fcl); + display_setXY(x, y - radius, x, y - radius); + display_LCD_Write_DATA(fch, fcl); + display_setXY(x + radius, y, x + radius, y); + display_LCD_Write_DATA(fch, fcl); + display_setXY(x - radius, y, x - radius, y); + display_LCD_Write_DATA(fch, fcl); + + while (x1 < y1) { + if (f >= 0) { + y1--; + ddF_y += 2; + f += ddF_y; + } + x1++; + ddF_x += 2; + f += ddF_x; + display_setXY(x + x1, y + y1, x + x1, y + y1); + display_LCD_Write_DATA(fch, fcl); + display_setXY(x - x1, y + y1, x - x1, y + y1); + display_LCD_Write_DATA(fch, fcl); + display_setXY(x + x1, y - y1, x + x1, y - y1); + display_LCD_Write_DATA(fch, fcl); + display_setXY(x - x1, y - y1, x - x1, y - y1); + display_LCD_Write_DATA(fch, fcl); + display_setXY(x + y1, y + x1, x + y1, y + x1); + display_LCD_Write_DATA(fch, fcl); + display_setXY(x - y1, y + x1, x - y1, y + x1); + display_LCD_Write_DATA(fch, fcl); + display_setXY(x + y1, y - x1, x + y1, y - x1); + display_LCD_Write_DATA(fch, fcl); + display_setXY(x - y1, y - x1, x - y1, y - x1); + display_LCD_Write_DATA(fch, fcl); + } + display_clrXY(); +} + +/* Fill a circle */ +void display_fillCircle(int x, int y, int radius) +{ + for (int y1 = -radius; y1 <= 0; y1++) { + for (int x1 = -radius; x1 <= 0; x1++) { + if (x1 * x1 + y1 * y1 <= radius * radius) { + display_drawHLine(x + x1, y + y1, 2 * (-x1)); + display_drawHLine(x + x1, y - y1, 2 * (-x1)); + break; + } + } + } +} + +/* Set color to separate RGB values */ +void display_setColor(byte r, byte g, byte b) +{ + fch = ((r & 248) | g >> 5); + fcl = ((g & 28) << 3 | b >> 3); +} + +/* Set color to RGB565 color */ +void display_setColor(word color) +{ + fch = byte(color >> 8); + fcl = byte(color & 0xFF); +} + +/* Get current RGB565 color */ +word display_getColor() +{ + return (fch << 8) | fcl; +} + +/* Set back color to separate RGB value */ +void display_setBackColor(byte r, byte g, byte b) +{ + bch = ((r & 248) | g >> 5); + bcl = ((g & 28) << 3 | b >> 3); + transparent = 0; +} + +/* Set back color to RGB565 value */ +void display_setBackColor(uint32_t color) +{ + if (color == VGA_TRANSPARENT) + transparent = 1; + else { + bch = byte(color >> 8); + bcl = byte(color & 0xFF); + transparent = 0; + } +} + +/* Get back color as RGB565 value */ +word display_getBackColor() +{ + return (bch << 8) | bcl; +} + +/* Print a specific char */ +void display_printChar(byte c, int x, int y) +{ + byte i, ch; + word j; + word temp; + + //For buffer display on Teensy 3.1 / 3.2, half coordinates + if ((display_writeToImage) && ((teensyVersion == teensyVersion_old) || (!hqRes))) { + x = x / 2; + y = y / 2; + } + + //Not transparent + if (!transparent) { + if (orient == PORTRAIT) { + display_setXY(x, y, x + cfont.x_size - 1, y + cfont.y_size - 1); + + temp = ((c - cfont.offset) * ((cfont.x_size / 8) * cfont.y_size)) + + 4; + for (j = 0; j < ((cfont.x_size / 8) * cfont.y_size); j++) { + ch = pgm_read_byte(&cfont.font[temp]); + for (i = 0; i < 8; i++) { + if ((ch & (1 << (7 - i))) != 0) { + display_setPixel((fch << 8) | fcl); + } + else { + display_setPixel((bch << 8) | bcl); + } + } + temp++; + } + } + else { + temp = ((c - cfont.offset) * ((cfont.x_size / 8) * cfont.y_size)) + + 4; + + for (j = 0; j < ((cfont.x_size / 8) * cfont.y_size); + j += (cfont.x_size / 8)) { + display_setXY(x, y + (j / (cfont.x_size / 8)), x + cfont.x_size - 1, + y + (j / (cfont.x_size / 8))); + for (int zz = (cfont.x_size / 8) - 1; zz >= 0; zz--) { + ch = pgm_read_byte(&cfont.font[temp + zz]); + for (i = 0; i < 8; i++) { + if ((ch & (1 << i)) != 0) { + display_setPixel((fch << 8) | fcl); + } + else { + display_setPixel((bch << 8) | bcl); + } + } + } + temp += (cfont.x_size / 8); + } + } + } + + //Transparent + else { + temp = ((c - cfont.offset) * ((cfont.x_size / 8) * cfont.y_size)) + 4; + for (j = 0; j < cfont.y_size; j++) { + for (int zz = 0; zz < (cfont.x_size / 8); zz++) { + ch = pgm_read_byte(&cfont.font[temp + zz]); + for (i = 0; i < 8; i++) { + display_setXY(x + i + (zz * 8), y + j, x + i + (zz * 8) + 1, + y + j + 1); + + if ((ch & (1 << (7 - i))) != 0) { + display_setPixel((fch << 8) | fcl); + } + } + } + temp += (cfont.x_size / 8); + } + } + + display_clrXY(); +} + +/* Get the font height */ +int display_getFontHeight() +{ + return (cfont.y_size); +} + +/* Return the Glyph data for an individual character in the font*/ +boolean display_getCharPtr(byte c, propFont& fontChar) +{ + byte* tempPtr = cfont.font + 4; // point at data + + do + { + fontChar.charCode = pgm_read_byte(tempPtr++); + fontChar.adjYOffset = pgm_read_byte(tempPtr++); + fontChar.width = pgm_read_byte(tempPtr++); + fontChar.height = pgm_read_byte(tempPtr++); + fontChar.xOffset = pgm_read_byte(tempPtr++); + fontChar.xOffset = fontChar.xOffset < 0x80 ? fontChar.xOffset : (0x100 - fontChar.xOffset); + fontChar.xDelta = pgm_read_byte(tempPtr++); + if (c != fontChar.charCode && fontChar.charCode != 0xFF) + { + if (fontChar.width != 0) + { + // packed bits + tempPtr += (((fontChar.width * fontChar.height) - 1) / 8) + 1; + } + } + } while (c != fontChar.charCode && fontChar.charCode != 0xFF); + + fontChar.dataPtr = tempPtr; + + return (fontChar.charCode != 0xFF); +} + +/* Print a proportional char */ +int display_printProportionalChar(byte c, int x, int y) +{ + byte i, j; + byte ch = 0; + byte *tempPtr; + + propFont fontChar; + if (!display_getCharPtr(c, fontChar)) + { + return 0; + } + + word fcolor = display_getColor(); + if (!transparent) + { + int fontHeight = display_getFontHeight(); + display_setColor(display_getBackColor()); + display_fillRect(x, y, x + fontChar.xDelta + 1, y + fontHeight); + display_setColor(fcolor); + } + + tempPtr = fontChar.dataPtr; + + if (fontChar.width != 0) + { + byte mask = 0x80; + for (j = 0; j < fontChar.height; j++) + { + for (i = 0; i < fontChar.width; i++) + { + if (((i + (j*fontChar.width)) % 8) == 0) + { + mask = 0x80; + ch = pgm_read_byte(tempPtr++); + } + + if ((ch & mask) != 0) + { + display_setXY(x + fontChar.xOffset + i, y + j + fontChar.adjYOffset, + x + fontChar.xOffset + i, y + j + fontChar.adjYOffset); + display_setPixel(fcolor); + } + + mask >>= 1; + } + } + } + return fontChar.xDelta; +} + +/* Rotate a proportional char */ +int display_rotatePropChar(byte c, int x, int y, int offset, int deg) +{ + propFont fontChar; + + if (!display_getCharPtr(c, fontChar)) + { + return 0; + } + + byte ch = 0; + byte *tempPtr = fontChar.dataPtr; + double radian = deg * 0.0175; + + word fcolor = display_getColor(); + + if (fontChar.width != 0) + { + byte mask = 0x80; + float cos_radian = cos(radian); + float sin_radian = sin(radian); + for (int j = 0; j < fontChar.height; j++) + { + for (int i = 0; i < fontChar.width; i++) + { + if (((i + (j*fontChar.width)) % 8) == 0) + { + mask = 0x80; + ch = pgm_read_byte(tempPtr++); + } + + int newX = x + ((offset + i) * cos_radian - (j + fontChar.adjYOffset)*sin_radian); + int newY = y + ((j + fontChar.adjYOffset) * cos_radian + (offset + i) * sin_radian); + if ((ch & mask) != 0) + { + display_setXY(newX, newY, newX, newY); + display_setPixel(fcolor); + } + else + { + if (!transparent) + { + display_setXY(newX, newY, newX, newY); + display_setPixel(display_getBackColor()); + } + } + mask >>= 1; + } + } + } + + display_clrXY(); + + return fontChar.xDelta; +} + +/* Rotate a char on the display */ +void display_rotateChar(byte c, int x, int y, int pos, int deg) +{ + byte i, j, ch; + word temp; + int newx, newy; + double radian; + radian = deg * 0.0175; + + temp = ((c - cfont.offset) * ((cfont.x_size / 8) * cfont.y_size)) + 4; + for (j = 0; j < cfont.y_size; j++) { + for (int zz = 0; zz < (cfont.x_size / 8); zz++) { + ch = pgm_read_byte(&cfont.font[temp + zz]); + for (i = 0; i < 8; i++) { + newx = x + + (((i + (zz * 8) + (pos * cfont.x_size)) * cos(radian)) + - ((j)* sin(radian))); + newy = y + + (((j)* cos(radian)) + + ((i + (zz * 8) + (pos * cfont.x_size)) + * sin(radian))); + + display_setXY(newx, newy, newx + 1, newy + 1); + + if ((ch & (1 << (7 - i))) != 0) { + display_setPixel((fch << 8) | fcl); + } + else { + if (!transparent) + display_setPixel((bch << 8) | bcl); + } + } + } + temp += (cfont.x_size / 8); + } + display_clrXY(); +} + +/* Print char array on the display */ +void display_print(char* st, int x, int y, int deg) +{ + int stl, i; + + stl = strlen(st); + + //For buffer display on Teensy 3.1 / 3.2, half coordinates + if ((display_writeToImage) && ((teensyVersion == teensyVersion_old) || (!hqRes))) { + x = x / 2; + y = y / 2; + } + + if (orient == PORTRAIT) { + if (x == RIGHT) + x = 320 - (stl * cfont.x_size); + if (x == CENTER) + x = (320 - (stl * cfont.x_size)) / 2; + } + else { + if (x == RIGHT) + x = 240 - (stl * cfont.x_size); + if (x == CENTER) + x = (240 - (stl * cfont.x_size)) / 2; + } + + int offset = 0; + for (i = 0; i < stl; i++) + { + if (deg == 0) + { + if (cfont.x_size == 0) + x += display_printProportionalChar(*st++, x, y) + 1; + else + { + display_printChar(*st++, x, y); + x += cfont.x_size; + } + } + else + { + if (cfont.x_size == 0) + offset += display_rotatePropChar(*st++, x, y, offset, deg); + else + display_rotateChar(*st++, x, y, i, deg); + } + } +} + +/* Print a rotated string on the display */ +void display_print(String st, int x, int y, int deg) +{ + char buf[st.length() + 1]; + st.toCharArray(buf, st.length() + 1); + display_print(buf, x, y, deg); +} + +/* Print string on the display */ +void display_printC(String st, int x, int y, uint32_t color) +{ + char buf[st.length() + 1]; + display_setColor(color); + st.toCharArray(buf, st.length() + 1); + display_print(buf, x, y, 0); +} + +/* Print an integer */ +void display_printNumI(long num, int x, int y, int length, char filler) +{ + char buf[25]; + char st[27]; + boolean neg = 0; + int c = 0, f = 0; + + if (num == 0) { + if (length != 0) { + for (c = 0; c < (length - 1); c++) + st[c] = filler; + st[c] = 48; + st[c + 1] = 0; + } + else { + st[0] = 48; + st[1] = 0; + } + } + else { + if (num < 0) { + neg = 1; + num = -num; + } + + while (num > 0) { + buf[c] = 48 + (num % 10); + c++; + num = (num - (num % 10)) / 10; + } + buf[c] = 0; + + if (neg) { + st[0] = 45; + } + + if (length > (c + neg)) { + for (int i = 0; i < (length - c - neg); i++) { + st[i + neg] = filler; + f++; + } + } + + for (int i = 0; i < c; i++) { + st[i + neg + f] = buf[c - i - 1]; + } + st[c + neg + f] = 0; + + } + + display_print(st, x, y); +} + +/* Helper method to convert a float*/ +void display_convertFloat(char* buf, double num, int width, byte prec) +{ + dtostrf(num, width, prec, buf); +} + +/* Print a float */ +void display_printNumF(double num, byte dec, int x, int y, char divider, int length, char filler) +{ + char st[27]; + boolean neg = 0; + + if (dec < 1) + dec = 1; + else if (dec > 5) + dec = 5; + + if (num < 0) + neg = 1; + display_convertFloat(st, num, length, dec); + if (divider != '.') { + for (uint16_t i = 0; i < sizeof(st); i++) + if (st[i] == '.') + st[i] = divider; + } + + if (filler != ' ') { + if (neg) { + st[0] = '-'; + for (uint16_t i = 1; i < sizeof(st); i++) + if ((st[i] == ' ') || (st[i] == '-')) + st[i] = filler; + } + else { + for (uint16_t i = 0; i < sizeof(st); i++) + if (st[i] == ' ') + st[i] = filler; + } + } + + display_print(st, x, y); +} + +/* Set a specific font */ +void display_setFont(const uint8_t* font) +{ + cfont.font = (uint8_t*) font; + cfont.x_size = fontbyte(0); + cfont.y_size = fontbyte(1); + cfont.offset = fontbyte(2); + cfont.numchars = fontbyte(3); +} + +/* Get the current font */ +uint8_t* display_getFont() +{ + return cfont.font; +} + +/* Get the x size of the current font */ +uint8_t display_getFontXsize() +{ + return cfont.x_size; +} + +/* Get the y size of the current font */ +uint8_t display_getFontYsize() +{ + return cfont.y_size; +} + +/* Draw a bitmap on the screen */ +void display_drawBitmap(int x, int y, int sx, int sy, unsigned short* data, int scale) +{ + unsigned int col; + int tx, ty, tsx, tsy, tc; + byte VH, VL; + SPI.beginTransaction(SPISettings(SPICLOCK, MSBFIRST, SPI_MODE0)); + + //Unscaled + if (scale == 1) + { + if (orient == PORTRAIT) + { + + display_setXY(x, y, x + sx - 1, y + sy - 1); + for (tc = 0; tc < (sx*sy); tc++) + { + col = pgm_read_word(&data[tc]); + VH = col >> 8; + VL = col & 0xff; + display_writedata16_last((VH << 8) | VL); + } + display_setAddr(0, 0, 319, 239); + display_writecommand_last(ILI9341_RAMWR); + + } + else + { + for (ty = 0; ty < sy; ty++) + { + display_setXY(x, y + ty, x + sx - 1, y + ty); + for (tx = sx - 1; tx >= 0; tx--) + { + col = pgm_read_word(&data[(ty*sx) + tx]); + VH = col >> 8; + VL = col & 0xff; + display_writedata16_last((VH << 8) | VL); + } + } + display_setAddr(0, 0, 239, 319); + display_writecommand_last(ILI9341_RAMWR); // write to RAM + } + } + + //Scaled + else + { + if (orient == PORTRAIT) + { + for (ty = 0; ty < sy; ty++) + { + display_setAddr(x, y + (ty*scale), x + ((sx*scale) - 1), y + (ty*scale) + scale); + display_writecommand_last(ILI9341_RAMWR); + for (tsy = 0; tsy < scale; tsy++) + for (tx = 0; tx < sx; tx++) + { + col = pgm_read_word(&data[(ty*sx) + tx]); + for (tsx = 0; tsx < scale; tsx++) { + VH = col >> 8; + VL = col & 0xff; + display_writedata16_last((VH << 8) | VL); + } + } + } + display_setAddr(0, 0, 319, 239); + display_writecommand_last(ILI9341_RAMWR); + + } + else + { + for (ty = 0; ty < sy; ty++) + { + for (tsy = 0; tsy < scale; tsy++) + { + display_setAddr(x, y + (ty*scale) + tsy, x + ((sx*scale) - 1), y + (ty*scale) + tsy); + display_writecommand_last(ILI9341_RAMWR); + for (tx = sx - 1; tx >= 0; tx--) + { + col = pgm_read_word(&data[(ty*sx) + tx]); + for (tsx = 0; tsx < scale; tsx++) { + VH = col >> 8; + VL = col & 0xff; + display_writedata16_last((VH << 8) | VL); + } + } + } + } + display_setAddr(0, 0, 239, 319); + display_writecommand_last(ILI9341_RAMWR); + } + } + + SPI.endTransaction(); +} + +/* Print a rotated bitmap */ +void display_drawBitmap(int x, int y, int sx, int sy, unsigned short* data, int deg, int rox, int roy) +{ + unsigned int col; + int tx, ty, newx, newy; + double radian; + radian = deg*0.0175; + + if (deg == 0) + display_drawBitmap(x, y, sx, sy, data); + else + { + for (ty = 0; ty < sy; ty++) + for (tx = 0; tx < sx; tx++) + { + col = pgm_read_word(&data[(ty*sx) + tx]); + + newx = x + rox + (((tx - rox)*cos(radian)) - ((ty - roy)*sin(radian))); + newy = y + roy + (((ty - roy)*cos(radian)) + ((tx - rox)*sin(radian))); + + display_setXY(newx, newy, newx, newy); + display_LCD_Write_DATA(col >> 8, col & 0xff); + } + + } + display_clrXY(); +} + +/* Write a paletted bitmap with 2BPP */ +void display_writeRect2BPP(int16_t x, int16_t y, int16_t w, int16_t h, const uint8_t* pixels, const uint16_t* palette) +{ + SPI.beginTransaction(SPISettings(SPICLOCK, MSBFIRST, SPI_MODE0)); + display_setAddr(x, y, x + w - 1, y + h - 1); + display_writecommand_cont(ILI9341_RAMWR); + for (y = h; y > 0; y--) { + for (x = w; x > 4; x -= 4) { + display_writedata16_cont(palette[((*pixels) >> 6) & 0x3]); + display_writedata16_cont(palette[((*pixels) >> 4) & 0x3]); + display_writedata16_cont(palette[((*pixels) >> 2) & 0x3]); + display_writedata16_cont(palette[(*pixels++) & 0x3]); + } + display_writedata16_cont(palette[((*pixels) >> 6) & 0x3]); + display_writedata16_cont(palette[((*pixels) >> 4) & 0x3]); + display_writedata16_cont(palette[((*pixels) >> 2) & 0x3]); + display_writedata16_last(palette[(*pixels++) & 0x3]); + } + SPI.endTransaction(); +} + +/* Write a paletted bitmap with 4BPP */ +void display_writeRect4BPP(int16_t x, int16_t y, int16_t w, int16_t h, const uint8_t* pixels, const uint16_t* palette) +{ + SPI.beginTransaction(SPISettings(SPICLOCK, MSBFIRST, SPI_MODE0)); + display_setAddr(x, y, x + w - 1, y + h - 1); + display_writecommand_cont(ILI9341_RAMWR); + for (y = h; y > 0; y--) { + for (x = w; x > 2; x -= 2) { + display_writedata16_cont(palette[((*pixels) >> 4) & 0xF]); + display_writedata16_cont(palette[(*pixels++) & 0xF]); + } + display_writedata16_cont(palette[((*pixels) >> 4) & 0xF]); + display_writedata16_last(palette[(*pixels++) & 0xF]); + } + SPI.endTransaction(); +} + +/* Returns the string width in pixels */ +int display_getStringWidth(char* str) +{ + //Fixed font width + if (cfont.x_size != 0) + { + return (strlen(str) * cfont.x_size); + } + + //Calculate the string width + int strWidth = 0; + while (*str != 0) + { + propFont fontChar; + boolean found = display_getCharPtr(*str, fontChar); + + if (found && *str == fontChar.charCode) + { + strWidth += fontChar.xDelta + 1; + } + + str++; + } + + return strWidth; +} + +/* Enter sleep mode */ +void display_enterSleepMode() +{ + SPI.beginTransaction(SPISettings(SPICLOCK, MSBFIRST, SPI_MODE0)); + display_writecommand_last(ILI9341_SLPIN); + SPI.endTransaction(); +} + +/* Exit sleep mode */ +void display_exitSleepMode() +{ + SPI.beginTransaction(SPISettings(SPICLOCK, MSBFIRST, SPI_MODE0)); + display_writecommand_last(ILI9341_SLPOUT); + SPI.endTransaction(); +} + +/* Read Pixel at x,y and get back 16-bit packed color */ +uint16_t display_readPixel(int16_t x, int16_t y) +{ + uint8_t r, g, b; + + SPI.beginTransaction(SPISettings(SPICLOCK, MSBFIRST, SPI_MODE0)); + + display_setAddr(x, y, x, y); + display_writecommand_cont(ILI9341_RAMRD); + display_waitTransmitComplete(); + + KINETISK_SPI0.PUSHR = 0 | (pcs_data << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT; + display_waitFifoEmpty(); + + KINETISK_SPI0.PUSHR = 0 | (pcs_data << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT; + KINETISK_SPI0.PUSHR = 0 | (pcs_data << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT; + KINETISK_SPI0.PUSHR = 0 | (pcs_data << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_EOQ; + + while ((KINETISK_SPI0.SR & SPI_SR_EOQF) == 0); + KINETISK_SPI0.SR = SPI_SR_EOQF; + + KINETISK_SPI0.POPR; + r = KINETISK_SPI0.POPR; + g = KINETISK_SPI0.POPR; + b = KINETISK_SPI0.POPR; + + SPI.endTransaction(); + return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); +} + +/* Write RGB565 data to the screen */ +void display_writeScreen(unsigned short* pcolors, boolean small) +{ + SPI.beginTransaction(SPISettings(SPICLOCK, MSBFIRST, SPI_MODE0)); + display_setAddr(0, 0, 319, 239); + display_writecommand_cont(ILI9341_RAMWR); + + //160x120 array, doubled + if (small) { + for (byte y = 120; y > 0; y--) { + for (byte x = 160; x > 1; x--) { + display_writedata16_cont(*pcolors); + display_writedata16_cont(*pcolors++); + } + display_writedata16_cont(*pcolors); + display_writedata16_last(*pcolors++); + pcolors = pcolors - 160; + for (byte x = 160; x > 1; x--) { + display_writedata16_cont(*pcolors); + display_writedata16_cont(*pcolors++); + } + display_writedata16_cont(*pcolors); + display_writedata16_last(*pcolors++); + } + } + //320x240 array + else + { + uint16_t *colors_end = &pcolors[76799]; + uint16_t *colors_curr = pcolors; + + // Quick write out the data; + while (colors_curr < colors_end) { + display_writedata16_cont(*colors_curr++); + } + display_writedata16_last(*colors_curr); + } + + SPI.endTransaction(); +} + +/* Read multiple pixels from the screen */ +void display_readScreen(byte step, unsigned short* pcolors) +{ + uint8_t r, g, b; + uint16_t c = 19201; + + SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0)); + + display_setAddr(0, (step * 60), 319, (step * 60) + 59); + display_writecommand_cont(ILI9341_RAMRD); + display_waitTransmitComplete(); + KINETISK_SPI0.PUSHR = 0 | (pcs_data << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT | SPI_PUSHR_EOQ; + while ((KINETISK_SPI0.SR & SPI_SR_EOQF) == 0); + KINETISK_SPI0.SR = SPI_SR_EOQF; + while ((KINETISK_SPI0.SR & 0xf0)) { + KINETISK_SPI0.POPR; + } + c *= 3; + while (c--) { + if (c) { + KINETISK_SPI0.PUSHR = 0 | (pcs_data << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT; + } + else { + KINETISK_SPI0.PUSHR = 0 | (pcs_data << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_EOQ; + } + + + if (c == 0) { + while ((KINETISK_SPI0.SR & SPI_SR_EOQF) == 0); + KINETISK_SPI0.SR = SPI_SR_EOQF; + } + + if ((KINETISK_SPI0.SR & 0xf0) >= 0x30) { + r = KINETISK_SPI0.POPR; + g = KINETISK_SPI0.POPR; + b = KINETISK_SPI0.POPR; + *pcolors++ = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); + } + + while ((KINETISK_SPI0.SR & (15 << 12)) > (3 << 12)); + } + SPI.endTransaction(); +} + +void display_HLine(int16_t x, int16_t y, int16_t w, uint16_t color) +{ + display_setAddr(x, y, x + w - 1, y); + display_writecommand_cont(ILI9341_RAMWR); + do { + display_writedata16_cont(color); + } while (--w > 0); +} + +void display_VLine(int16_t x, int16_t y, int16_t h, uint16_t color) +{ + display_setAddr(x, y, x, y + h - 1); + display_writecommand_cont(ILI9341_RAMWR); + do { + display_writedata16_cont(color); + } while (--h > 0); +} + +void display_Pixel(int16_t x, int16_t y, uint16_t color) +{ + display_setAddr(x, y, x, y); + display_writecommand_cont(ILI9341_RAMWR); + display_writedata16_cont(color); +} diff --git a/Firmware_V2/src/hardware/display/display.h b/Firmware_V2/src/hardware/display/display.h new file mode 100644 index 0000000..1c40f46 --- /dev/null +++ b/Firmware_V2/src/hardware/display/display.h @@ -0,0 +1,128 @@ +/* +* +* Display - ILI9341 SPI Display Module +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef DISPLAY_H +#define DISPLAY_H + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +#define LEFT 0 +#define RIGHT 9999 +#define CENTER 9998 + +#define PORTRAIT 0 +#define LANDSCAPE 1 + +#define VGA_BLACK 0x0000 +#define VGA_WHITE 0xFFFF +#define VGA_RED 0xF800 +#define VGA_GREEN 0x0400 +#define VGA_BLUE 0x001F +#define VGA_SILVER 0xC618 +#define VGA_GRAY 0x8410 +#define VGA_MAROON 0x8000 +#define VGA_YELLOW 0xFE40 +#define VGA_OLIVE 0x8400 +#define VGA_LIME 0x07E0 +#define VGA_AQUA 0xBE7F +#define VGA_TEAL 0x0410 +#define VGA_NAVY 0x0010 +#define VGA_FUCHSIA 0xF81F +#define VGA_PURPLE 0x8010 +#define VGA_TRANSPARENT 0xFFFFFFFF + +struct propFont +{ + byte charCode; + int adjYOffset; + int width; + int height; + int xOffset; + int xDelta; + byte* dataPtr; +}; + +extern boolean display_writeToImage; + +/*########################## PUBLIC PROCEDURES ################################*/ + +void display_clrScr(); +void display_clrXY(); +void display_convertFloat(char* buf, double num, int width, byte prec); +void display_drawBitmap(int x, int y, int sx, int sy, unsigned short* data, int scale = 1); +void display_drawCircle(int x, int y, int radius); +void display_drawHLine(int x, int y, int l); +void display_drawLine(int x1, int y1, int x2, int y2); +void display_drawPixel(int x, int y); +void display_drawRect(int x1, int y1, int x2, int y2); +void display_drawRoundRect(int x1, int y1, int x2, int y2); +void display_drawVLine(int x, int y, int l); +void display_enterSleepMode(); +void display_exitSleepMode(); +void display_fillCircle(int x, int y, int radius); +void display_fillRect(int x1, int y1, int x2, int y2); +void display_fillRoundRect(int x1, int y1, int x2, int y2); +void display_fillScr(word color); +void display_fillScr(byte r, byte g, byte b); +word display_getBackColor(); +boolean display_getCharPtr(byte c, propFont& fontChar); +word display_getColor(); +int display_getFontHeight(); +uint8_t* display_getFont(); +uint8_t display_getFontXsize(); +uint8_t display_getFontYsize(); +int display_getStringWidth(char* str); +void display_HLine(int16_t x, int16_t y, int16_t w, uint16_t color); +byte display_InitLCD(); +void display_init(); +void display_LCD_Write_DATA(char VH, char VL); +void display_Pixel(int16_t x, int16_t y, uint16_t color); +void display_printChar(byte c, int x, int y); +void display_printC(String st, int x, int y, uint32_t color = VGA_BLACK); +void display_printNumF(double num, byte dec, int x, int y, char divider = '.', int length = 0, char filler = ' '); +void display_printNumI(long num, int x, int y, int length = 0, char filler = ' '); +int display_printProportionalChar(byte c, int x, int y); +void display_print(char* st, int x, int y, int deg = 0); +void display_print(String st, int x, int y, int deg = 0); +uint8_t display_readcommand8(uint8_t c, uint8_t index = 0); +uint16_t display_readPixel(int16_t x, int16_t y); +void display_readScreen(byte step, unsigned short* pcolors); +void display_rotateChar(byte c, int x, int y, int pos, int deg); +int display_rotatePropChar(byte c, int x, int y, int offset, int deg); +void display_setAddr(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1); +void display_setBackColor(byte r, byte g, byte b); +void display_setBackColor(uint32_t color); +void display_setColor(byte r, byte g, byte b); +void display_setColor(word color); +void display_setFont(const uint8_t* font); +void display_setPixel(word color); +void display_setRotation(uint8_t m); +void display_setXY(word x1, word y1, word x2, word y2); +void display_VLine(int16_t x, int16_t y, int16_t h, uint16_t color); +void display_waitFifoEmpty(); +void display_waitFifoNotFull(); +void display_waitTransmitComplete(); +void display_writecommand_cont(uint8_t c); +void display_writecommand_last(uint8_t c); +void display_writedata16_cont(uint16_t d); +void display_writedata16_last(uint16_t d); +void display_writedata8_cont(uint8_t c); +void display_writedata8_last(uint8_t c); +void display_writeRect2BPP(int16_t x, int16_t y, int16_t w, int16_t h, const uint8_t* pixels, const uint16_t* palette); +void display_writeRect4BPP(int16_t x, int16_t y, int16_t w, int16_t h, const uint8_t* pixels, const uint16_t* palette); +void display_writeScreen(unsigned short* pcolors, boolean small); + + +#endif /* DISPLAY_H */ diff --git a/Firmware_V2/src/hardware/display/fonts.cpp b/Firmware_V2/src/hardware/display/fonts.cpp new file mode 100644 index 0000000..69fded5 --- /dev/null +++ b/Firmware_V2/src/hardware/display/fonts.cpp @@ -0,0 +1,518 @@ +/* +* +* Fonts +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include + +/*############################# PUBLIC VARIABLES ##############################*/ + +/* Tiny Font */ +const uint8_t tinyFont[] = +{ + 0x00, 0x06, 0x00, 0x00, + // ' ' + 0x20,0x05,0x00,0x00,0x00,0x04, + // '!' + 0x21,0x00,0x01,0x05,0x00,0x02, + 0xE8, + // '"' + 0x22,0x00,0x03,0x02,0x00,0x04, + 0xB4, + // '#' + 0x23,0x00,0x03,0x05,0x00,0x04, + 0xBE,0xFA, + // '$' + 0x24,0x00,0x03,0x05,0x00,0x04, + 0xFB,0xBE, + // '%' + 0x25,0x00,0x03,0x05,0x00,0x04, + 0x85,0x42, + // '&' + 0x26,0x00,0x03,0x05,0x00,0x04, + 0x5E,0x74, + // ''' + 0x27,0x00,0x01,0x02,0x00,0x02, + 0xC0, + // '(' + 0x28,0x00,0x02,0x05,0x00,0x03, + 0x6A,0x40, + // ')' + 0x29,0x00,0x02,0x05,0x00,0x03, + 0x95,0x80, + // '*' + 0x2A,0x01,0x03,0x03,0x00,0x04, + 0x55,0x00, + // '+' + 0x2B,0x01,0x03,0x03,0x00,0x04, + 0x5D,0x00, + // ',' + 0x2C,0x03,0x02,0x02,0x00,0x03, + 0x70, + // '-' + 0x2D,0x02,0x03,0x01,0x00,0x04, + 0xE0, + // '.' + 0x2E,0x04,0x01,0x01,0x00,0x02, + 0x80, + // '/' + 0x2F,0x01,0x03,0x03,0x00,0x04, + 0x2A,0x00, + // '0' + 0x30,0x00,0x03,0x05,0x00,0x04, + 0xF6,0xDE, + // '1' + 0x31,0x00,0x03,0x05,0x00,0x04, + 0xC9,0x2E, + // '2' + 0x32,0x00,0x03,0x05,0x00,0x04, + 0xE7,0xCE, + // '3' + 0x33,0x00,0x03,0x05,0x00,0x04, + 0xE7,0x9E, + // '4' + 0x34,0x00,0x03,0x05,0x00,0x04, + 0xB7,0x92, + // '5' + 0x35,0x00,0x03,0x05,0x00,0x04, + 0xF3,0x9E, + // '6' + 0x36,0x00,0x03,0x05,0x00,0x04, + 0xF3,0xDE, + // '7' + 0x37,0x00,0x03,0x05,0x00,0x04, + 0xE4,0x92, + // '8' + 0x38,0x00,0x03,0x05,0x00,0x04, + 0xF7,0xDE, + // '9' + 0x39,0x00,0x03,0x05,0x00,0x04, + 0xF7,0x9E, + // ':' + 0x3A,0x01,0x01,0x03,0x00,0x02, + 0xA0, + // ';' + 0x3B,0x01,0x02,0x04,0x00,0x03, + 0x47, + // '<' + 0x3C,0x00,0x03,0x05,0x00,0x04, + 0x2A,0x22, + // '=' + 0x3D,0x01,0x03,0x03,0x00,0x04, + 0xE3,0x80, + // '>' + 0x3E,0x00,0x03,0x05,0x00,0x04, + 0x88,0xA8, + // '?' + 0x3F,0x00,0x03,0x05,0x00,0x04, + 0xE5,0x84, + // '@' + 0x40,0x00,0x03,0x05,0x00,0x04, + 0xF6,0xCE, + // 'A' + 0x41,0x00,0x03,0x05,0x00,0x04, + 0xF7,0xDA, + // 'B' + 0x42,0x00,0x03,0x05,0x00,0x04, + 0xF7,0x5E, + // 'C' + 0x43,0x00,0x03,0x05,0x00,0x04, + 0xF2,0x4E, + // 'D' + 0x44,0x00,0x03,0x05,0x00,0x04, + 0xD6,0xDC, + // 'E' + 0x45,0x00,0x03,0x05,0x00,0x04, + 0xF3,0xCE, + // 'F' + 0x46,0x00,0x03,0x05,0x00,0x04, + 0xF3,0xC8, + // 'G' + 0x47,0x00,0x03,0x05,0x00,0x04, + 0xF2,0xDE, + // 'H' + 0x48,0x00,0x03,0x05,0x00,0x04, + 0xB7,0xDA, + // 'I' + 0x49,0x00,0x03,0x05,0x00,0x04, + 0xE9,0x2E, + // 'J' + 0x4A,0x00,0x03,0x05,0x00,0x04, + 0x24,0xDE, + // 'K' + 0x4B,0x00,0x03,0x05,0x00,0x04, + 0xB7,0x5A, + // 'L' + 0x4C,0x00,0x03,0x05,0x00,0x04, + 0x92,0x4E, + // 'M' + 0x4D,0x00,0x05,0x05,0x00,0x06, + 0xFD,0x6B,0x5A,0x80, + // 'N' + 0x4E,0x00,0x03,0x05,0x00,0x04, + 0xF6,0xDA, + // 'O' + 0x4F,0x00,0x03,0x05,0x00,0x04, + 0xF6,0xDE, + // 'P' + 0x50,0x00,0x03,0x05,0x00,0x04, + 0xF7,0xC8, + // 'Q' + 0x51,0x00,0x03,0x06,0x00,0x04, + 0xF6,0xDE,0x80, + // 'R' + 0x52,0x00,0x03,0x05,0x00,0x04, + 0xF7,0x5A, + // 'S' + 0x53,0x00,0x03,0x05,0x00,0x04, + 0xF3,0x9E, + // 'T' + 0x54,0x00,0x03,0x05,0x00,0x04, + 0xE9,0x24, + // 'U' + 0x55,0x00,0x03,0x05,0x00,0x04, + 0xB6,0xDE, + // 'V' + 0x56,0x00,0x03,0x05,0x00,0x04, + 0xB6,0xD4, + // 'W' + 0x57,0x00,0x05,0x05,0x00,0x06, + 0xAD,0x6B,0x5F,0x80, + // 'X' + 0x58,0x00,0x03,0x05,0x00,0x04, + 0xB5,0x5A, + // 'Y' + 0x59,0x00,0x03,0x05,0x00,0x04, + 0xB7,0xA4, + // 'Z' + 0x5A,0x00,0x03,0x05,0x00,0x04, + 0xE5,0x4E, + // '[' + 0x5B,0x00,0x02,0x05,0x00,0x03, + 0xEA,0xC0, + // '\' + 0x5C,0x01,0x03,0x03,0x00,0x04, + 0x88,0x80, + // ']' + 0x5D,0x00,0x02,0x05,0x00,0x03, + 0xD5,0xC0, + // '^' + 0x5E,0x00,0x03,0x02,0x00,0x04, + 0x54, + // '_' + 0x5F,0x04,0x03,0x01,0x00,0x04, + 0xE0, + // '`' + 0x60,0x00,0x02,0x02,0x00,0x03, + 0x90, + // 'a' + 0x61,0x01,0x03,0x04,0x00,0x04, + 0x76,0xB0, + // 'b' + 0x62,0x00,0x03,0x05,0x00,0x04, + 0x9A,0xDC, + // 'c' + 0x63,0x01,0x03,0x04,0x00,0x04, + 0xF2,0x70, + // 'd' + 0x64,0x00,0x03,0x05,0x00,0x04, + 0x2E,0xD6, + // 'e' + 0x65,0x01,0x03,0x04,0x00,0x04, + 0x57,0x30, + // 'f' + 0x66,0x00,0x03,0x05,0x00,0x04, + 0x6B,0xA4, + // 'g' + 0x67,0x01,0x03,0x05,0x00,0x04, + 0xF7,0x9C, + // 'h' + 0x68,0x00,0x03,0x05,0x00,0x04, + 0x9A,0xDA, + // 'i' + 0x69,0x00,0x01,0x05,0x00,0x02, + 0xB8, + // 'j' + 0x6A,0x00,0x02,0x06,0x00,0x03, + 0x45,0x60, + // 'k' + 0x6B,0x00,0x03,0x05,0x00,0x04, + 0x97,0x5A, + // 'l' + 0x6C,0x00,0x02,0x05,0x00,0x03, + 0xD5,0x40, + // 'm' + 0x6D,0x01,0x05,0x04,0x00,0x06, + 0xFD,0x6B,0x50, + // 'n' + 0x6E,0x01,0x03,0x04,0x00,0x04, + 0xF6,0xD0, + // 'o' + 0x6F,0x01,0x03,0x04,0x00,0x04, + 0xF6,0xF0, + // 'p' + 0x70,0x01,0x03,0x05,0x00,0x04, + 0xD6,0xE8, + // 'q' + 0x71,0x01,0x03,0x05,0x00,0x04, + 0x76,0xB2, + // 'r' + 0x72,0x01,0x03,0x04,0x00,0x04, + 0xF6,0x40, + // 's' + 0x73,0x01,0x03,0x04,0x00,0x04, + 0x70,0xE0, + // 't' + 0x74,0x00,0x03,0x05,0x00,0x04, + 0x5D,0x26, + // 'u' + 0x75,0x01,0x03,0x04,0x00,0x04, + 0xB6,0xF0, + // 'v' + 0x76,0x01,0x03,0x04,0x00,0x04, + 0xB6,0xA0, + // 'w' + 0x77,0x01,0x05,0x04,0x00,0x06, + 0xAD,0x6B,0xF0, + // 'x' + 0x78,0x01,0x03,0x04,0x00,0x04, + 0xAA,0xD0, + // 'y' + 0x79,0x01,0x03,0x05,0x00,0x04, + 0xB7,0x9C, + // 'z' + 0x7A,0x01,0x03,0x04,0x00,0x04, + 0xE6,0x70, + // '{' + 0x7B,0x00,0x03,0x05,0x00,0x04, + 0x6B,0x26, + // '|' + 0x7C,0x00,0x01,0x05,0x00,0x02, + 0xF8, + // '}' + 0x7D,0x00,0x03,0x05,0x00,0x04, + 0xC9,0xAC, + // '~' + 0x7E,0x01,0x04,0x02,0x00,0x05, + 0x5A, + // Terminator + 0xFF +}; + +/* Small Font */ +const uint8_t smallFont[] = { + 0x08,0x0C,0x20,0x5F, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // + 0x00,0x00,0x20,0x20,0x20,0x20,0x20,0x20,0x00,0x20,0x00,0x00, // ! + 0x00,0x28,0x50,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // " + 0x00,0x00,0x28,0x28,0xFC,0x28,0x50,0xFC,0x50,0x50,0x00,0x00, // # + 0x00,0x20,0x78,0xA8,0xA0,0x60,0x30,0x28,0xA8,0xF0,0x20,0x00, // $ + 0x00,0x00,0x48,0xA8,0xB0,0x50,0x28,0x34,0x54,0x48,0x00,0x00, // % + 0x00,0x00,0x20,0x50,0x50,0x78,0xA8,0xA8,0x90,0x6C,0x00,0x00, // & + 0x00,0x40,0x40,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ' + 0x00,0x04,0x08,0x10,0x10,0x10,0x10,0x10,0x10,0x08,0x04,0x00, // ( + 0x00,0x40,0x20,0x10,0x10,0x10,0x10,0x10,0x10,0x20,0x40,0x00, // ) + 0x00,0x00,0x00,0x20,0xA8,0x70,0x70,0xA8,0x20,0x00,0x00,0x00, // * + 0x00,0x00,0x20,0x20,0x20,0xF8,0x20,0x20,0x20,0x00,0x00,0x00, // + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x40,0x80, // , + 0x00,0x00,0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00, // - + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00, // . + 0x00,0x08,0x10,0x10,0x10,0x20,0x20,0x40,0x40,0x40,0x80,0x00, // / + 0x00,0x00,0x70,0x88,0x88,0x88,0x88,0x88,0x88,0x70,0x00,0x00, // 0 + 0x00,0x00,0x20,0x60,0x20,0x20,0x20,0x20,0x20,0x70,0x00,0x00, // 1 + 0x00,0x00,0x70,0x88,0x88,0x10,0x20,0x40,0x80,0xF8,0x00,0x00, // 2 + 0x00,0x00,0x70,0x88,0x08,0x30,0x08,0x08,0x88,0x70,0x00,0x00, // 3 + 0x00,0x00,0x10,0x30,0x50,0x50,0x90,0x78,0x10,0x18,0x00,0x00, // 4 + 0x00,0x00,0xF8,0x80,0x80,0xF0,0x08,0x08,0x88,0x70,0x00,0x00, // 5 + 0x00,0x00,0x70,0x90,0x80,0xF0,0x88,0x88,0x88,0x70,0x00,0x00, // 6 + 0x00,0x00,0xF8,0x90,0x10,0x20,0x20,0x20,0x20,0x20,0x00,0x00, // 7 + 0x00,0x00,0x70,0x88,0x88,0x70,0x88,0x88,0x88,0x70,0x00,0x00, // 8 + 0x00,0x00,0x70,0x88,0x88,0x88,0x78,0x08,0x48,0x70,0x00,0x00, // 9 + 0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x20,0x00,0x00, // : + 0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x20,0x20,0x00, // ; + 0x00,0x04,0x08,0x10,0x20,0x40,0x20,0x10,0x08,0x04,0x00,0x00, // < + 0x00,0x00,0x00,0x00,0xF8,0x00,0x00,0xF8,0x00,0x00,0x00,0x00, // = + 0x00,0x40,0x20,0x10,0x08,0x04,0x08,0x10,0x20,0x40,0x00,0x00, // > + 0x00,0x00,0x70,0x88,0x88,0x10,0x20,0x20,0x00,0x20,0x00,0x00, // ? + 0x00,0x00,0x70,0x88,0x98,0xA8,0xA8,0xB8,0x80,0x78,0x00,0x00, // @ + 0x00,0x00,0x20,0x20,0x30,0x50,0x50,0x78,0x48,0xCC,0x00,0x00, // A + 0x00,0x00,0xF0,0x48,0x48,0x70,0x48,0x48,0x48,0xF0,0x00,0x00, // B + 0x00,0x00,0x78,0x88,0x80,0x80,0x80,0x80,0x88,0x70,0x00,0x00, // C + 0x00,0x00,0xF0,0x48,0x48,0x48,0x48,0x48,0x48,0xF0,0x00,0x00, // D + 0x00,0x00,0xF8,0x48,0x50,0x70,0x50,0x40,0x48,0xF8,0x00,0x00, // E + 0x00,0x00,0xF8,0x48,0x50,0x70,0x50,0x40,0x40,0xE0,0x00,0x00, // F + 0x00,0x00,0x38,0x48,0x80,0x80,0x9C,0x88,0x48,0x30,0x00,0x00, // G + 0x00,0x00,0xCC,0x48,0x48,0x78,0x48,0x48,0x48,0xCC,0x00,0x00, // H + 0x00,0x00,0xF8,0x20,0x20,0x20,0x20,0x20,0x20,0xF8,0x00,0x00, // I + 0x00,0x00,0x7C,0x10,0x10,0x10,0x10,0x10,0x10,0x90,0xE0,0x00, // J + 0x00,0x00,0xEC,0x48,0x50,0x60,0x50,0x50,0x48,0xEC,0x00,0x00, // K + 0x00,0x00,0xE0,0x40,0x40,0x40,0x40,0x40,0x44,0xFC,0x00,0x00, // L + 0x00,0x00,0xD8,0xD8,0xD8,0xD8,0xA8,0xA8,0xA8,0xA8,0x00,0x00, // M + 0x00,0x00,0xDC,0x48,0x68,0x68,0x58,0x58,0x48,0xE8,0x00,0x00, // N + 0x00,0x00,0x70,0x88,0x88,0x88,0x88,0x88,0x88,0x70,0x00,0x00, // O + 0x00,0x00,0xF0,0x48,0x48,0x70,0x40,0x40,0x40,0xE0,0x00,0x00, // P + 0x00,0x00,0x70,0x88,0x88,0x88,0x88,0xE8,0x98,0x70,0x18,0x00, // Q + 0x00,0x00,0xF0,0x48,0x48,0x70,0x50,0x48,0x48,0xEC,0x00,0x00, // R + 0x00,0x00,0x78,0x88,0x80,0x60,0x10,0x08,0x88,0xF0,0x00,0x00, // S + 0x00,0x00,0xF8,0xA8,0x20,0x20,0x20,0x20,0x20,0x70,0x00,0x00, // T + 0x00,0x00,0xCC,0x48,0x48,0x48,0x48,0x48,0x48,0x30,0x00,0x00, // U + 0x00,0x00,0xCC,0x48,0x48,0x50,0x50,0x30,0x20,0x20,0x00,0x00, // V + 0x00,0x00,0xA8,0xA8,0xA8,0x70,0x50,0x50,0x50,0x50,0x00,0x00, // W + 0x00,0x00,0xD8,0x50,0x50,0x20,0x20,0x50,0x50,0xD8,0x00,0x00, // X + 0x00,0x00,0xD8,0x50,0x50,0x20,0x20,0x20,0x20,0x70,0x00,0x00, // Y + 0x00,0x00,0xF8,0x90,0x10,0x20,0x20,0x40,0x48,0xF8,0x00,0x00, // Z + 0x00,0x38,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x38,0x00, // [ + 0x00,0x40,0x40,0x40,0x20,0x20,0x10,0x10,0x10,0x08,0x00,0x00, // + 0x00,0x70,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x70,0x00, // ] + 0x00,0x20,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ^ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFC, // _ + 0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ' + 0x00,0x00,0x00,0x00,0x00,0x30,0x48,0x38,0x48,0x3C,0x00,0x00, // a + 0x00,0x00,0xC0,0x40,0x40,0x70,0x48,0x48,0x48,0x70,0x00,0x00, // b + 0x00,0x00,0x00,0x00,0x00,0x38,0x48,0x40,0x40,0x38,0x00,0x00, // c + 0x00,0x00,0x18,0x08,0x08,0x38,0x48,0x48,0x48,0x3C,0x00,0x00, // d + 0x00,0x00,0x00,0x00,0x00,0x30,0x48,0x78,0x40,0x38,0x00,0x00, // e + 0x00,0x00,0x1C,0x20,0x20,0x78,0x20,0x20,0x20,0x78,0x00,0x00, // f + 0x00,0x00,0x00,0x00,0x00,0x3C,0x48,0x30,0x40,0x78,0x44,0x38, // g + 0x00,0x00,0xC0,0x40,0x40,0x70,0x48,0x48,0x48,0xEC,0x00,0x00, // h + 0x00,0x00,0x20,0x00,0x00,0x60,0x20,0x20,0x20,0x70,0x00,0x00, // i + 0x00,0x00,0x10,0x00,0x00,0x30,0x10,0x10,0x10,0x10,0x10,0xE0, // j + 0x00,0x00,0xC0,0x40,0x40,0x5C,0x50,0x70,0x48,0xEC,0x00,0x00, // k + 0x00,0x00,0xE0,0x20,0x20,0x20,0x20,0x20,0x20,0xF8,0x00,0x00, // l + 0x00,0x00,0x00,0x00,0x00,0xF0,0xA8,0xA8,0xA8,0xA8,0x00,0x00, // m + 0x00,0x00,0x00,0x00,0x00,0xF0,0x48,0x48,0x48,0xEC,0x00,0x00, // n + 0x00,0x00,0x00,0x00,0x00,0x30,0x48,0x48,0x48,0x30,0x00,0x00, // o + 0x00,0x00,0x00,0x00,0x00,0xF0,0x48,0x48,0x48,0x70,0x40,0xE0, // p + 0x00,0x00,0x00,0x00,0x00,0x38,0x48,0x48,0x48,0x38,0x08,0x1C, // q + 0x00,0x00,0x00,0x00,0x00,0xD8,0x60,0x40,0x40,0xE0,0x00,0x00, // r + 0x00,0x00,0x00,0x00,0x00,0x78,0x40,0x30,0x08,0x78,0x00,0x00, // s + 0x00,0x00,0x00,0x20,0x20,0x70,0x20,0x20,0x20,0x18,0x00,0x00, // t + 0x00,0x00,0x00,0x00,0x00,0xD8,0x48,0x48,0x48,0x3C,0x00,0x00, // u + 0x00,0x00,0x00,0x00,0x00,0xEC,0x48,0x50,0x30,0x20,0x00,0x00, // v + 0x00,0x00,0x00,0x00,0x00,0xA8,0xA8,0x70,0x50,0x50,0x00,0x00, // w + 0x00,0x00,0x00,0x00,0x00,0xD8,0x50,0x20,0x50,0xD8,0x00,0x00, // x + 0x00,0x00,0x00,0x00,0x00,0xEC,0x48,0x50,0x30,0x20,0x20,0xC0, // y + 0x00,0x00,0x00,0x00,0x00,0x78,0x10,0x20,0x20,0x78,0x00,0x00, // z + 0x00,0x18,0x10,0x10,0x10,0x20,0x10,0x10,0x10,0x10,0x18,0x00, // { + 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, // | + 0x00,0x60,0x20,0x20,0x20,0x10,0x20,0x20,0x20,0x20,0x60,0x00, // } + 0x40,0xA4,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ~ +}; + +/* Big Font */ +const uint8_t bigFont[] = { + 0x10,0x10,0x20,0x5F, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // + 0x00,0x00,0x00,0x00,0x07,0x00,0x0F,0x80,0x0F,0x80,0x0F,0x80,0x0F,0x80,0x0F,0x80,0x07,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x00,0x00, // ! + 0x00,0x00,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x06,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // " + 0x00,0x00,0x0C,0x30,0x0C,0x30,0x0C,0x30,0x7F,0xFE,0x7F,0xFE,0x0C,0x30,0x0C,0x30,0x0C,0x30,0x0C,0x30,0x7F,0xFE,0x7F,0xFE,0x0C,0x30,0x0C,0x30,0x0C,0x30,0x00,0x00, // # + 0x00,0x00,0x02,0x40,0x02,0x40,0x0F,0xF8,0x1F,0xF8,0x1A,0x40,0x1A,0x40,0x1F,0xF0,0x0F,0xF8,0x02,0x58,0x02,0x58,0x1F,0xF8,0x1F,0xF0,0x02,0x40,0x02,0x40,0x00,0x00, // $ + 0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x10,0x0E,0x30,0x0E,0x70,0x00,0xE0,0x01,0xC0,0x03,0x80,0x07,0x00,0x0E,0x70,0x0C,0x70,0x08,0x70,0x00,0x00,0x00,0x00,0x00,0x00, // % + 0x00,0x00,0x00,0x00,0x0F,0x00,0x19,0x80,0x19,0x80,0x19,0x80,0x0F,0x00,0x0F,0x08,0x0F,0x98,0x19,0xF8,0x18,0xF0,0x18,0xE0,0x19,0xF0,0x0F,0x98,0x00,0x00,0x00,0x00, // & + 0x00,0x00,0x00,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ' + 0x00,0x00,0x00,0x00,0x00,0xF0,0x01,0xC0,0x03,0x80,0x07,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x07,0x00,0x03,0x80,0x01,0xC0,0x00,0xF0,0x00,0x00,0x00,0x00, // ( + 0x00,0x00,0x00,0x00,0x0F,0x00,0x03,0x80,0x01,0xC0,0x00,0xE0,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0xE0,0x01,0xC0,0x03,0x80,0x0F,0x00,0x00,0x00,0x00,0x00, // ) + 0x00,0x00,0x00,0x00,0x01,0x80,0x11,0x88,0x09,0x90,0x07,0xE0,0x07,0xE0,0x3F,0xFC,0x3F,0xFC,0x07,0xE0,0x07,0xE0,0x09,0x90,0x11,0x88,0x01,0x80,0x00,0x00,0x00,0x00, // * + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x80,0x01,0x80,0x01,0x80,0x0F,0xF0,0x0F,0xF0,0x01,0x80,0x01,0x80,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x0E,0x00,0x00,0x00, // , + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xF8,0x1F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // - + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x00,0x00,0x00,0x00, // , + 0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x06,0x00,0x0E,0x00,0x1C,0x00,0x38,0x00,0x70,0x00,0xE0,0x01,0xC0,0x03,0x80,0x07,0x00,0x0E,0x00,0x1C,0x00,0x00,0x00,0x00,0x00, // / + + 0x00,0x00,0x00,0x00,0x0F,0xF0,0x1C,0x38,0x1C,0x78,0x1C,0xF8,0x1C,0xF8,0x1D,0xB8,0x1D,0xB8,0x1F,0x38,0x1F,0x38,0x1E,0x38,0x1C,0x38,0x0F,0xF0,0x00,0x00,0x00,0x00, // 0 + 0x00,0x00,0x00,0x00,0x01,0x80,0x01,0x80,0x03,0x80,0x1F,0x80,0x1F,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x1F,0xF0,0x00,0x00,0x00,0x00, // 1 + 0x00,0x00,0x00,0x00,0x0F,0xE0,0x1C,0x70,0x1C,0x38,0x00,0x38,0x00,0x70,0x00,0xE0,0x01,0xC0,0x03,0x80,0x07,0x00,0x0E,0x38,0x1C,0x38,0x1F,0xF8,0x00,0x00,0x00,0x00, // 2 + 0x00,0x00,0x00,0x00,0x0F,0xE0,0x1C,0x70,0x1C,0x38,0x00,0x38,0x00,0x70,0x03,0xC0,0x03,0xC0,0x00,0x70,0x00,0x38,0x1C,0x38,0x1C,0x70,0x0F,0xE0,0x00,0x00,0x00,0x00, // 3 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x01,0xE0,0x03,0xE0,0x06,0xE0,0x0C,0xE0,0x18,0xE0,0x1F,0xF8,0x1F,0xF8,0x00,0xE0,0x00,0xE0,0x00,0xE0,0x03,0xF8,0x00,0x00,0x00,0x00, // 4 + 0x00,0x00,0x00,0x00,0x1F,0xF8,0x1C,0x00,0x1C,0x00,0x1C,0x00,0x1C,0x00,0x1F,0xE0,0x1F,0xF0,0x00,0x78,0x00,0x38,0x1C,0x38,0x1C,0x70,0x0F,0xE0,0x00,0x00,0x00,0x00, // 5 + 0x00,0x00,0x00,0x00,0x03,0xE0,0x07,0x00,0x0E,0x00,0x1C,0x00,0x1C,0x00,0x1F,0xF0,0x1F,0xF8,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x0F,0xF0,0x00,0x00,0x00,0x00, // 6 + 0x00,0x00,0x00,0x00,0x1F,0xFC,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x00,0x1C,0x00,0x38,0x00,0x70,0x00,0xE0,0x01,0xC0,0x03,0x80,0x03,0x80,0x03,0x80,0x00,0x00,0x00,0x00, // 7 + 0x00,0x00,0x00,0x00,0x0F,0xF0,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x1F,0x38,0x07,0xE0,0x07,0xE0,0x1C,0xF8,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x0F,0xF0,0x00,0x00,0x00,0x00, // 8 + 0x00,0x00,0x00,0x00,0x0F,0xF0,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x1F,0xF8,0x0F,0xF8,0x00,0x38,0x00,0x38,0x00,0x70,0x00,0xE0,0x07,0xC0,0x00,0x00,0x00,0x00, // 9 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x80,0x03,0x80,0x03,0x80,0x00,0x00,0x00,0x00,0x03,0x80,0x03,0x80,0x03,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // : + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x80,0x03,0x80,0x03,0x80,0x00,0x00,0x00,0x00,0x03,0x80,0x03,0x80,0x03,0x80,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ; + 0x00,0x00,0x00,0x70,0x00,0xE0,0x01,0xC0,0x03,0x80,0x07,0x00,0x0E,0x00,0x1C,0x00,0x1C,0x00,0x0E,0x00,0x07,0x00,0x03,0x80,0x01,0xC0,0x00,0xE0,0x00,0x70,0x00,0x00, // < + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0xFC,0x3F,0xFC,0x00,0x00,0x00,0x00,0x3F,0xFC,0x3F,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // = + 0x00,0x00,0x1C,0x00,0x0E,0x00,0x07,0x00,0x03,0x80,0x01,0xC0,0x00,0xE0,0x00,0x70,0x00,0x70,0x00,0xE0,0x01,0xC0,0x03,0x80,0x07,0x00,0x0E,0x00,0x1C,0x00,0x00,0x00, // > + 0x00,0x00,0x03,0xC0,0x0F,0xF0,0x1E,0x78,0x18,0x38,0x00,0x38,0x00,0x70,0x00,0xE0,0x01,0xC0,0x01,0xC0,0x00,0x00,0x00,0x00,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x00,0x00, // ? + + 0x00,0x00,0x0F,0xF8,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0xFC,0x1C,0xFC,0x1C,0xFC,0x1C,0xFC,0x1C,0x00,0x1C,0x00,0x1C,0x00,0x1F,0xF0,0x07,0xF8,0x00,0x00, // @ + 0x00,0x00,0x00,0x00,0x03,0xC0,0x07,0xE0,0x0E,0x70,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x1F,0xF8,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x00,0x00,0x00,0x00, // A + 0x00,0x00,0x00,0x00,0x1F,0xF0,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0F,0xF0,0x0F,0xF0,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x1F,0xF0,0x00,0x00,0x00,0x00, // B + 0x00,0x00,0x00,0x00,0x07,0xF0,0x0E,0x38,0x1C,0x38,0x1C,0x00,0x1C,0x00,0x1C,0x00,0x1C,0x00,0x1C,0x00,0x1C,0x00,0x1C,0x38,0x0E,0x38,0x07,0xF0,0x00,0x00,0x00,0x00, // C + 0x00,0x00,0x00,0x00,0x1F,0xE0,0x0E,0x70,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x70,0x1F,0xE0,0x00,0x00,0x00,0x00, // D + 0x00,0x00,0x00,0x00,0x1F,0xF8,0x0E,0x18,0x0E,0x08,0x0E,0x00,0x0E,0x30,0x0F,0xF0,0x0F,0xF0,0x0E,0x30,0x0E,0x00,0x0E,0x08,0x0E,0x18,0x1F,0xF8,0x00,0x00,0x00,0x00, // E + 0x00,0x00,0x00,0x00,0x1F,0xF8,0x0E,0x18,0x0E,0x08,0x0E,0x00,0x0E,0x30,0x0F,0xF0,0x0F,0xF0,0x0E,0x30,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x1F,0x00,0x00,0x00,0x00,0x00, // F + 0x00,0x00,0x00,0x00,0x07,0xF0,0x0E,0x38,0x1C,0x38,0x1C,0x38,0x1C,0x00,0x1C,0x00,0x1C,0x00,0x1C,0xF8,0x1C,0x38,0x1C,0x38,0x0E,0x38,0x07,0xF8,0x00,0x00,0x00,0x00, // G + 0x00,0x00,0x00,0x00,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1F,0xF0,0x1F,0xF0,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x00,0x00,0x00,0x00, // H + 0x00,0x00,0x00,0x00,0x0F,0xE0,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x0F,0xE0,0x00,0x00,0x00,0x00, // I + 0x00,0x00,0x00,0x00,0x01,0xFC,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x38,0x70,0x38,0x70,0x38,0x70,0x38,0x70,0x0F,0xE0,0x00,0x00,0x00,0x00, // J + 0x00,0x00,0x00,0x00,0x1E,0x38,0x0E,0x38,0x0E,0x70,0x0E,0xE0,0x0F,0xC0,0x0F,0x80,0x0F,0x80,0x0F,0xC0,0x0E,0xE0,0x0E,0x70,0x0E,0x38,0x1E,0x38,0x00,0x00,0x00,0x00, // K + 0x00,0x00,0x00,0x00,0x1F,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x08,0x0E,0x18,0x0E,0x38,0x1F,0xF8,0x00,0x00,0x00,0x00, // L + 0x00,0x00,0x00,0x00,0x1C,0x1C,0x1E,0x3C,0x1F,0x7C,0x1F,0xFC,0x1F,0xFC,0x1D,0xDC,0x1C,0x9C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x00,0x00,0x00,0x00, // M + 0x00,0x00,0x00,0x00,0x1C,0x1C,0x1C,0x1C,0x1E,0x1C,0x1F,0x1C,0x1F,0x9C,0x1D,0xDC,0x1C,0xFC,0x1C,0x7C,0x1C,0x3C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x00,0x00,0x00,0x00, // N + 0x00,0x00,0x00,0x00,0x03,0xE0,0x07,0xF0,0x0E,0x38,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x0E,0x38,0x07,0xF0,0x03,0xE0,0x00,0x00,0x00,0x00, // O + + 0x00,0x00,0x00,0x00,0x1F,0xF0,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0F,0xF0,0x0F,0xF0,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x1F,0x00,0x00,0x00,0x00,0x00, // P + 0x00,0x00,0x00,0x00,0x03,0xE0,0x0F,0x78,0x0E,0x38,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x7C,0x1C,0xFC,0x0F,0xF8,0x0F,0xF8,0x00,0x38,0x00,0xFC,0x00,0x00, // Q + 0x00,0x00,0x00,0x00,0x1F,0xF0,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0F,0xF0,0x0F,0xF0,0x0E,0x70,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x1E,0x38,0x00,0x00,0x00,0x00, // R + 0x00,0x00,0x00,0x00,0x0F,0xF0,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x1C,0x00,0x0F,0xE0,0x07,0xF0,0x00,0x38,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x0F,0xF0,0x00,0x00,0x00,0x00, // S + 0x00,0x00,0x00,0x00,0x1F,0xFC,0x19,0xCC,0x11,0xC4,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x07,0xF0,0x00,0x00,0x00,0x00, // T + 0x00,0x00,0x00,0x00,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x0F,0xE0,0x00,0x00,0x00,0x00, // U + 0x00,0x00,0x00,0x00,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x0E,0xE0,0x07,0xC0,0x03,0x80,0x00,0x00,0x00,0x00, // V + 0x00,0x00,0x00,0x00,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x9C,0x1C,0x9C,0x1C,0x9C,0x0F,0xF8,0x0F,0xF8,0x07,0x70,0x07,0x70,0x00,0x00,0x00,0x00, // W + 0x00,0x00,0x00,0x00,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x0E,0xE0,0x07,0xC0,0x03,0x80,0x03,0x80,0x07,0xC0,0x0E,0xE0,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x00,0x00,0x00,0x00, // X + 0x00,0x00,0x00,0x00,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x0E,0xE0,0x07,0xC0,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x0F,0xE0,0x00,0x00,0x00,0x00, // Y + 0x00,0x00,0x00,0x00,0x1F,0xF8,0x1C,0x38,0x18,0x38,0x10,0x70,0x00,0xE0,0x01,0xC0,0x03,0x80,0x07,0x00,0x0E,0x08,0x1C,0x18,0x1C,0x38,0x1F,0xF8,0x00,0x00,0x00,0x00, // Z + 0x00,0x00,0x00,0x00,0x07,0xF0,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0xF0,0x00,0x00,0x00,0x00, // [ + 0x00,0x00,0x00,0x00,0x10,0x00,0x18,0x00,0x1C,0x00,0x0E,0x00,0x07,0x00,0x03,0x80,0x01,0xC0,0x00,0xE0,0x00,0x70,0x00,0x38,0x00,0x1C,0x00,0x07,0x00,0x00,0x00,0x00, // + 0x00,0x00,0x00,0x00,0x07,0xF0,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x07,0xF0,0x00,0x00,0x00,0x00, // ] + 0x00,0x00,0x01,0x80,0x03,0xC0,0x07,0xE0,0x0E,0x70,0x1C,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ^ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0xFF,0x7F,0xFF, // _ + + 0x00,0x00,0x00,0x00,0x1C,0x00,0x1C,0x00,0x07,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ' + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0xE0,0x00,0x70,0x00,0x70,0x0F,0xF0,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x0F,0xD8,0x00,0x00,0x00,0x00, // a + 0x00,0x00,0x00,0x00,0x1E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x0F,0xF0,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x1B,0xF0,0x00,0x00,0x00,0x00, // b + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0xE0,0x1C,0x70,0x1C,0x70,0x1C,0x00,0x1C,0x00,0x1C,0x70,0x1C,0x70,0x0F,0xE0,0x00,0x00,0x00,0x00, // c + 0x00,0x00,0x00,0x00,0x00,0xF8,0x00,0x70,0x00,0x70,0x00,0x70,0x0F,0xF0,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x0F,0xD8,0x00,0x00,0x00,0x00, // d + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0xE0,0x1C,0x70,0x1C,0x70,0x1F,0xF0,0x1C,0x00,0x1C,0x70,0x1C,0x70,0x0F,0xE0,0x00,0x00,0x00,0x00, // e + 0x00,0x00,0x00,0x00,0x03,0xE0,0x07,0x70,0x07,0x70,0x07,0x00,0x07,0x00,0x1F,0xE0,0x1F,0xE0,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x1F,0xC0,0x00,0x00,0x00,0x00, // f + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0xD8,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x0F,0xF0,0x07,0xF0,0x00,0x70,0x1C,0x70,0x0F,0xE0, // g + 0x00,0x00,0x00,0x00,0x1E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0xF0,0x0F,0x38,0x0F,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x1E,0x38,0x00,0x00,0x00,0x00, // h + 0x00,0x00,0x00,0x00,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x00,0x00,0x0F,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x0F,0xF8,0x00,0x00,0x00,0x00, // i + 0x00,0x00,0x00,0x00,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x00,0x03,0xF0,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x1C,0x70,0x0C,0xF0,0x07,0xE0, // j + 0x00,0x00,0x00,0x00,0x1E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x38,0x0E,0x70,0x0E,0xE0,0x0F,0xC0,0x0E,0xE0,0x0E,0x70,0x0E,0x38,0x1E,0x38,0x00,0x00,0x00,0x00, // k + 0x00,0x00,0x00,0x00,0x0F,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x0F,0xF8,0x00,0x00,0x00,0x00, // l + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xF8,0x1C,0x9C,0x1C,0x9C,0x1C,0x9C,0x1C,0x9C,0x1C,0x9C,0x1C,0x9C,0x1C,0x9C,0x00,0x00,0x00,0x00, // m + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xE0,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x00,0x00,0x00,0x00, // n + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0xE0,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x0F,0xE0,0x00,0x00,0x00,0x00, // o + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1B,0xF0,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0F,0xF0,0x0E,0x00,0x0E,0x00,0x1F,0x00, // p + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xB0,0x38,0xE0,0x38,0xE0,0x38,0xE0,0x38,0xE0,0x38,0xE0,0x1F,0xE0,0x00,0xE0,0x00,0xE0,0x01,0xF0, // q + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1E,0xF0,0x0F,0xF8,0x0F,0x38,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x1F,0x00,0x00,0x00,0x00,0x00, // r + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0xE0,0x1C,0x30,0x1C,0x30,0x0F,0x80,0x03,0xE0,0x18,0x70,0x18,0x70,0x0F,0xE0,0x00,0x00,0x00,0x00, // s + 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x00,0x07,0x00,0x1F,0xF0,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x70,0x07,0x70,0x03,0xE0,0x00,0x00,0x00,0x00, // t + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x0F,0xD8,0x00,0x00,0x00,0x00, // u + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x0E,0xE0,0x07,0xC0,0x03,0x80,0x00,0x00,0x00,0x00, // v + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x9C,0x1C,0x9C,0x0F,0xF8,0x07,0x70,0x07,0x70,0x00,0x00,0x00,0x00, // w + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1C,0xE0,0x1C,0xE0,0x0F,0xC0,0x07,0x80,0x07,0x80,0x0F,0xC0,0x1C,0xE0,0x1C,0xE0,0x00,0x00,0x00,0x00, // x + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x07,0xF0,0x03,0xE0,0x00,0xE0,0x01,0xC0,0x1F,0x80, // y + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xE0,0x18,0xE0,0x11,0xC0,0x03,0x80,0x07,0x00,0x0E,0x20,0x1C,0x60,0x1F,0xE0,0x00,0x00,0x00,0x00, // z + 0x00,0x00,0x00,0x00,0x01,0xF8,0x03,0x80,0x03,0x80,0x03,0x80,0x07,0x00,0x1C,0x00,0x1C,0x00,0x07,0x00,0x03,0x80,0x03,0x80,0x03,0x80,0x01,0xF8,0x00,0x00,0x00,0x00, // { + 0x00,0x00,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x00,0x00, // | + 0x00,0x00,0x00,0x00,0x1F,0x80,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x00,0xE0,0x00,0x38,0x00,0x38,0x00,0xE0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x1F,0x80,0x00,0x00,0x00,0x00, // } + 0x00,0x00,0x00,0x00,0x1F,0x1C,0x3B,0x9C,0x39,0xDC,0x38,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 // ~ +}; diff --git a/Firmware_V2/src/hardware/display/fonts.h b/Firmware_V2/src/hardware/display/fonts.h new file mode 100644 index 0000000..69ec572 --- /dev/null +++ b/Firmware_V2/src/hardware/display/fonts.h @@ -0,0 +1,25 @@ +/* +* +* Fonts +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef FONTS_H +#define FONTS_H + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +extern const uint8_t tinyFont[]; +extern const uint8_t smallFont[]; +extern const uint8_t bigFont[]; + +#endif /* FONTS_H */ diff --git a/Firmware_V2/src/hardware/hardware.cpp b/Firmware_V2/src/hardware/hardware.cpp new file mode 100644 index 0000000..f2523ae --- /dev/null +++ b/Firmware_V2/src/hardware/hardware.cpp @@ -0,0 +1,851 @@ +/* + * + * HARDWARE - Main hardware functions + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Converts a float to four bytes */ +void floatToBytes(uint8_t* farray, float val) +{ + union + { + float f; + unsigned long ul; + } u; + u.f = val; + farray[0] = u.ul & 0x00FF; + farray[1] = (u.ul & 0xFF00) >> 8; + farray[2] = (u.ul & 0xFF0000) >> 16; + farray[3] = (u.ul & 0xFF000000) >> 24; +} + +/* Converts four bytes back to float */ +float bytesToFloat(uint8_t* farray) +{ + union + { + float f; + unsigned long ul; + } u; + u.ul = (farray[3] << 24) | (farray[2] << 16) | (farray[1] << 8) + | (farray[0]); + return u.f; +} + +/* Switch the SPI clockline to pin 14 */ +void startAltClockline(boolean sdStart) +{ + CORE_PIN13_CONFIG = PORT_PCR_MUX(1); + CORE_PIN14_CONFIG = PORT_PCR_DSE | PORT_PCR_MUX(2); + if (sdStart) + sd.begin(pin_sd_cs, SPI_FULL_SPEED); +} + +/* Switch the SPI clockline back to pin 13 */ +void endAltClockline() +{ + CORE_PIN13_CONFIG = PORT_PCR_DSE | PORT_PCR_MUX(2); + CORE_PIN14_CONFIG = PORT_PCR_MUX(1); +} + +/* Checks if the sd card is inserted for ThermocamV4 */ +boolean checkSDCard() +{ + //ThermocamV4 or DIY-Thermocam V2 - try init SD card + if ((mlx90614Version == mlx90614Version_old) + || (teensyVersion == teensyVersion_new)) + return beginSD(); + + //All other do not need the check + return true; +} + +/* Reads the adjust combined settings from EEPROM */ +void readAdjustCombined() +{ + //Adjust combined selection + byte adjCombPreset; + byte read = EEPROM.read(eeprom_adjCombPreset); + if ((read >= adjComb_preset1) && (read <= adjComb_preset3)) + adjCombPreset = read; + else + adjCombPreset = adjComb_temporary; + //Adjust combined preset 1 + if ((adjCombPreset == adjComb_preset1) + && (EEPROM.read(eeprom_adjComb1Set) == eeprom_setValue)) + { + adjCombDown = EEPROM.read(eeprom_adjComb1Down); + adjCombLeft = EEPROM.read(eeprom_adjComb1Left); + adjCombRight = EEPROM.read(eeprom_adjComb1Right); + adjCombUp = EEPROM.read(eeprom_adjComb1Up); + adjCombAlpha = EEPROM.read(eeprom_adjComb1Alpha) / 100.0; + adjCombFactor = EEPROM.read(eeprom_adjComb1Factor) / 100.0; + } + //Adjust combined preset 2 + else if ((adjCombPreset == adjComb_preset2) + && (EEPROM.read(eeprom_adjComb2Set) == eeprom_setValue)) + { + adjCombDown = EEPROM.read(eeprom_adjComb2Down); + adjCombLeft = EEPROM.read(eeprom_adjComb2Left); + adjCombRight = EEPROM.read(eeprom_adjComb2Right); + adjCombUp = EEPROM.read(eeprom_adjComb2Up); + adjCombAlpha = EEPROM.read(eeprom_adjComb2Alpha) / 100.0; + adjCombFactor = EEPROM.read(eeprom_adjComb2Factor) / 100.0; + } + //Adjust combined preset 3 + else if ((adjCombPreset == adjComb_preset3) + && (EEPROM.read(eeprom_adjComb3Set) == eeprom_setValue)) + { + adjCombDown = EEPROM.read(eeprom_adjComb3Down); + adjCombLeft = EEPROM.read(eeprom_adjComb3Left); + adjCombRight = EEPROM.read(eeprom_adjComb3Right); + adjCombUp = EEPROM.read(eeprom_adjComb3Up); + adjCombAlpha = EEPROM.read(eeprom_adjComb3Alpha) / 100.0; + adjCombFactor = EEPROM.read(eeprom_adjComb3Factor) / 100.0; + } + //Load defaults + else + { + adjCombDown = 0; + adjCombUp = 0; + adjCombLeft = 0; + adjCombRight = 0; + adjCombAlpha = 0.5; + adjCombFactor = 1.0; + } + //Set factor to standard if invalid + if ((adjCombFactor < 0.7) || (adjCombFactor > 1.0)) + adjCombFactor = 1.0; +} + +/* Clears the whole EEPROM */ +void clearEEPROM() +{ + for (unsigned int i = 100; i < 250; i++) + EEPROM.write(i, 0); +} + +/* Checks if a FW upgrade has been done */ +void checkFWUpgrade() +{ + //If the first start setup has not been completed, skip + if (checkFirstStart()) + return; + + //Read current FW version from EEPROM + byte eepromVersion = EEPROM.read(eeprom_fwVersion); + + //Show message after firmware upgrade + if (eepromVersion != fwVersion) + { + //Upgrade from old Thermocam-V4 firmware + if ((mlx90614Version == mlx90614Version_old) + && (EEPROM.read(eeprom_liveHelper) != eeprom_setValue)) + { + //Clear EEPROM + clearEEPROM(); + //Show message and wait + showFullMessage((char*) "FW update completed, pls restart"); + while (true) + ; + } + //Upgrade + if (fwVersion > eepromVersion) + { + //If coming from a firmware version smaller than 2.00, clear EEPROM + if (eepromVersion < 200) + clearEEPROM(); + + //Clear adjust combined settings when coming from FW smaller than 2.13 + if (eepromVersion < 213) + { + EEPROM.write(eeprom_adjCombPreset, adjComb_temporary); + EEPROM.write(eeprom_adjComb1Set, 0); + EEPROM.write(eeprom_adjComb2Set, 0); + EEPROM.write(eeprom_adjComb3Set, 0); + } + + //Clear temperature presets when coming from a FW smaller than 2.43 + if (eepromVersion < 243) + { + EEPROM.write(eeprom_minMax1Set, 0); + EEPROM.write(eeprom_minMax2Set, 0); + EEPROM.write(eeprom_minMax3Set, 0); + EEPROM.write(eeprom_minMaxPreset, 0); + } + + //Set Lepton3.5 gain mode to high for FW smaller than 2.47 + if(eepromVersion < 247) + { + EEPROM.write(eeprom_lepton_3_5_gain, lepton_3_5_gain_high); + } + + //Show upgrade completed message + showFullMessage((char*) "Update completed, restart device"); + //Set EEPROM firmware version to current one + EEPROM.write(eeprom_fwVersion, fwVersion); + //Wait for hard-reset + while (true) + ; + } + + //Show downgrade completed message + showFullMessage((char*) "Downgrade completed, restart device"); + //Set EEPROM firmware version to current one + EEPROM.write(eeprom_fwVersion, fwVersion); + //Wait for hard-reset + while (true) + ; + } +} + +/* Reads the calibration slope from EEPROM */ +void readCalibration() +{ + uint8_t farray[4]; + //Read slope + for (int i = 0; i < 4; i++) + farray[i] = EEPROM.read(eeprom_calSlopeBase + i); + calSlope = bytesToFloat(farray); +} + +/* Reads the old settings from EEPROM */ +void readEEPROM() +{ + byte read; + //Temperature format + read = EEPROM.read(eeprom_tempFormat); + if ((read == tempFormat_celcius) || (read == tempFormat_fahrenheit)) + tempFormat = read; + else + tempFormat = tempFormat_celcius; + //Color scheme + read = EEPROM.read(eeprom_colorScheme); + if ((read >= 0) && (read <= (colorSchemeTotal - 1))) + colorScheme = read; + else + colorScheme = colorScheme_rainbow; + //Convert Enabled + read = EEPROM.read(eeprom_convertEnabled); + if ((read == false) || (read == true)) + convertEnabled = read; + else + convertEnabled = false; + //Visual Enabled, only enable if camera is connected + read = EEPROM.read(eeprom_visualEnabled); + if (((read == false) || (read == true)) && checkDiagnostic(diag_camera)) + visualEnabled = read; + else + visualEnabled = false; + //Battery Enabled + read = EEPROM.read(eeprom_batteryEnabled); + if ((read == false) || (read == true)) + batteryEnabled = read; + else + batteryEnabled = false; + //Time Enabled + read = EEPROM.read(eeprom_timeEnabled); + if ((read == false) || (read == true)) + timeEnabled = read; + else + timeEnabled = false; + //Date Enabled + read = EEPROM.read(eeprom_dateEnabled); + if ((read == false) || (read == true)) + dateEnabled = read; + else + dateEnabled = false; + //Storage Enabled + read = EEPROM.read(eeprom_storageEnabled); + if ((read == false) || (read == true)) + storageEnabled = read; + else + storageEnabled = false; + //Spot Enabled, only load when spot sensor is working + read = EEPROM.read(eeprom_spotEnabled); + if (((read == false) || (read == true)) && checkDiagnostic(diag_spot)) + spotEnabled = read; + else + spotEnabled = false; + //Filter Type + read = EEPROM.read(eeprom_filterType); + if ((read == filterType_none) || (read == filterType_box) + || (read == filterType_gaussian)) + filterType = read; + else + filterType = filterType_gaussian; + //Colorbar Enabled + read = EEPROM.read(eeprom_colorbarEnabled); + if ((read == false) || (read == true)) + colorbarEnabled = read; + else + colorbarEnabled = true; + //Display Mode, only load when camera is connected + read = EEPROM.read(eeprom_displayMode); + if (((read == displayMode_thermal) || (read == displayMode_visual) + || (read == displayMode_combined)) && checkDiagnostic(diag_camera)) + displayMode = read; + else + displayMode = displayMode_thermal; + //Text color + read = EEPROM.read(eeprom_textColor); + if ((read >= textColor_white) && (read <= textColor_blue)) + textColor = read; + else + textColor = textColor_white; + //Horizontal mirroring + read = EEPROM.read(eeprom_rotationHorizont); + if ((read == false) || (read == true)) + rotationHorizont = read; + else + rotationHorizont = false; + //Hot / cold mode + read = EEPROM.read(eeprom_hotColdMode); + if ((read >= hotColdMode_disabled) && (read <= hotColdMode_hot)) + hotColdMode = read; + else + hotColdMode = hotColdMode_disabled; + //Hot / cold level and color + if (hotColdMode != hotColdMode_disabled) + { + hotColdLevel = ((EEPROM.read(eeprom_hotColdLevelHigh) << 8) + + EEPROM.read(eeprom_hotColdLevelLow)); + hotColdColor = EEPROM.read(eeprom_hotColdColor); + } + //Calibration slope + read = EEPROM.read(eeprom_calSlopeSet); + if ((leptonVersion == leptonVersion_2_5_shutter) + || (leptonVersion == leptonVersion_3_5_shutter)) + calSlope = lepton_get_resolution(); + else if (read == eeprom_setValue) + readCalibration(); + else + calSlope = cal_stdSlope; + //Min/Max Points + read = EEPROM.read(eeprom_minMaxPoints); + if ((read == minMaxPoints_disabled) || (read == minMaxPoints_min) + || (read == minMaxPoints_max) || (read == minMaxPoints_both)) + minMaxPoints = read; + else + minMaxPoints = minMaxPoints_disabled; + //HQ res, V2 only + if (teensyVersion == teensyVersion_new) + { + read = EEPROM.read(eeprom_hqRes); + if ((read == false) || (read == true)) + hqRes = read; + else + hqRes = true; + + } + //Gain Mode, Lepton3.5 only + if (leptonVersion == leptonVersion_3_5_shutter) + { + read = EEPROM.read(eeprom_lepton_3_5_gain); + if (read == lepton_3_5_gain_high) + { + lepton_3_5_set_high_gain(); + } + else if(read == lepton_3_5_gain_low) + { + lepton_3_5_set_low_gain(); + } + else + { + lepton_3_5_set_high_gain(); + } + } + + //Align combined settings + readAdjustCombined(); +} + +/* Checks the specific device from the diagnostic variable */ +bool checkDiagnostic(byte device) +{ + //Returns false if the device does not work + return (diagnostic >> device) & 1; +} + +/* Sets the status of a specific device from the diagnostic variable */ +void setDiagnostic(byte device) +{ + diagnostic &= ~(1 << device); +} + +/* Checks for hardware issues */ +void checkHardware() +{ + //When returning from mass storage, do not check + if (EEPROM.read(eeprom_massStorage) == eeprom_setValue) + { + EEPROM.write(eeprom_massStorage, 0); + diagnostic = diag_ok; + } + + //If the diagnostic is not okay show info + if (diagnostic != diag_ok) + showDiagnostic(); + + //If there is a problem with the visual camera, switch to thermal + if (!checkDiagnostic(diag_camera)) + { + //We switch to thermal mode + displayMode = displayMode_thermal; + //And disable the visual image save + visualEnabled = false; + } +} + +/* Stores the current calibration to EEPROM */ +void storeCalibration() +{ + uint8_t farray[4]; + //Store slope + floatToBytes(farray, (float) calSlope); + for (int i = 0; i < 4; i++) + EEPROM.write(eeprom_calSlopeBase + i, (farray[i])); + EEPROM.write(eeprom_calSlopeSet, eeprom_setValue); +} + +/* A method to check if the touch screen is pressed */ +boolean touchScreenPressed() +{ + //Check button status with debounce + touchDebouncer.update(); + return touchDebouncer.read(); +} + +/* A method to check if the external button is pressed */ +boolean extButtonPressed() +{ + //Check button status with debounce + buttonDebouncer.update(); + return buttonDebouncer.read(); +} + +/* Initialize the GPIO pins */ +void initGPIO() +{ + //Deactivate the laser for old HW + if (teensyVersion == teensyVersion_old) + { + pinMode(pin_laser, OUTPUT); + digitalWrite(pin_laser, LOW); + laserEnabled = false; + } + //Set the touch IRQ pin to input + pinMode(pin_touch_irq, INPUT); + //For Teensy 3.6, activate internal pulldown for button + if (teensyVersion == teensyVersion_new) + pinMode(pin_button, INPUT_PULLDOWN); + //Set button as input for Teensy 3.1/3.2 + else + pinMode(pin_button, INPUT); + //Activate display backlight + pinMode(pin_lcd_backlight, OUTPUT); + digitalWrite(pin_lcd_backlight, HIGH); +} + +/* Disables all Chip-Select lines on the SPI bus */ +void initSPI() +{ + pinMode(pin_lcd_dc, OUTPUT); + pinMode(pin_touch_cs, OUTPUT); + pinMode(pin_lepton_cs, OUTPUT); + pinMode(pin_sd_cs, OUTPUT); + pinMode(pin_lcd_cs, OUTPUT); + pinMode(pin_cam_cs, OUTPUT); + digitalWrite(pin_lcd_dc, HIGH); + digitalWrite(pin_touch_cs, HIGH); + digitalWrite(pin_lepton_cs, HIGH); + digitalWrite(pin_sd_cs, HIGH); + digitalWrite(pin_lcd_cs, HIGH); + digitalWrite(pin_cam_cs, HIGH); + SPI.begin(); +} + +/* Inits the I2C Bus */ +void initI2C() +{ + //Start the Bus + Wire.begin(); + //Enable Timeout for Hardware start + Wire.setDefaultTimeout(1000); + //Use external pullups for new HW + if (teensyVersion == teensyVersion_new) + Wire.pinConfigure(I2C_PINS_18_19, I2C_PULLUP_EXT); + //Use internal pullups for old HW + else + Wire.pinConfigure(I2C_PINS_18_19, I2C_PULLUP_INT); +} + +/* Init the Analog-Digital-Converter for the battery measure */ +void initADC() +{ + //Init ADC + batMeasure = new ADC(); + //set number of averages + batMeasure->setAveraging(4); + //set bits of resolution + batMeasure->setResolution(12); + //change the conversion speed + batMeasure->setConversionSpeed(ADC_MED_SPEED); + //change the sampling speed + batMeasure->setSamplingSpeed(ADC_MED_SPEED); + //set battery pin as input + pinMode(pin_bat_measure, INPUT); +} + +/* Init the buffer(s) */ +void initBuffer() +{ + //For Teensy 3.6, init 320x240 buffer + if (teensyVersion == teensyVersion_new) + bigBuffer = (uint16_t*) malloc(153600); + + //Init 160x120 buffer for all devices + smallBuffer = (uint16_t*) malloc(38400); +} + +/* Display the content of the small/big buffer on the screen */ +void displayBuffer() +{ + //Display 320x240 for Teensy 3.6 + if ((teensyVersion == teensyVersion_new) && (hqRes)) + display_writeScreen(bigBuffer, 0); + //160x120 for Teensy 3.1 / 3.2 + else + display_writeScreen(smallBuffer, 1); +} + +/* Detect which teensy version is used */ +void detectTeensyVersion() +{ + //Teensy 3.6 used in the DIY-Thermocam V2 +#if defined(__MK66FX1M0__) + teensyVersion = teensyVersion_new; + //Teensy 3.1 / 3.2 used in the old hardware generations +#elif defined(__MK20DX256__) + teensyVersion = teensyVersion_old; +#endif + + //Set hardware version to detect V1/V2 over EEPROM + EEPROM.write(eeprom_teensyVersion, teensyVersion); +} + +/* Sets the display rotation depending on the setting */ +void setDisplayRotation() +{ + if (rotationVert) + { + display_setRotation(135); + touch_setRotation(true); + } + else + { + display_setRotation(45); + touch_setRotation(false); + } +} + +/* Reads the temperature limits from EEPROM */ +void readTempLimits() +{ + //Some variables to get started + byte minValueHigh, minValueLow, maxValueHigh, maxValueLow, minMaxComp; + bool found = false; + + //Min / max selection + byte minMaxPreset; + byte read = EEPROM.read(eeprom_minMaxPreset); + if ((read >= minMax_preset1) && (read <= minMax_preset3)) + minMaxPreset = read; + else + minMaxPreset = minMax_temporary; + + //Min / max preset 1 + if ((minMaxPreset == minMax_preset1) + && (EEPROM.read(eeprom_minMax1Set) == eeprom_setValue)) + { + minValueHigh = eeprom_minValue1High; + minValueLow = eeprom_minValue1Low; + maxValueHigh = eeprom_maxValue1High; + maxValueLow = eeprom_maxValue1Low; + minMaxComp = eeprom_minMax1Comp; + found = true; + } + //Min / max preset 2 + else if ((minMaxPreset == minMax_preset2) + && (EEPROM.read(eeprom_minMax2Set) == eeprom_setValue)) + { + minValueHigh = eeprom_minValue2High; + minValueLow = eeprom_minValue2Low; + maxValueHigh = eeprom_maxValue2High; + maxValueLow = eeprom_maxValue2Low; + minMaxComp = eeprom_minMax2Comp; + found = true; + } + //Min / max preset 3 + else if ((minMaxPreset == minMax_preset3) + && (EEPROM.read(eeprom_minMax3Set) == eeprom_setValue)) + { + minValueHigh = eeprom_minValue3High; + minValueLow = eeprom_minValue3Low; + maxValueHigh = eeprom_maxValue3High; + maxValueLow = eeprom_maxValue3Low; + minMaxComp = eeprom_minMax3Comp; + found = true; + } + + //Apply settings + if (found) + { + minValue = + ((EEPROM.read(minValueHigh) << 8) + EEPROM.read(minValueLow)); + maxValue = + ((EEPROM.read(maxValueHigh) << 8) + EEPROM.read(maxValueLow)); + uint8_t farray[4]; + for (int i = 0; i < 4; i++) + farray[i] = EEPROM.read(minMaxComp + i); + calComp = bytesToFloat(farray); + autoMode = false; + } +} + +/* Init the screen off timer */ +void initScreenOffTimer() +{ + byte read = EEPROM.read(eeprom_screenOffTime); + //Try to read from EEPROM + if ((read == screenOffTime_disabled) || (read == screenOffTime_5min) + || read == screenOffTime_20min) + { + screenOffTime = read; + //10 Minutes + if (screenOffTime == screenOffTime_5min) + screenOff.begin(300000, false); + //30 Minutes + else if (screenOffTime == screenOffTime_20min) + screenOff.begin(1200000, false); + //Disable marker + screenPressed = false; + } + else + screenOffTime = screenOffTime_disabled; +} + +/* Get time from the RTC */ +time_t getTeensy3Time() +{ + return Teensy3Clock.get(); +} + +/* Init the time and correct it if required */ +void initRTC() +{ + //Get the time from the Teensy + setSyncProvider(getTeensy3Time); + + //Check if year is lower than 2017 + if ((year() < 2017) && (EEPROM.read(eeprom_firstStart) == eeprom_setValue)) + { + showFullMessage((char*) "Empty coin cell battery"); + delay(1000); + setTime(0, 0, 0, 1, 1, 2017); + Teensy3Clock.set(now()); + } +} + +/* Disable the screen backlight */ +void disableScreenLight() +{ + digitalWrite(pin_lcd_backlight, LOW); +} + +/* Enables the screen backlight */ +void enableScreenLight() +{ + digitalWrite(pin_lcd_backlight, HIGH); +} + +/* Checks if the screen backlight is on or off*/ +bool checkScreenLight() +{ + return digitalRead(pin_lcd_backlight); +} + +/* Disable automatic FFC when saved in EEPROM */ +void checkNoFFC() +{ + //Set value found + if (EEPROM.read(eeprom_noShutter) == eeprom_setValue) + { + //Disable auto FFC mode + lepton_ffcMode(false); + + //Set lepton shutter to none + leptonShutter = leptonShutter_none; + } +} + +//Get the spot temperature from Lepton or MLX90614 +void getSpotTemp() +{ + //Get spot value from radiometric Lepton + if ((leptonVersion == leptonVersion_2_5_shutter) + || (leptonVersion == leptonVersion_3_5_shutter)) + spotTemp = lepton_spotTemp(); + + //Get temperature from MLX90614 + else + spotTemp = mlx90614_getTemp(); + + //Convert to Fahrenheit if required + if (tempFormat == tempFormat_fahrenheit) + spotTemp = celciusToFahrenheit(spotTemp); +} + +/* Switches the laser on or off*/ +void toggleLaser(bool message) +{ + //Thermocam V4 or DIY-Thermocam V2 does not support this + if ((mlx90614Version == mlx90614Version_old) + || (teensyVersion == teensyVersion_new)) + return; + + //Laser enabled, switch off + if (laserEnabled) + { + digitalWrite(pin_laser, LOW); + laserEnabled = false; + if (message) + { + showFullMessage((char*) "Laser is now off", true); + delay(1000); + } + } + //Laser disabled, switch on + else + { + digitalWrite(pin_laser, HIGH); + laserEnabled = true; + if (message) + { + showFullMessage((char*) "Laser is now on", true); + delay(1000); + } + } +} + +/* Toggle the display*/ +void toggleDisplay() +{ + showFullMessage((char*) "Screen goes off, touch to continue", true); + delay(1000); + disableScreenLight(); + //Wait for touch press + while (!touch_touched()) + ; + //Turning screen on + drawMainMenuBorder(); + showFullMessage((char*) "Turning screen on..", true); + enableScreenLight(); + delay(1000); +} + +/* Check if the screen was pressed in the time period */ +bool screenOffCheck() +{ + //Timer exceeded + if ((screenOff.check()) && (screenOffTime != screenOffTime_disabled)) + { + //No touch press in the last interval + if (screenPressed == false) + { + toggleDisplay(); + screenOff.reset(); + return true; + } + //Touch pressed, restart timer + screenPressed = false; + screenOff.reset(); + return false; + } + return false; +} + +/* Init the hardware */ +void initHardware() +{ + //Init UART + Serial.begin(115200); + //Detect teensy version + detectTeensyVersion(); + //Init GPIO + initGPIO(); + //Init SPI + initSPI(); + //Init I2C + initI2C(); + //Init ADC + initADC(); + //Init display + display_init(); + //Show bootscreen + bootScreen(); + //Init touch + touch_init(); + //Init camera + camera_init(); + //Init lepton + lepton_init(); + //Init spot sensor + mlx90614_init(); + //Init SD card + initSD(); + //Disable I2C timeout + Wire.setDefaultTimeout(0); + //Init screen off timer + initScreenOffTimer(); + //Init the buffer(s) + initBuffer(); + //Check battery for the first time + checkBattery(true); + //Init the realtime clock + initRTC(); + //If no automatic FFC desired, disable it forever + checkNoFFC(); +} diff --git a/Firmware_V2/src/hardware/hardware.h b/Firmware_V2/src/hardware/hardware.h new file mode 100644 index 0000000..325b11b --- /dev/null +++ b/Firmware_V2/src/hardware/hardware.h @@ -0,0 +1,59 @@ +/* +* +* HARDWARE - Main hardware functions +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef HARDWARE_H +#define HARDWARE_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +float bytesToFloat(uint8_t* farray); +void checkHardware(); +bool checkDiagnostic(byte device); +void checkFWUpgrade(); +void checkNoFFC(); +bool checkScreenLight(); +boolean checkSDCard(); +void clearEEPROM(); +void detectTeensyVersion(); +void disableScreenLight(); +void displayBuffer(); +void enableScreenLight(); +void endAltClockline(); +boolean extButtonPressed(); +void floatToBytes(uint8_t* farray, float val); +void getSpotTemp(); +time_t getTeensy3Time(); +void initADC(); +void initBuffer(); +void initGPIO(); +void initHardware(); +void initI2C(); +void initRTC(); +void initScreenOffTimer(); +void initSPI(); +void readAdjustCombined(); +void readCalibration(); +void readEEPROM(); +void readTempLimits(); +bool screenOffCheck(); +void setDiagnostic(byte device); +void setDisplayRotation(); +void startAltClockline(boolean sdStart = false); +void storeCalibration(); +void toggleDisplay(); +void toggleLaser(bool message = false); +boolean touchScreenPressed(); + +#endif /* HARDWARE_H */ diff --git a/Firmware_V2/src/hardware/lepton.cpp b/Firmware_V2/src/hardware/lepton.cpp new file mode 100644 index 0000000..99bc819 --- /dev/null +++ b/Firmware_V2/src/hardware/lepton.cpp @@ -0,0 +1,809 @@ +/* + * + * LEPTON - Access the FLIR Lepton LWIR module + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################### STATIC DATA DECLARATIONS ##########################*/ + +//Array to store one Lepton frame +static byte leptonFrame[164]; + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Start Lepton SPI Transmission */ +void lepton_begin() +{ + //Start alternative clock line, except for old HW + if (mlx90614Version == mlx90614Version_new) + startAltClockline(); + + //For Teensy 3.1 / 3.2 and Lepton3 use this one + if ((teensyVersion == teensyVersion_old) + && ((leptonVersion == leptonVersion_3_0_shutter) + || (leptonVersion == leptonVersion_3_5_shutter))) + SPI.beginTransaction(SPISettings(30000000, MSBFIRST, SPI_MODE0)); + + //Otherwise use 20 Mhz maximum and SPI mode 1 + else + SPI.beginTransaction(SPISettings(20000000, MSBFIRST, SPI_MODE1)); + + //Start transfer - CS LOW + digitalWrite(pin_lepton_cs, LOW); +} + +/* End Lepton SPI Transmission */ +void lepton_end() +{ + //End transfer - CS HIGH + digitalWriteFast(pin_lepton_cs, HIGH); + + //End SPI Transaction + SPI.endTransaction(); + + //End alternative clock line, except for old HW + if (mlx90614Version == mlx90614Version_new) + endAltClockline(); +} + +/* Reset the SPI bus to re-initiate Lepton communication */ +void lepton_reset() +{ + lepton_end(); + delay(186); + lepton_begin(); +} + +/* Store one package of 80 columns into RAM */ +bool savePackage(byte line, byte segment) +{ + //Go through the video pixels for one video line + for (int column = 0; column < 80; column++) + { + //Apply horizontal mirroring + if (rotationHorizont) + column = 79 - column; + + //Make a 16-bit rawvalue from the lepton frame + uint16_t result = (uint16_t) (leptonFrame[2 * column + 4] << 8 + | leptonFrame[2 * column + 5]); + + //Discard horizontal mirroring + if (rotationHorizont) + column = 79 - column; + + //Invalid value, return + if (result == 0) + { + return 0; + } + + //Lepton2.x + if ((leptonVersion != leptonVersion_3_0_shutter) + && (leptonVersion != leptonVersion_3_5_shutter)) + { + //Rotated or old hardware version + if (((mlx90614Version == mlx90614Version_old) && (!rotationVert)) + || ((mlx90614Version == mlx90614Version_new) + && (rotationVert))) + { + smallBuffer[(line * 2 * 160) + (column * 2)] = result; + smallBuffer[(line * 2 * 160) + (column * 2) + 1] = result; + smallBuffer[(line * 2 * 160) + 160 + (column * 2)] = result; + smallBuffer[(line * 2 * 160) + 160 + (column * 2) + 1] = result; + } + //Non-rotated + else + { + smallBuffer[19199 - ((line * 2 * 160) + (column * 2))] = result; + smallBuffer[19199 - ((line * 2 * 160) + (column * 2) + 1)] = + result; + smallBuffer[19199 - ((line * 2 * 160) + 160 + (column * 2))] = + result; + smallBuffer[19199 - ((line * 2 * 160) + 160 + (column * 2) + 1)] = + result; + } + } + + //Lepton3 + else + { + //Non-rotated + if (!rotationVert) + { + switch (segment) + { + case 1: + if (rotationHorizont) + smallBuffer[19199 + - (((line / 2) * 160) + ((1 - (line % 2)) * 80) + + (column))] = result; + else + smallBuffer[19199 + - (((line / 2) * 160) + ((line % 2) * 80) + + (column))] = result; + break; + case 2: + if (rotationHorizont) + smallBuffer[14399 + - (((line / 2) * 160) + ((1 - (line % 2)) * 80) + + (column))] = result; + else + smallBuffer[14399 + - (((line / 2) * 160) + ((line % 2) * 80) + + (column))] = result; + break; + case 3: + if (rotationHorizont) + smallBuffer[9599 + - (((line / 2) * 160) + ((1 - (line % 2)) * 80) + + (column))] = result; + else + smallBuffer[9599 + - (((line / 2) * 160) + ((line % 2) * 80) + + (column))] = result; + break; + case 4: + if (rotationHorizont) + smallBuffer[4799 + - (((line / 2) * 160) + ((1 - (line % 2)) * 80) + + (column))] = result; + else + smallBuffer[4799 + - (((line / 2) * 160) + ((line % 2) * 80) + + (column))] = result; + break; + } + } + //Rotated + else + { + switch (segment) + { + case 1: + smallBuffer[((line / 2) * 160) + ((line % 2) * 80) + + (column)] = result; + break; + case 2: + smallBuffer[4800 + + (((line / 2) * 160) + ((line % 2) * 80) + (column))] = + result; + break; + case 3: + smallBuffer[9600 + + (((line / 2) * 160) + ((line % 2) * 80) + (column))] = + result; + break; + case 4: + smallBuffer[14400 + + (((line / 2) * 160) + ((line % 2) * 80) + (column))] = + result; + break; + } + } + } + + } + + //Everything worked + return 1; +} + +/* Get one line package from the Lepton */ +LeptonReadError lepton_getPackage(byte line, byte seg) +{ + //Receive one frame over SPI + SPI.transfer(leptonFrame, 164); + + //Repeat as long as the frame is not valid, equals sync + if ((leptonFrame[0] & 0x0F) == 0x0F) + return DISCARD; + + //Check if the line number matches the expected line + if (leptonFrame[1] != line) + return ROW_ERROR; + + //For the Lepton3.x, check if the segment number matches + if ((line == 20) + && ((leptonVersion == leptonVersion_3_0_shutter) + || (leptonVersion == leptonVersion_3_5_shutter))) + { + byte segment = (leptonFrame[0] >> 4); + if (segment == 0) + return SEGMENT_INVALID; + if (segment != seg) + return SEGMENT_ERROR; + } + + //Everything worked + return NONE; +} + +/* Get one frame of raw values from the lepton */ +void lepton_getRawValues() +{ + byte line, error, segmentNumbers; + + //Determine number of segments + if ((leptonVersion == leptonVersion_3_0_shutter) + || (leptonVersion == leptonVersion_3_5_shutter)) + segmentNumbers = 4; + else + segmentNumbers = 1; + + //Begin SPI Transmission + lepton_begin(); + + //Go through the segments + for (byte segment = 1; segment <= segmentNumbers; segment++) + { + //Reset error counter for each segment + error = 0; + + //Go through one segment, equals 60 lines of 80 values + do + { + for (line = 0; line < 60; line++) + { + //Maximum error count + if (error == 255) + { + //If main menu should be entreed + if (showMenu == showMenu_desired) + { + lepton_end(); + return; + } + + //Reset segment + segment = 1; + //Reset error + error = 0; + //Reset Lepton SPI + lepton_reset(); + //Restart at line 0 + break; + } + + //Get a package from the lepton + LeptonReadError retVal = lepton_getPackage(line, segment); + + //If everythin worked, continue + if (retVal == NONE) + if (savePackage(line, segment)) + continue; + + //Raise lepton error + error++; + + //Stabilize framerate + uint32_t time = micros(); + while ((micros() - time) < 800) + __asm__ volatile ("nop"); + + //Restart at line 0 + break; + } + } while (line != 60); + } + + //End SPI Transmission + lepton_end(); +} + +/* Select I2C Register on the Lepton */ +void lepton_setReg(byte reg) +{ + Wire.beginTransmission(0x2A); + Wire.write(reg >> 8 & 0xff); + Wire.write(reg & 0xff); + Wire.endTransmission(); +} + +/* Read I2C Register on the lepton */ +int lepton_readReg(byte reg) +{ + uint16_t reading; + lepton_setReg(reg); + Wire.requestFrom(0x2A, 2); + reading = Wire.read(); + reading = reading << 8; + reading |= Wire.read(); + return reading; +} + +/* + First reads the DATA Length Register (0x006) + Then reads the acutal DATA Registers: + DATA 0 Register, DATA 1 Register etc. + + If the request length is smaller that the DATA Length it returns -1 + */ +int lepton_i2c_read_data_register(byte *data, int data_length_request) +{ + + int data_length_recv; + int data_read; + // Wait for execution of the command + while (lepton_readReg(0x2) & 0x01) + ; + + // Read the data length (should be 4) + data_length_recv = lepton_readReg(0x6); + + if (data_length_recv < data_length_request) + { + return -1; + } + + Wire.requestFrom(0x2A, data_length_request); + data_read = Wire.readBytes(data, data_length_request); + Wire.endTransmission(); + return data_read; +} + +/* + First writes the actual DATA Registers (0x0008). + Then writes the DATA Length Register (0x006) + */ +byte lepton_i2c_write_data_register(byte *data, int length) +{ + + // Wait for execution of the command + while (lepton_readReg(0x2) & 0x01) + ; + + Wire.beginTransmission(0x2A); + // CCI/TWI Data Registers is at 0x0008 + Wire.write(0x00); + Wire.write(0x08); + for (int i = 0; i < length; i++) + { + Wire.write(data[i]); + } + Wire.endTransmission(); + + // CCI/TWI Data Length Register is at 0x0006 + Wire.beginTransmission(0x2A); + Wire.write(0x00); + Wire.write(0x06); + //Data length bytes + Wire.write((length >> 8) & 0xFF); + Wire.write(length & 0xFF); + return Wire.endTransmission(); +} + +/* + Write the command words (16-bit) via I2C + */ +byte lepton_i2c_execute_command(byte cmdbyte0, byte cmdbyte1) +{ + // Wait for execution of the command + while (lepton_readReg(0x2) & 0x01) + ; + + Wire.beginTransmission(0x2A); + Wire.write(0x00); + Wire.write(0x04); //COMMANDID_REG + Wire.write(cmdbyte0); + Wire.write(cmdbyte1); + return Wire.endTransmission(); +} + +/* Trigger a flat-field-correction on the Lepton */ +bool lepton_ffc(bool message, bool switch_gain) +{ + //Show a message for main menu + if (message) + { + //When in manual temperature mode, a FFC is not possible + if ((!autoMode) && (!switch_gain)) + { + showFullMessage((char*) "No FFC in manual mode", true); + delay(1000); + return false; + } + showFullMessage((char*) "Performing FFC..", true); + } + + byte error; + + if ((leptonVersion == leptonVersion_2_5_shutter) + || (leptonVersion == leptonVersion_3_5_shutter)) + { + // For radiometric Lepton, send RAD FFC command + // RAD FFC Normalization Command + // 0x0E00 (SDK Module ID) + 0x2C (SDK Command ID) + 0x2 (RUN operation) + 0x4000 (Protection Bit) = 0x4E2E + error = lepton_i2c_execute_command(0x4E, 0x2E); + } + else + { + //For all others, send normal FFC command + // SYS Run FFC Normalization + // 0x0200 (SDK Module ID) + 0x40 (SDK Command ID) + 0x2 (RUN operation) + 0x0000 (Protection Bit) = 0x0242 + error = lepton_i2c_execute_command(0x02, 0x42); + } + + //Wait some time when in main menu + if (message) + delay(2000); + + return error; +} + +/* Get the spotmeter value on a radiometric lepton */ +float lepton_spotTemp() +{ + //Get RAD spotmeter value + Wire.beginTransmission(0x2A); + Wire.write(0x00); + Wire.write(0x04); + Wire.write(0x4E); + Wire.write(0xD0); + byte error = Wire.endTransmission(); + + //Lepton I2C error, set diagnostic + if (error != 0) + { + setDiagnostic(diag_spot); + return 0; + } + + //Transfer the new package + Wire.beginTransmission(0x2A); + while (lepton_readReg(0x2) & 0x01) + ; + Wire.requestFrom(0x2A, lepton_readReg(0x6)); + byte response[8]; + Wire.readBytes(response, 8); + Wire.endTransmission(); + + //Calculate spot temperature in Kelvin + float spotTemp = (response[0] * 256.0) + response[1]; + //Multiply by correction factor + if(gainMode == lepton_3_5_gain_high) + { + spotTemp *= 0.01; + } + else + { + spotTemp *= 0.1; + } + //Convert to celsius + spotTemp -= 273.15; + + return spotTemp; +} + +/* Set the shutter operation to manual/auto */ +void lepton_ffcMode(bool automatic) +{ + //If there is no shutter, return + if (leptonShutter == leptonShutter_none) + return; + + //When enabling auto FFC, check for some factors + if ((automatic) + && ((hotColdMode != hotColdMode_disabled) || (autoMode == false) + || (limitsLocked == true))) + return; + + //Contains the standard values for the FFC mode + byte package[] = + { automatic, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, + 147, 4, 0, 0, 0, 0, 0, 44, 1, 52, 0 }; + + lepton_i2c_write_data_register(package, sizeof(package)); + + // SYS FFC Mode Control Command + // 0x0200 (SDK Module ID) + 0x3C (SDK Command ID) + 0x1 (SET operation) + 0x0000 (Protection Bit) = 0x023D + lepton_i2c_execute_command(0x02, 0x3D); + + //Set shutter mode + if (automatic) + leptonShutter = leptonShutter_auto; + else + leptonShutter = leptonShutter_manual; +} + +/* Checks the Lepton hardware revision */ +void lepton_version() +{ + + // OEM FLIR Systems Part Number + // 0x0800 (SDK Module ID) + 0x1C (SDK Command ID) + 0x0 (GET operation) + 0x4000 (Protection Bit) = 0x481C + byte error = lepton_i2c_execute_command(0x48, 0x1C); + + //Lepton I2C error, set diagnostic + if (error != 0) + { + setDiagnostic(diag_lep_conf); + leptonVersion = leptonVersion_2_0_noShutter; + return; + } + + char leptonhw[33]; + lepton_i2c_read_data_register((byte*) leptonhw, 32); + + //Detected Lepton2 Non-Shuttered + if (strstr(leptonhw, "05-060340") != NULL) + { + leptonVersion = leptonVersion_2_0_noShutter; + leptonShutter = leptonShutter_none; + } + + //Detected Lepton2 Shuttered + else if (strstr(leptonhw, "05-060950") != NULL) + { + leptonVersion = leptonVersion_2_0_shutter; + leptonShutter = leptonShutter_auto; + } + + //Detected Lepton2.5 Shuttered (Radiometric) + else if (strstr(leptonhw, "05-070360") != NULL) + { + leptonVersion = leptonVersion_2_5_shutter; + leptonShutter = leptonShutter_autoRAD; + } + + //Detected Lepton3 Shuttered + else if (strstr(leptonhw, "05-070620") != NULL) + { + leptonVersion = leptonVersion_3_0_shutter; + leptonShutter = leptonShutter_auto; + } + + //Detected Lepton3.5 Shuttered (Radiometric) + else if (strstr(leptonhw, "05-070170") != NULL) + { + leptonVersion = leptonVersion_3_5_shutter; + leptonShutter = leptonShutter_autoRAD; + } + + //Detected unknown Lepton2 No-Shutter + else + { + leptonVersion = leptonVersion_2_0_noShutter; + leptonShutter = leptonShutter_none; + } + + //Write to EEPROM + EEPROM.write(eeprom_leptonVersion, leptonVersion); +} + +/* + * Set the SYS Gain Mode + * 0: high mode (hardware default), + * 1: low mode, + * 2: auto mode + * The measurement range for the Lepton 3.5 is (see datasheet for details): + * High Gain Mode: -10 to +140 deg C + * Low Gain Mode: -10 to +450 deg C + * + */ +void lepton_set_sys_gain_mode(byte mode) +{ + if (mode > 2) + { + return; + } + + //The enum value is the LSB of DATA0 + byte data[4] = + { 0x00, mode, 0x00, 0x00 }; + lepton_i2c_write_data_register(data, 4); + + // Execute the SYS Gain Mode Set Command, so that the module applies the values + // 0x0200 (SDK Module ID) + 0x48 (SDK Command ID) + 0x1 (SET operation) + 0x0000 (Protection Bit) = 0x0249. + lepton_i2c_execute_command(0x02, 0x49); +} + +/* + * Sets the SYS Gain Mode to high gain mode + */ +void lepton_set_sys_gain_high() +{ + lepton_set_sys_gain_mode(0x00); +} + +/* + * Sets the SYS Gain Mode to low gain mode + */ +void lepton_set_sys_gain_low() +{ + lepton_set_sys_gain_mode(0x01); +} + +/* + * Sets the SYS Gain Mode to auto mode + */ +void lepton_set_sys_gain_auto() +{ + lepton_set_sys_gain_mode(0x02); +} + +/* + * Returns the SYS Gain Mode + * 0: high mode (hardware default), + * 1: low mode, + * 2: auto mode + * The measurement range for the Lepton 3.5 is (see datasheet for details): + * High Gain Mode: -10 to +140 deg C + * Low Gain Mode: -10 to +450 deg C + * + * Returns -1 if the gain mode could not be read + */ +int lepton_get_sys_gain_mode() +{ + + byte data[4]; + + //SYS Gain Mode Get Command + // 0x0200 (SDK Module ID) + 0x48 (SDK Command ID) + 0x0 (GET operation) + 0x0000 (Protection Bit) = 0x0248. + lepton_i2c_execute_command(0x02, 0x48); + lepton_i2c_read_data_register(data, 4); + + //The enum value is the LSB of DATA0 + return data[1]; +} + +/* + * Returns the RAD T-Linear Resolution + * 0: 100 + * 1: 10 + */ +byte lepton_get_rad_tlinear_resolution() +{ + + byte data[4]; + + // RAD T-Linear Resolution Get Command + // 0x0E00 (SDK Module ID) + 0xC4 (SDK Command ID) + 0x0 (GET operation) + 0x4000 (Protection Bit) = 0x4EC4. + lepton_i2c_execute_command(0x4E, 0xC4); + lepton_i2c_read_data_register(data, 4); + + //The enum value is the LSB of DATA0 + return data[1]; +} + +/* + * Returns the RAD T-Linear Resolution Factor as a float + * Returns -1 if the value could not be read + */ +float lepton_get_resolution() +{ + byte resolution = lepton_get_rad_tlinear_resolution(); + if (resolution == 0) + { + return 0.1; + } + else + { + return 0.01; + } +} + +/* + * Sets the RAD T-Linear Resolution + * resolution: 0 = factor 100 + * resolution: 1 = factor 10 + * + * You need to set this to factor 10 for temperature over 382.2 deg C + * The maximum temperature value of the 16-bit is 65535. + * For factor 100: The maximum is 655.35 Kelvin which equals to 655.35 - 273.15 = 382.2 deg C + * For factor 10: The maximum is 6553.5 Kelvin which equals to 6553.5 - 273.15 = 6280.35 deg C + */ +void lepton_set_rad_tlinear_resolution(byte resolution) +{ + if (resolution > 1) + { + return; + } + + //The enum value is the LSB of DATA0 + byte data[4] = + { 0x00, resolution, 0x00, 0x00 }; + lepton_i2c_write_data_register(data, 4); + + // RAD T-Linear Resolution Set Command + // 0x0E00 (SDK Module ID) + 0xC4 (SDK Command ID) + 0x1 (SET operation) + 0x4000 (Protection Bit) = 0x4EC5. + lepton_i2c_execute_command(0x4E, 0xC5); +} + +/* + * Sets the RAD T-Linear Resolution to 10 (factor 0.1) + */ +void lepton_set_rad_tlinear_10() +{ + lepton_set_rad_tlinear_resolution(0); +} + +/* + * Sets the RAD T-Linear Resolution to 100 (factor 0.01) + */ +void lepton_set_rad_tlinear_100() +{ + lepton_set_rad_tlinear_resolution(1); +} + +/* Switch to low gain on Lepton3.5 (-10 to +450 deg C) */ +void lepton_3_5_set_low_gain() +{ + lepton_set_sys_gain_low(); + lepton_set_rad_tlinear_10(); + calSlope = 0.1; + gainMode = lepton_3_5_gain_low; +} + +/* Switch to high gain on Lepton3.5 (-10 to +140 deg C) */ +void lepton_3_5_set_high_gain() +{ + lepton_set_sys_gain_high(); + lepton_set_rad_tlinear_100(); + calSlope = 0.01; + gainMode = lepton_3_5_gain_high; +} + +/* Init the FLIR Lepton LWIR sensor */ +void lepton_init() +{ + //Check the Lepton HW Revision + lepton_version(); + + //For radiometric Lepton, set calibration to done + if ((leptonVersion == leptonVersion_2_5_shutter) + || (leptonVersion == leptonVersion_3_5_shutter)) + calStatus = cal_standard; + + //Otherwise init warmup timer + else + { + //Set the calibration timer + calTimer = millis(); + //Set calibration status to warmup if not coming from mass storage + calStatus = cal_warmup; + } + + //Set the compensation value to zero + calComp = 0; + + //Activate auto mode + autoMode = true; + //Deactivate limits Locked + limitsLocked = false; + + //Check if SPI works + lepton_begin(); + do + { + SPI.transfer(leptonFrame, 164); + } + //Repeat as long as the frame is not valid, equals sync + while (((leptonFrame[0] & 0x0F) == 0x0F) && ((millis() - calTimer) < 1000)); + lepton_end(); + + //If sync not received after a second, set diagnostic + if ((leptonFrame[0] & 0x0F) == 0x0F) + setDiagnostic(diag_lep_data); +} diff --git a/Firmware_V2/src/hardware/lepton.h b/Firmware_V2/src/hardware/lepton.h new file mode 100644 index 0000000..72d4bf4 --- /dev/null +++ b/Firmware_V2/src/hardware/lepton.h @@ -0,0 +1,49 @@ +/* +* +* LEPTON - Access the FLIR Lepton LWIR module +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef LEPTON_H +#define LEPTON_H + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +//Lepton frame error return +enum LeptonReadError { + NONE, DISCARD, SEGMENT_ERROR, ROW_ERROR, SEGMENT_INVALID +}; + +/*########################## PUBLIC PROCEDURES ################################*/ + +void lepton_begin(); +void lepton_end(); +bool lepton_ffc(bool message = false, bool switch_gain = false); +void lepton_ffcMode(bool automatic); +LeptonReadError lepton_getPackage(byte line, byte seg); +void lepton_getRawValues(); +void lepton_init(); +int lepton_readReg(byte reg); +void lepton_reset(); +void lepton_setReg(byte reg); +float lepton_spotTemp(); +void lepton_version(); +bool savePackage(byte line, byte segment = 0); +void lepton_set_sys_gain_high(); +void lepton_set_sys_gain_low(); +void lepton_set_sys_gain_auto(); +int lepton_get_sys_gain_mode(); +float lepton_get_resolution(); +void lepton_3_5_set_low_gain(); +void lepton_3_5_set_high_gain(); + +#endif /* LEPTON_H */ diff --git a/Firmware_V2/src/hardware/massstorage.cpp b/Firmware_V2/src/hardware/massstorage.cpp new file mode 100644 index 0000000..f18571f --- /dev/null +++ b/Firmware_V2/src/hardware/massstorage.cpp @@ -0,0 +1,115 @@ +/* + * + * MASS STORAGE - Mass storage mode to connect the internal storage to the PC + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include + +/*################# DATA TYPES, CONSTANTS & MACRO DEFINITIONS #################*/ + +#define CPU_RESTART_ADDR (uint32_t *)0xE000ED0C +#define CPU_RESTART_VAL 0x5FA0004 +#define CPU_RESTART() (*CPU_RESTART_ADDR = CPU_RESTART_VAL); +#define IS_JUMP_TO_OFFSET_FLAG_SET() (eeprom_read_byte(0x00) == 0xAE) +#define CLEAR_JUMP_FLAG() eeprom_write_byte(0x00, 0) +#define SET_JUMP_FLAG() eeprom_write_byte(0x00, 0xAE) + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +//The assembly code hook must be run inside a C, not C++ function + +/* Load stack pointer and program counter from start of new program */ +void jumpToApplicationAt0x38980() { + asm("movw r0, #0x8880"); + asm("movt r0, #0x0003"); + asm("ldr sp, [r0]"); + asm("ldr pc, [r0, #4]"); +} + +/* Set peripherals back to reset conditions */ +void resetPeripherals() { + //Set (some of) USB back to normal + NVIC_DISABLE_IRQ(IRQ_USBOTG); + NVIC_CLEAR_PENDING(IRQ_USBOTG); + SIM_SCGC4 &= ~(SIM_SCGC4_USBOTG); + + //Disable all GPIO interrupts + NVIC_DISABLE_IRQ(IRQ_PORTA); + NVIC_DISABLE_IRQ(IRQ_PORTB); + NVIC_DISABLE_IRQ(IRQ_PORTC); + NVIC_DISABLE_IRQ(IRQ_PORTD); + NVIC_DISABLE_IRQ(IRQ_PORTE); + + //Set (some of) ADC1 back to normal + //Wait until calibration is complete + while (ADC1_SC3 & ADC_SC3_CAL) + ; + //Clear flag if calibration failed + if (ADC1_SC3 & 1 << 6) + ADC1_SC3 |= 1 << 6; + + //Clear conversion complete flag (which could trigger ISR otherwise) + if (ADC1_SC1A & 1 << 7) + ADC1_SC1A |= 1 << 7; + + //Set some clocks back to default/reset settings + MCG_C1 = MCG_C1_CLKS(2) | MCG_C1_FRDIV(4); + SIM_CLKDIV1 = 0; + SIM_CLKDIV2 = 0; +} + +/* Look for the condition that indicates we want to jump to the application with offset */ +void startup_late_hook(void) { + if (IS_JUMP_TO_OFFSET_FLAG_SET()) { + //Clear the condition + CLEAR_JUMP_FLAG(); + //Set peripherals (mostly) back to normal then jump + __disable_irq(); + resetPeripherals(); + jumpToApplicationAt0x38980(); + } +} + +/* Jump to the mass storage section */ +void restartAndJumpToApp(void) { + SET_JUMP_FLAG(); + CPU_RESTART() + ; +} + +/* Go into mass storage mode */ +void massStorage() { + //Thermocam V4 or DIY-Thermocam V2 does not support this + if ((mlx90614Version == mlx90614Version_old) || (teensyVersion == teensyVersion_new)) + return; + + //Check if the user really wants to do it + if (!massStoragePrompt()) + return; + //Show message + showFullMessage((char*) "Disconnect USB cable to return"); + //Set marker + EEPROM.write(eeprom_massStorage, eeprom_setValue); + //Wait some time + delay(1500); + //Jump to mass storage mode + restartAndJumpToApp(); +} diff --git a/Firmware_V2/src/hardware/massstorage.h b/Firmware_V2/src/hardware/massstorage.h new file mode 100644 index 0000000..6035245 --- /dev/null +++ b/Firmware_V2/src/hardware/massstorage.h @@ -0,0 +1,27 @@ +/* +* +* MASS STORAGE - Mass storage mode to connect the internal storage to the PC +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef MASSSTORAGE_H +#define MASSSTORAGE_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void jumpToApplicationAt0x38980(); +void massStorage(); +void resetPeripherals(); +void restartAndJumpToApp(void); +void startup_late_hook(void); + +#endif /* MASSSTORAGE_H */ diff --git a/Firmware_V2/src/hardware/mlx90614.cpp b/Firmware_V2/src/hardware/mlx90614.cpp new file mode 100644 index 0000000..a042716 --- /dev/null +++ b/Firmware_V2/src/hardware/mlx90614.cpp @@ -0,0 +1,407 @@ +/* +* +* MLX90614 - Access the MLX90614 spot IR sensor +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +/*################# DATA TYPES, CONSTANTS & MACRO DEFINITIONS #################*/ + +//Registers +#define mlx90614_EEPROM 0x00 +#define mlx90614_RAM 0x5A +#define mlx90614_AmbientTemp 0x06 +#define mlx90614_ObjectTemp 0x07 +#define mlx90614_toMax 0x20 +#define mlx90614_toMin 0x21 +#define mlx90614_PWMCTRL 0x22 +#define mlx90614_TaRange 0x23 +#define mlx90614_Emissivity 0x24 +#define mlx90614_Filter 0x25 + +/*######################### STATIC DATA DECLARATIONS ##########################*/ + +//CRC Table to calculate I2C PEC +static const unsigned char mlx90614_crcTable[] = { 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, 0x70, 0x77, 0x7E, +0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, +0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, 0xC7, +0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, +0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, +0x0A, 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, +0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, +0xDA, 0xD3, 0xD4, 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, 0x19, 0x1E, 0x17, 0x10, +0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, +0x7F, 0x6A, 0x6D, 0x64, 0x63, 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, 0xAE, 0xA9, +0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, +0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 }; + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Calculate CRC8 checksum for PEC */ +char mlx90614_crc8(byte buffer[], int len) { + unsigned char m_crc = 0; + int m_byte_count = 0; + for (int i = 0; i < len; i++) { + m_byte_count++; + m_crc = mlx90614_crcTable[m_crc ^ buffer[i]]; + } + return m_crc; +} + +/* Receive data from the RAM over I2C */ +uint16_t mlx90614_getRawData(boolean TaTo, boolean* check) { + // Store the two relevant bytes of data for temperature + byte dataLow, dataHigh; + Wire.beginTransmission(mlx90614_RAM); + //Measure Ambient Temp + if (TaTo) + Wire.send(mlx90614_AmbientTemp); + //Measure Object Temp + else { + Wire.send(mlx90614_ObjectTemp); + } + Wire.endTransmission(I2C_NOSTOP); + //Receive data + Wire.requestFrom(mlx90614_RAM, 3); + if (Wire.available() != 3) + *check = 0; + dataLow = Wire.read(); + if (dataLow == -1) + *check = 0; + dataHigh = Wire.read(); + if (dataHigh == -1) + *check = 0; + byte pec = Wire.read(); + if (pec == -1) + *check = 0; + Wire.endTransmission(); + //Convert data + uint16_t tempData = (((dataHigh & 0x007F) << 8) + dataLow); + return tempData; +} + +/* Send data to the EEPROM over I2C*/ +void mlx90614_send(byte address, byte LSB, byte MSB) { + Wire.beginTransmission(mlx90614_EEPROM); + Wire.write(address); + Wire.write(LSB); + Wire.write(MSB); + byte msg[4] = { 0x00, address, LSB, MSB }; + byte PEC = mlx90614_crc8(msg, 4); + Wire.write(PEC); + Wire.endTransmission(); +} + +/* Receive data from the EEPROM over I2C */ +uint16_t mlx90614_receive(byte address, byte* error) { + Wire.beginTransmission(mlx90614_EEPROM); + Wire.write(address); + Wire.endTransmission(I2C_NOSTOP); + Wire.requestFrom(mlx90614_EEPROM, 3); + byte LSB = Wire.read(); + byte MSB = Wire.read(); + Wire.read(); + //Check if the transmission worked + if (error != NULL) + *error = Wire.endTransmission(); + else + Wire.endTransmission(); + //Calc 16-bit value and return + uint16_t regValue = (((MSB) << 8) + LSB); + return regValue; +} + +/* Measures the ambient or object temperature */ +float mlx90614_measure(boolean TaTo, boolean* check) { + //Get the raw value from the sensor + uint16_t rawData = mlx90614_getRawData(TaTo, check); + + //Convert to Kelvin + float tempData = (rawData * 0.02) - 0.01; + + //Convert to Celsius + tempData -= 273.15; + + //TaTo is one, check for ambient borders + if ((TaTo) && ((tempData < -40) || (tempData > 125))) + *check = false; + + //TaTo is zero, check for object borders + else if ((tempData < -70) || (tempData > 380)) + *check = false; + + //Return temperature in Celsius + return tempData; +} + +/* Set the maximum temp to 380°C */ +void mlx90614_setMax() { + byte count = 0; + do { + //Set it to zero first + mlx90614_send(mlx90614_toMax, 0x00, 0x00); + delay(100); + //Then write the new value + mlx90614_send(mlx90614_toMax, 0x23, 0xFF); + delay(100); + //If we failed after 10 retries, set error and continue + if (count == 10) { + setDiagnostic(diag_spot); + return; + } + count++; + } while (mlx90614_receive(mlx90614_toMax) != 65315); +} + +/* Check if the maximum temp is 380°C */ +boolean mlx90614_checkMax() { + //Check if maximum temp setting is correct + if (mlx90614_receive(mlx90614_toMax) != 65315) + return 0; + //Everything was fine + return 1; +} + +/* Set the minimum temp to -70°C */ +void mlx90614_setMin() { + byte count = 0; + do { + //Set it to zero first + mlx90614_send(mlx90614_toMin, 0x00, 0x00); + delay(100); + //Then write the new value + mlx90614_send(mlx90614_toMin, 0x5B, 0x4F); + delay(100); + //If we failed after 10 retries, set error and continue + if (count == 10) { + setDiagnostic(diag_spot); + return; + } + count++; + } while (mlx90614_receive(mlx90614_toMin) != 20315); +} + +/* Check if the minimum temp is -70°C */ +boolean mlx90614_checkMin() { + //Check if minimum temp setting is correct + if (mlx90614_receive(mlx90614_toMin) != 20315) + return 0; + //Everything was fine + return 1; +} + +/* Set the emissivity to 0.9 */ +void mlx90614_setEmissivity() { + byte count = 0; + do { + //Set it to zero first + mlx90614_send(mlx90614_Emissivity, 0x00, 0x00); + delay(100); + //Then write the new value + mlx90614_send(mlx90614_Emissivity, 0x66, 0xE6); + delay(100); + //If we failed after 10 retries, set error and continue + if (count == 10) { + setDiagnostic(diag_spot); + return; + } + count++; + } while (mlx90614_receive(mlx90614_Emissivity) != 58982); +} + +/* Check if the emissivity is set to 0.9 */ +boolean mlx90614_checkEmissivity() { + //Check if emissivity setting is correct + if (mlx90614_receive(mlx90614_Emissivity) != 58982) + return 0; + //Everything was fine + return 1; +} + +/* Check if the filter settings match for noise-reduction (50% IIR, max settling time) */ +void mlx90614_setFilter() { + byte MSB, LSB; + uint16_t filterSettings; + //Old MLX90614 with gain factor of 12.5 + if (mlx90614Version == mlx90614Version_old) { + filterSettings = 40820; + MSB = 0x74; + LSB = 0x9F; + } + + //New MLX90614 with gain factor of 100 + else { + filterSettings = 46964; + MSB = 0x74; + LSB = 0xB7; + } + //Try to set the new filter settings + byte count = 0; + do { + //Set it to zero first + mlx90614_send(mlx90614_Filter, 0x00, 0x00); + delay(100); + //Then write the new value + mlx90614_send(mlx90614_Filter, MSB, LSB); + delay(100); + //If we failed after 10 retries, set error and continue + if (count == 10) { + setDiagnostic(diag_spot); + return; + } + count++; + } while (mlx90614_receive(mlx90614_Filter) != filterSettings); +} + +/* Check if the filter settings match for fast measurement */ +boolean mlx90614_checkFilter() { + uint16_t filter = mlx90614_receive(mlx90614_Filter); + //Old MLX90614 with gain factor of 12.5 + if ((mlx90614Version == mlx90614Version_old) && (filter != 40820)) + return 0; + //New MLX90614 with gain factor of 100 + else if ((mlx90614Version == mlx90614Version_new) && (filter != 46964)) + return 0; + ///Everything was fine + return 1; +} + +/* Read and return the ambient temperature */ +float mlx90614_getAmb() { + float ambTemp; + boolean check = 1; + byte count = 0; + + //Repeat until valid value + do { + ambTemp = mlx90614_measure(1, &check); + //If we cannot connect,continue + if (count == 100) + return 0; + count++; + delay(10); + } while (check == 0); + + //Return ambient temperature + return ambTemp; +} + +/* Read and return the object temperature */ +float mlx90614_getTemp() { + float objTemp; + boolean check = 1; + byte count = 0; + + //Repeat until valid value + do { + objTemp = mlx90614_measure(0, &check); + //If we cannot connect, continue + if (count == 100) + return 0; + count++; + delay(10); + } while (check == 0); + + //Return object temperature + return objTemp; +} + +/* Initializes the sensor */ +void mlx90614_init() { + byte error; + + //For radiometric Lepton, do not init the MLX90614 + if ((leptonVersion == leptonVersion_2_5_shutter) || (leptonVersion == leptonVersion_3_5_shutter)) { + mlx90614Version = mlx90614Version_new; + return; + } + + //Get filter settings + uint16_t filter = mlx90614_receive(mlx90614_Filter, &error); + + //If I2C connection is not working, set diagnostic + if (error != 0) + { + setDiagnostic(diag_spot); + return; + } + + //Calc MLX90614 version + mlx90614Version = (filter >> 13) & 1; + + //If first start has been completed before, do checks + if (EEPROM.read(eeprom_firstStart) == eeprom_setValue) { + //Check Filter Temp + if (mlx90614_checkFilter() == 0) { + mlx90614_setFilter(); + setDiagnostic(diag_spot); + } + //Check Min Temp + if (mlx90614_checkMin() == 0) { + mlx90614_setMin(); + setDiagnostic(diag_spot); + } + //Check Max Temp + if (mlx90614_checkMax() == 0) { + mlx90614_setMax(); + setDiagnostic(diag_spot); + } + //Check Emissivity + if (mlx90614_checkEmissivity() == 0) { + mlx90614_setEmissivity(); + setDiagnostic(diag_spot); + } + //Show message, if one of the settings had to be re-written + if (!checkDiagnostic(diag_spot)) { + showFullMessage((char*)"Spot EEPROM updated, restart device"); + while (1); + } + } + + //Check if the object temp is valid + boolean check = 1; + byte count = 0; + do { + mlx90614_measure(0, &check); + //If we cannot connect, set error and continue + if (count == 100) { + setDiagnostic(diag_spot); + break; + } + count++; + delay(10); + } while (check == 0); + + //Check if the ambient temp is valid + check = 1; + count = 0; + do { + mlx90614_measure(1, &check); + //If we cannot connect, set error and continue + if (count == 100) { + setDiagnostic(diag_spot); + break; + } + count++; + delay(10); + } while (check == 0); +} diff --git a/Firmware_V2/src/hardware/mlx90614.h b/Firmware_V2/src/hardware/mlx90614.h new file mode 100644 index 0000000..c069bcb --- /dev/null +++ b/Firmware_V2/src/hardware/mlx90614.h @@ -0,0 +1,38 @@ +/* +* +* MLX90614 - Access the MLX90614 spot IR sensor +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef MLX90614_H +#define MLX90614_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +boolean mlx90614_checkEmissivity(); +boolean mlx90614_checkFilter(); +boolean mlx90614_checkMax(); +boolean mlx90614_checkMin(); +char mlx90614_crc8(byte buffer[], int len); +float mlx90614_getAmb(); +uint16_t mlx90614_getRawData(boolean TaTo, boolean* check); +float mlx90614_getTemp(); +void mlx90614_init(); +float mlx90614_measure(boolean TaTo, boolean* check); +uint16_t mlx90614_receive(byte address, byte* error = NULL); +void mlx90614_send(byte address, byte LSB, byte MSB); +void mlx90614_setEmissivity(); +void mlx90614_setFilter(); +void mlx90614_setMax(); +void mlx90614_setMin(); + +#endif /* MLX90614_H */ diff --git a/Firmware_V2/src/hardware/sd.cpp b/Firmware_V2/src/hardware/sd.cpp new file mode 100644 index 0000000..1c573db --- /dev/null +++ b/Firmware_V2/src/hardware/sd.cpp @@ -0,0 +1,406 @@ +/* +* +* SD - Methods to access the internal SD storage +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include + +/*######################### STATIC DATA DECLARATIONS ##########################*/ + +//Card +static uint32_t cardSizeBlocks; +static uint16_t cardCapacityMB; +//Cache for SD block +static cache_t cache; +//Fake disk geometry +static uint8_t numberOfHeads; +static uint8_t sectorsPerTrack; +//MBR information +static uint8_t partType; +static uint32_t relSector; +static uint32_t partSize; +//FAT parameters +static uint16_t reservedSectors; +static uint8_t sectorsPerCluster; +static uint32_t fatStart; +static uint32_t fatSize; +static uint32_t dataStart; +//Cconstants for file system structure +static uint16_t const BU16 = 128; +static uint16_t const BU32 = 8192; +//Strings needed in file system structures +static char noName[] = "NO NAME "; +static char fat16str[] = "FAT16 "; +static char fat32str[] = "FAT32 "; + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Returns the free space on the card in KB */ +uint32_t getSDSpace() { + startAltClockline(true); + uint32_t freeKB = sd.vol()->freeClusterCount(); + freeKB *= sd.vol()->blocksPerCluster() / 2; + endAltClockline(); + return freeKB; +} + +/* Returns the internal sd size in MB */ +uint16_t getCardSize() { + //Start Card + startAltClockline(); + //Get card info + cardSizeBlocks = sd.card()->cardSize(); + uint16_t cardSize = (cardSizeBlocks + 2047) / 2048; + //End Card + endAltClockline(); + return cardSize; +} + +//Refresh free space of the internal space +void refreshFreeSpace() { + uint16_t cardSize = getCardSize(); + if (cardSize != 0) + sdInfo = String((int)(getSDSpace() / 1024)) + "/" + String((int)(cardSize - 1)) + " MB"; +} + +/* Timestamp set for SDFat */ +void dateTime(uint16_t* date, uint16_t* time) { + // return date using FAT_DATE macro to format fields + *date = FAT_DATE(year(), month(), day()); + // return time using FAT_TIME macro to format fields + *time = FAT_TIME(hour(), minute(), second()); +} + +/* Begin the SD card */ +bool beginSD() +{ + bool ret; + + //Start alternative clockline + startAltClockline(); + + //Try to begin the SD card at full speed + ret = sd.begin(pin_sd_cs, SPI_FULL_SPEED); + + //End alternative clockline + endAltClockline(); + + //Return result + return ret; +} + +/* Initializes the SD card */ +void initSD() { + //Storage info string + sdInfo = " - / - MB"; + + //Check if the sd card works + if (beginSD()) + refreshFreeSpace(); + + //Not working, set diagnosis on DIY-Thermocam V1 only + else if ((mlx90614Version == mlx90614Version_new) && ( + teensyVersion == teensyVersion_old)) + setDiagnostic(diag_sd); + + //Set SD Timestamp to current time + SdFile::dateTimeCallback(dateTime); +} + +/* Zero cache and optionally set the sector signature */ +void clearCache(uint8_t addSig) { + memset(&cache, 0, sizeof(cache)); + if (addSig) { + cache.mbr.mbrSig0 = BOOTSIG0; + cache.mbr.mbrSig1 = BOOTSIG1; + } +} + +/* Write cached block to the card */ +uint8_t writeCache(uint32_t lbn) { + return sd.card()->writeBlock(lbn, cache.data); +} + +/* Return cylinder number for a logical block number */ +uint16_t lbnToCylinder(uint32_t lbn) { + return lbn / (numberOfHeads * sectorsPerTrack); +} + +/* Return head number for a logical block number */ +uint8_t lbnToHead(uint32_t lbn) { + return (lbn % (numberOfHeads * sectorsPerTrack)) / sectorsPerTrack; +} + +/* Return sector number for a logical block number */ +uint8_t lbnToSector(uint32_t lbn) { + return (lbn % sectorsPerTrack) + 1; +} + +/* Format and write the Master Boot Record */ +void writeMbr() { + clearCache(true); + part_t* p = cache.mbr.part; + p->boot = 0; + uint16_t c = lbnToCylinder(relSector); + p->beginCylinderHigh = c >> 8; + p->beginCylinderLow = c & 0XFF; + p->beginHead = lbnToHead(relSector); + p->beginSector = lbnToSector(relSector); + p->type = partType; + uint32_t endLbn = relSector + partSize - 1; + c = lbnToCylinder(endLbn); + if (c <= 1023) { + p->endCylinderHigh = c >> 8; + p->endCylinderLow = c & 0XFF; + p->endHead = lbnToHead(endLbn); + p->endSector = lbnToSector(endLbn); + } + else { + // Too big flag, c = 1023, h = 254, s = 63 + p->endCylinderHigh = 3; + p->endCylinderLow = 255; + p->endHead = 254; + p->endSector = 63; + } + p->firstSector = relSector; + p->totalSectors = partSize; + writeCache(0); +} + +/* Zero FAT and root dir area on SD */ +void clearFatDir(uint32_t bgn, uint32_t count) { + clearCache(false); + sd.card()->writeStart(bgn, count); + for (uint32_t i = 0; i < count; i++) { + sd.card()->writeData(cache.data); + } + sd.card()->writeStop(); +} + +/* Generate serial number from card size and micros since boot */ +uint32_t volSerialNumber() { + return (cardSizeBlocks << 8) + micros(); +} + +/* Format with FAT16 file system for 2GB internal storage */ +void formatFAT16() +{ + uint32_t nc; + for (dataStart = 2 * BU16;; dataStart += BU16) { + nc = (cardSizeBlocks - dataStart) / sectorsPerCluster; + fatSize = (nc + 2 + 255) / 256; + uint32_t r = BU16 + 1 + 2 * fatSize + 32; + if (dataStart < r) { + continue; + } + relSector = dataStart - r + BU16; + break; + } + reservedSectors = 1; + fatStart = relSector + reservedSectors; + partSize = nc * sectorsPerCluster + 2 * fatSize + reservedSectors + 32; + if (partSize < 32680) { + partType = 0X01; + } + else if (partSize < 65536) { + partType = 0X04; + } + else { + partType = 0X06; + } + // write MBR + writeMbr(); + clearCache(true); + fat_boot_t* pb = &cache.fbs; + pb->jump[0] = 0XEB; + pb->jump[1] = 0X00; + pb->jump[2] = 0X90; + for (uint8_t i = 0; i < sizeof(pb->oemId); i++) { + pb->oemId[i] = ' '; + } + pb->bytesPerSector = 512; + pb->sectorsPerCluster = sectorsPerCluster; + pb->reservedSectorCount = reservedSectors; + pb->fatCount = 2; + pb->rootDirEntryCount = 512; + pb->mediaType = 0XF8; + pb->sectorsPerFat16 = fatSize; + pb->sectorsPerTrack = sectorsPerTrack; + pb->headCount = numberOfHeads; + pb->hidddenSectors = relSector; + pb->totalSectors32 = partSize; + pb->driveNumber = 0X80; + pb->bootSignature = EXTENDED_BOOT_SIG; + pb->volumeSerialNumber = volSerialNumber(); + memcpy(pb->volumeLabel, noName, sizeof(pb->volumeLabel)); + memcpy(pb->fileSystemType, fat16str, sizeof(pb->fileSystemType)); + // write partition boot sector + writeCache(relSector); + // clear FAT and root directory + clearFatDir(fatStart, dataStart - fatStart); + clearCache(false); + cache.fat16[0] = 0XFFF8; + cache.fat16[1] = 0XFFFF; + // write first block of FAT and backup for reserved clusters + writeCache(fatStart); + writeCache(fatStart + fatSize); +} + +/* Format with FAT32 file system for SDHC cards */ +void formatFAT32() +{ + uint32_t nc; + relSector = BU32; + for (dataStart = 2 * BU32;; dataStart += BU32) { + nc = (cardSizeBlocks - dataStart) / sectorsPerCluster; + fatSize = (nc + 2 + 127) / 128; + uint32_t r = relSector + 9 + 2 * fatSize; + if (dataStart >= r) { + break; + } + } + reservedSectors = dataStart - relSector - 2 * fatSize; + fatStart = relSector + reservedSectors; + partSize = nc * sectorsPerCluster + dataStart - relSector; + // type depends on address of end sector + // max CHS has lbn = 16450560 = 1024*255*63 + if ((relSector + partSize) <= 16450560) { + // FAT32 + partType = 0X0B; + } + else { + // FAT32 with INT 13 + partType = 0X0C; + } + writeMbr(); + clearCache(true); + fat32_boot_t* pb = &cache.fbs32; + pb->jump[0] = 0XEB; + pb->jump[1] = 0X00; + pb->jump[2] = 0X90; + for (uint8_t i = 0; i < sizeof(pb->oemId); i++) { + pb->oemId[i] = ' '; + } + pb->bytesPerSector = 512; + pb->sectorsPerCluster = sectorsPerCluster; + pb->reservedSectorCount = reservedSectors; + pb->fatCount = 2; + pb->mediaType = 0XF8; + pb->sectorsPerTrack = sectorsPerTrack; + pb->headCount = numberOfHeads; + pb->hidddenSectors = relSector; + pb->totalSectors32 = partSize; + pb->sectorsPerFat32 = fatSize; + pb->fat32RootCluster = 2; + pb->fat32FSInfo = 1; + pb->fat32BackBootBlock = 6; + pb->driveNumber = 0X80; + pb->bootSignature = EXTENDED_BOOT_SIG; + pb->volumeSerialNumber = volSerialNumber(); + memcpy(pb->volumeLabel, noName, sizeof(pb->volumeLabel)); + memcpy(pb->fileSystemType, fat32str, sizeof(pb->fileSystemType)); + // write partition boot sector and backup + writeCache(relSector); + writeCache(relSector + 6); + clearCache(true); + // write extra boot area and backup + writeCache(relSector + 2); + writeCache(relSector + 8); + fat32_fsinfo_t* pf = &cache.fsinfo; + pf->leadSignature = FSINFO_LEAD_SIG; + pf->structSignature = FSINFO_STRUCT_SIG; + pf->freeCount = 0XFFFFFFFF; + pf->nextFree = 0XFFFFFFFF; + // write FSINFO sector and backup + writeCache(relSector + 1); + writeCache(relSector + 7); + clearFatDir(fatStart, 2 * fatSize + sectorsPerCluster); + clearCache(false); + cache.fat32[0] = 0x0FFFFFF8; + cache.fat32[1] = 0x0FFFFFFF; + cache.fat32[2] = 0x0FFFFFFF; + // write first block of FAT and backup for reserved clusters + writeCache(fatStart); + writeCache(fatStart + fatSize); +} + +/* Format the internal storage with FAT16*/ +void formatCard() { + //Start Card + startAltClockline(); + //Get card info + cardSizeBlocks = sd.card()->cardSize(); + cardCapacityMB = (cardSizeBlocks + 2047) / 2048; + if (cardCapacityMB <= 16) { + sectorsPerCluster = 2; + } + else if (cardCapacityMB <= 32) { + sectorsPerCluster = 4; + } + else if (cardCapacityMB <= 64) { + sectorsPerCluster = 8; + } + else if (cardCapacityMB <= 128) { + sectorsPerCluster = 16; + } + else if (cardCapacityMB <= 1024) { + sectorsPerCluster = 32; + } + else if (cardCapacityMB <= 32768) { + sectorsPerCluster = 64; + } + else { + sectorsPerCluster = 128; + } + // set fake disk geometry + sectorsPerTrack = cardCapacityMB <= 256 ? 32 : 63; + if (cardCapacityMB <= 16) { + numberOfHeads = 2; + } + else if (cardCapacityMB <= 32) { + numberOfHeads = 4; + } + else if (cardCapacityMB <= 128) { + numberOfHeads = 8; + } + else if (cardCapacityMB <= 504) { + numberOfHeads = 16; + } + else if (cardCapacityMB <= 1008) { + numberOfHeads = 32; + } + else if (cardCapacityMB <= 2016) { + numberOfHeads = 64; + } + else if (cardCapacityMB <= 4032) { + numberOfHeads = 128; + } + else { + numberOfHeads = 255; + } + //Format with FAT16 or FAT32 + if (teensyVersion == teensyVersion_old) + formatFAT16(); + else + formatFAT32(); + //End SD + endAltClockline(); +} diff --git a/Firmware_V2/src/hardware/sd.h b/Firmware_V2/src/hardware/sd.h new file mode 100644 index 0000000..7111f7a --- /dev/null +++ b/Firmware_V2/src/hardware/sd.h @@ -0,0 +1,39 @@ +/* +* +* SD - Methods to access the internal SD storage +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef SD_H +#define SD_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +bool beginSD(); +void clearCache(uint8_t addSig); +void clearFatDir(uint32_t bgn, uint32_t count); +void dateTime(uint16_t* date, uint16_t* time); +void formatCard(); +void formatFAT16(); +void formatFAT32(); +uint16_t getCardSize(); +uint32_t getSDSpace(); +void initSD(); +uint16_t lbnToCylinder(uint32_t lbn); +uint8_t lbnToHead(uint32_t lbn); +uint8_t lbnToSector(uint32_t lbn); +void refreshFreeSpace(); +uint32_t volSerialNumber(); +uint8_t writeCache(uint32_t lbn); +void writeMbr(); + +#endif /* SD_H */ diff --git a/Firmware_V2/src/hardware/touchscreen/ft6206_touchscreen.cpp b/Firmware_V2/src/hardware/touchscreen/ft6206_touchscreen.cpp new file mode 100644 index 0000000..7cf4c9f --- /dev/null +++ b/Firmware_V2/src/hardware/touchscreen/ft6206_touchscreen.cpp @@ -0,0 +1,97 @@ +/* + * + * FT6206 Touch controller + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include + +/*###################### PRIVATE FUNCTION BODIES ##############################*/ + +boolean FT6206_Touchscreen::begin(uint8_t threshhold) { + writeRegister8(FT6206_REG_THRESHHOLD, threshhold); + if ((readRegister8(FT6206_REG_VENDID) != 17) || (readRegister8(FT6206_REG_CHIPID) != 54)) return false; + return true; +} + +boolean FT6206_Touchscreen::touched(void) { + uint8_t n = readRegister8(FT6206_REG_NUMTOUCHES); + if ((n == 1) || (n == 2)) return true; + return false; +} + +void FT6206_Touchscreen::readData(uint16_t *x, uint16_t *y) { + uint8_t i2cdat[16]; + Wire.beginTransmission(FT6206_ADDR); + Wire.write((byte)0); + Wire.endTransmission(); + Wire.beginTransmission(FT6206_ADDR); + Wire.requestFrom((byte)FT6206_ADDR, (byte)32); + for (uint8_t i = 0; i < 16; i++) + i2cdat[i] = Wire.read(); + Wire.endTransmission(); + touches = i2cdat[0x02]; + if (touches > 2) { + touches = 0; + *x = *y = 0; + } + if (touches == 0) { + *x = *y = 0; + return; + } + for (uint8_t i = 0; i < 2; i++) { + touchX[i] = i2cdat[0x03 + i * 6] & 0x0F; + touchX[i] <<= 8; + touchX[i] |= i2cdat[0x04 + i * 6]; + touchY[i] = i2cdat[0x05 + i * 6] & 0x0F; + touchY[i] <<= 8; + touchY[i] |= i2cdat[0x06 + i * 6]; + touchID[i] = i2cdat[0x05 + i * 6] >> 4; + } + *x = touchY[0]; + *y = touchX[0]; + *y = map(*y, 0, 240, 240, 0); +} + +TS_Point FT6206_Touchscreen::getPoint(void) { + uint16_t x, y; + readData(&x, &y); + if (rotated) { + y = map(y, 0, 240, 240, 0); + x = map(x, 0, 320, 320, 0); + } + return TS_Point(x, y, 1); +} + +uint8_t FT6206_Touchscreen::readRegister8(uint8_t reg) { + uint8_t x; + Wire.beginTransmission(FT6206_ADDR); + Wire.write((byte)reg); + Wire.endTransmission(); + Wire.beginTransmission(FT6206_ADDR); + Wire.requestFrom((byte)FT6206_ADDR, (byte)1); + x = Wire.read(); + Wire.endTransmission(); + return x; +} + +void FT6206_Touchscreen::writeRegister8(uint8_t reg, uint8_t val) { + Wire.beginTransmission(FT6206_ADDR); + Wire.write((byte)reg); + Wire.write((byte)val); + Wire.endTransmission(); +} diff --git a/Firmware_V2/src/hardware/touchscreen/ft6206_touchscreen.h b/Firmware_V2/src/hardware/touchscreen/ft6206_touchscreen.h new file mode 100644 index 0000000..5ae0944 --- /dev/null +++ b/Firmware_V2/src/hardware/touchscreen/ft6206_touchscreen.h @@ -0,0 +1,55 @@ +/* + * + * FT6206 Touch controller + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +#ifndef FT6206_TOUCHSCREEN_H +#define FT6206_TOUCHSCREEN_H + +#define FT6206_ADDR 0x38 +#define FT6206_G_FT5201ID 0xA8 +#define FT6206_REG_NUMTOUCHES 0x02 + +#define FT6206_NUM_X 0x33 +#define FT6206_NUM_Y 0x34 +#define FT6206_REG_MODE 0x00 +#define FT6206_REG_CALIBRATE 0x02 +#define FT6206_REG_WORKMODE 0x00 +#define FT6206_REG_FACTORYMODE 0x40 +#define FT6206_REG_THRESHHOLD 0x80 +#define FT6206_REG_POINTRATE 0x88 +#define FT6206_REG_FIRMVERS 0xA6 +#define FT6206_REG_CHIPID 0xA3 +#define FT6206_REG_VENDID 0xA8 + +#define FT6206_DEFAULT_THRESSHOLD 128 + +/*########################## PUBLIC PROCEDURES ################################*/ + +class FT6206_Touchscreen { +public: + boolean begin(uint8_t thresh = FT6206_DEFAULT_THRESSHOLD); + void writeRegister8(uint8_t reg, uint8_t val); + uint8_t readRegister8(uint8_t reg); + void readData(uint16_t *x, uint16_t *y); + boolean touched(void); + TS_Point getPoint(void); + bool rotated = false; +private: + uint8_t touches; + uint16_t touchX[2], touchY[2], touchID[2]; +}; + +#endif /* FT6206_TOUCHSCREEN_H */ diff --git a/Firmware_V2/src/hardware/touchscreen/touchscreen.cpp b/Firmware_V2/src/hardware/touchscreen/touchscreen.cpp new file mode 100644 index 0000000..bc8c3c7 --- /dev/null +++ b/Firmware_V2/src/hardware/touchscreen/touchscreen.cpp @@ -0,0 +1,123 @@ +/* +* +* Touchscreen - FT6206 or XPT2046 controller +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################### STATIC DATA DECLARATIONS ##########################*/ + +//Resistive Touch Controller +static XPT2046_Touchscreen resTouch; +//Capacitive Touch Controller +static FT6206_Touchscreen capTouch; + +/*############################# PUBLIC VARIABLES ##############################*/ + +//Choose the right touch screen +volatile bool touch_capacitive; + +/*###################### PRIVATE FUNCTION BODIES ##############################*/ + +/* Returns the coordinates of the touched point */ +TS_Point touch_getPoint() { + //Get touch from capacitive + if (touch_capacitive) + return capTouch.getPoint(); + //Get point from resistive + return resTouch.getPoint(); +} + +/* Initializes the touch module and checks if it is working */ +void touch_init() { + //Capacitive screen + if (capTouch.begin()) + touch_capacitive = true; + + //Resistive screen or none + else { + resTouch.begin(); + touch_capacitive = false; + } + + //If not capacitive, check if connected + if (!touch_capacitive) + { + //Get a point + TS_Point point = touch_getPoint(); + //Wait short + delay(10); + //Read one time to stabilize + digitalRead(pin_touch_irq); + + //Init touch status + bool touchStatus = true; + //Check IRQ 10 times, should be HIGH + for (int i = 0; i < 10; i++) + { + if (!digitalRead(pin_touch_irq)) + touchStatus = false; + delay(10); + } + + //Comparison value depending on rotation + uint16_t xval, yval; + if (rotationVert) { + xval = 320; + yval = 240; + } + else { + xval = 0; + yval = 0; + } + + //Check if touch is working, otherwise set diagnostic + if (!(((point.x == xval) && (point.y == yval) && (touchStatus == true)) + || ((point.x != xval) && (point.y != yval) && (touchStatus == false)))) + setDiagnostic(diag_touch); + } +} + +/* Returns if the screen is currently touched */ +volatile bool touch_touched() { + bool touch; + //Check for touch, capacitive or resistive + if (touch_capacitive) + touch = capTouch.touched(); + else + touch = resTouch.touched(); + //If touch registered, set screen pressed marker + if (touch) + screenPressed = true; + return touch; +} + +/* Set rotation for touch screen */ +void touch_setRotation(bool rotated) { + if (touch_capacitive) + capTouch.rotated = rotated; + else + resTouch.rotated = rotated; +} diff --git a/Firmware_V2/src/hardware/touchscreen/touchscreen.h b/Firmware_V2/src/hardware/touchscreen/touchscreen.h new file mode 100644 index 0000000..f4833ba --- /dev/null +++ b/Firmware_V2/src/hardware/touchscreen/touchscreen.h @@ -0,0 +1,40 @@ +/* +* +* Touchscreen - FT6206 or XPT2046 controller +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef TOUCHSCREEN_H +#define TOUCHSCREEN_H + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +class TS_Point { +public: + TS_Point(void) : x(0), y(0), z(0) {} + TS_Point(int16_t x, int16_t y, int16_t z) : x(x), y(y), z(z) {} + bool operator==(TS_Point p) { return ((p.x == x) && (p.y == y) && (p.z == z)); } + bool operator!=(TS_Point p) { return ((p.x != x) || (p.y != y) || (p.z != z)); } + int16_t x, y, z; +}; + +extern volatile bool touch_capacitive; + +/*########################## PUBLIC PROCEDURES ################################*/ + +TS_Point touch_getPoint(); +void touch_init(); +void touch_setRotation(bool rotated); +volatile bool touch_touched(); + + +#endif /* TOUCHSCREEN_H */ diff --git a/Firmware_V2/src/hardware/touchscreen/xpt2046_touchscreen.cpp b/Firmware_V2/src/hardware/touchscreen/xpt2046_touchscreen.cpp new file mode 100644 index 0000000..72fc5af --- /dev/null +++ b/Firmware_V2/src/hardware/touchscreen/xpt2046_touchscreen.cpp @@ -0,0 +1,157 @@ +/* + * + * XPT2046 Touch controller + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include + +/*################# DATA TYPES, CONSTANTS & MACRO DEFINITIONS #################*/ + +#define Z_THRESHOLD 400 +#define MSEC_THRESHOLD 3 + +/*###################### PRIVATE FUNCTION BODIES ##############################*/ + +XPT2046_Touchscreen::XPT2046_Touchscreen() +{ + csPin = 9; + tirqPin = 255; + msraw = 0x80000000; + xraw = 0; + yraw = 0; + zraw = 0; + isrWake = true; +} + +static XPT2046_Touchscreen *isrPinptr; +void isrPin(void); + +bool XPT2046_Touchscreen::begin() +{ + if (255 != tirqPin) { + pinMode(tirqPin, INPUT); + attachInterrupt(tirqPin, isrPin, FALLING); + isrPinptr = this; + } + return true; +} + +void isrPin(void) +{ + XPT2046_Touchscreen *o = isrPinptr; + o->isrWake = true; +} + +TS_Point XPT2046_Touchscreen::getPoint() +{ + update(); + if (rotated) { + yraw = map(yraw, 0, 240, 240, 0); + xraw = map(xraw, 0, 320, 320, 0); + } + return TS_Point(xraw, yraw, zraw); +} + +bool XPT2046_Touchscreen::touched() +{ + update(); + //Prevent double touch + delay(20); + return (zraw >= Z_THRESHOLD); +} + +void XPT2046_Touchscreen::readData(uint16_t *x, uint16_t *y, uint8_t *z) +{ + update(); + *x = xraw; + *y = yraw; + *z = zraw; +} + +bool XPT2046_Touchscreen::bufferEmpty() +{ + return ((millis() - msraw) < MSEC_THRESHOLD); +} + +static int16_t besttwoavg(int16_t x, int16_t y, int16_t z) { + int16_t da, db, dc; + int16_t reta; + if (x > y) da = x - y; else da = y - x; + if (x > z) db = x - z; else db = z - x; + if (z > y) dc = z - y; else dc = y - z; + if (da <= db && da <= dc) reta = (x + y) >> 1; + else if (db <= da && db <= dc) reta = (x + z) >> 1; + else reta = (y + z) >> 1; + + return (reta); +} + +void XPT2046_Touchscreen::update() +{ + int16_t data[6]; + if (!isrWake) return; + uint32_t now = millis(); + if (now - msraw < MSEC_THRESHOLD) return; + SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0)); +#if defined(__MK20DX256__) + CORE_PIN13_CONFIG = PORT_PCR_MUX(1); + CORE_PIN14_CONFIG = PORT_PCR_DSE | PORT_PCR_MUX(2); +#endif + digitalWrite(csPin, LOW); + SPI.transfer(0xB1); + int16_t z1 = SPI.transfer16(0xC1) >> 3; + int z = z1 + 4095; + int16_t z2 = SPI.transfer16(0x91) >> 3; + z -= z2; + if (z >= Z_THRESHOLD) { + SPI.transfer16(0x91); + data[0] = SPI.transfer16(0xD1) >> 3; + data[1] = SPI.transfer16(0x91) >> 3; + data[2] = SPI.transfer16(0xD1) >> 3; + data[3] = SPI.transfer16(0x91) >> 3; + } + else data[0] = data[1] = data[2] = data[3] = 0; + data[4] = SPI.transfer16(0xD0) >> 3; + data[5] = SPI.transfer16(0) >> 3; + digitalWrite(csPin, HIGH); +#if defined(__MK20DX256__) + CORE_PIN13_CONFIG = PORT_PCR_DSE | PORT_PCR_MUX(2); + CORE_PIN14_CONFIG = PORT_PCR_MUX(1); +#endif + SPI.endTransaction(); + if (z < 0) z = 0; + if (z < Z_THRESHOLD) { + zraw = 0; + if (255 != tirqPin) isrWake = false; + return; + } + zraw = z; + int16_t x = besttwoavg(data[0], data[2], data[4]); + int16_t y = besttwoavg(data[1], data[3], data[5]); + if (z >= Z_THRESHOLD) { + msraw = now; + xraw = x; + yraw = y; + } + xraw = map(xraw, 230, 3670, 0, 320); + yraw = map(yraw, 230, 3800, 240, 0); + if (xraw < 0) xraw = 0; + if (xraw > 319) xraw = 319; + if (yraw < 0) yraw = 0; + if (yraw > 239) yraw = 239; +} diff --git a/Firmware_V2/src/hardware/touchscreen/xpt2046_touchscreen.h b/Firmware_V2/src/hardware/touchscreen/xpt2046_touchscreen.h new file mode 100644 index 0000000..d0b4ece --- /dev/null +++ b/Firmware_V2/src/hardware/touchscreen/xpt2046_touchscreen.h @@ -0,0 +1,39 @@ +/* + * + * FT6206 Touch controller + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +#ifndef XPT2046_TOUCHSCREEN_H +#define XPT2046_TOUCHSCREEN_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +class XPT2046_Touchscreen { +public: + XPT2046_Touchscreen(); + bool begin(); + TS_Point getPoint(); + bool touched(); + void readData(uint16_t *x, uint16_t *y, uint8_t *z); + bool bufferEmpty(); + uint8_t bufferSize() { return 1; } + bool isrWake; + bool rotated = false; +private: + void update(); + uint8_t csPin, tirqPin; + int16_t xraw, yraw, zraw; + uint32_t msraw; +}; + +#endif /* XPT2046_TOUCHSCREEN_H */ diff --git a/Firmware_V2/src/libraries/ADC/ADC.cpp b/Firmware_V2/src/libraries/ADC/ADC.cpp new file mode 100644 index 0000000..0177b5a --- /dev/null +++ b/Firmware_V2/src/libraries/ADC/ADC.cpp @@ -0,0 +1,1307 @@ +/* Teensy 3.x, LC ADC library + * https://github.com/pedvide/ADC + * Copyright (c) 2015 Pedro Villanueva + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* ADC.cpp: Implements the control of one or more ADC modules of Teensy 3.x, LC + * + */ + +#include "ADC.h" + + + +// translate pin number to SC1A nomenclature and viceversa +// we need to create this static const arrays so that we can assign the "normal arrays" to the correct one +// depending on which ADC module we will be. +/* channel2sc1aADCx converts a pin number to their value for the SC1A register, for the ADC0 and ADC1 +* numbers with +ADC_SC1A_PIN_MUX (128) means those pins use mux a, the rest use mux b. +* numbers with +ADC_SC1A_PIN_DIFF (64) means it's also a differential pin (treated also in the channel2sc1a_diff_ADCx) +* For diff_table_ADCx, +ADC_SC1A_PIN_PGA means the pin can use PGA on that ADC +*/ + +///////// ADC0 +#if defined(ADC_TEENSY_3_0) +const uint8_t ADC::channel2sc1aADC0[]= { // new version, gives directly the sc1a number. 0x1F=31 deactivates the ADC. + 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, 0, 19, 3, 21, // 0-13, we treat them as A0-A13 + 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, // 14-23 (A0-A9) + 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, // 24-33 + 0+ADC_SC1A_PIN_DIFF, 19+ADC_SC1A_PIN_DIFF, 3+ADC_SC1A_PIN_DIFF, 21+ADC_SC1A_PIN_DIFF, // 34-37 (A10-A13) + 26, 22, 23, 27, 29, 30 // 38-43: temp. sensor, VREF_OUT, A14, bandgap, VREFH, VREFL. A14 isn't connected to anything in Teensy 3.0. +}; +#elif defined(ADC_TEENSY_3_1) // the only difference with 3.0 is that A13 is not connected to ADC0 and that T3.1 has PGA. +const uint8_t ADC::channel2sc1aADC0[]= { // new version, gives directly the sc1a number. 0x1F=31 deactivates the ADC. + 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, 0, 19, 3, 31, // 0-13, we treat them as A0-A13 + 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, // 14-23 (A0-A9) + 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, // 24-33 + 0+ADC_SC1A_PIN_DIFF, 19+ADC_SC1A_PIN_DIFF, 3+ADC_SC1A_PIN_DIFF, 31+ADC_SC1A_PIN_DIFF, // 34-37 (A10-A13) + 26, 22, 23, 27, 29, 30 // 38-43: temp. sensor, VREF_OUT, A14, bandgap, VREFH, VREFL. A14 isn't connected to anything in Teensy 3.0. +}; +#elif defined(ADC_TEENSY_LC) +// Teensy LC +const uint8_t ADC::channel2sc1aADC0[]= { // new version, gives directly the sc1a number. 0x1F=31 deactivates the ADC. + 5, 14, 8, 9, 13, 12, 6, 7, 15, 11, 0, 4+ADC_SC1A_PIN_MUX, 23, 31, // 0-13, we treat them as A0-A12 + A13= doesn't exist + 5, 14, 8, 9, 13, 12, 6, 7, 15, 11, // 14-23 (A0-A9) + 0+ADC_SC1A_PIN_DIFF, 4+ADC_SC1A_PIN_MUX+ADC_SC1A_PIN_DIFF, 23, 31, 31, 31, 31, 31, 31, 31, // 24-33 ((A10-A12) + nothing), A11 uses mux a + 31, 31, 31, 31, // 34-37 nothing + 26, 31, 31, 27, 29, 30 // 38-43: temp. sensor, , , bandgap, VREFH, VREFL. +}; +#elif defined(ADC_TEENSY_3_5) || defined(ADC_TEENSY_3_6) +const uint8_t ADC::channel2sc1aADC0[]= { // new version, gives directly the sc1a number. 0x1F=31 deactivates the ADC. + 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, 3, 31, 31, 31, // 0-13, we treat them as A0-A13 + 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, // 14-23 (A0-A9) + 31, 31, 31, 31, 31, 31, 31, // 24-30 + 31, 31, 17, 18,// 31-34 A12, A13, A14, A15 + 31, 31, 31, 31, 31,// 33-39 + 3+ADC_SC1A_PIN_DIFF, 31+ADC_SC1A_PIN_DIFF, 23, 31 // 40-43: A10, A11 (cannot be read by ADC0), A21, A22 +}; +#endif // defined + +///////// ADC1 +#if defined(ADC_TEENSY_3_1) +const uint8_t ADC::channel2sc1aADC1[]= { // new version, gives directly the sc1a number. 0x1F=31 deactivates the ADC. + 31, 31, 8, 9, 31, 31, 31, 31, 31, 31, 3, 31, 0, 19, // 0-13, we treat them as A0-A13 + 31, 31, 8, 9, 31, 31, 31, 31, 31, 31, // 14-23 (A0-A9) + 31, 31, // 24,25 are digital only pins + 5+ADC_SC1A_PIN_MUX, 5, 4, 6, 7, 4+ADC_SC1A_PIN_MUX, 31, 31, // 26-33 26=5a, 27=5b, 28=4b, 29=6b, 30=7b, 31=4a, 32,33 are digital only + 3+ADC_SC1A_PIN_DIFF, 31+ADC_SC1A_PIN_DIFF, 0+ADC_SC1A_PIN_DIFF, 19+ADC_SC1A_PIN_DIFF, // 34-37 (A10-A13) A11 isn't connected. + 26, 18, 31, 27, 29, 30 // 38-43: temp. sensor, VREF_OUT, A14 (not connected), bandgap, VREFH, VREFL. +}; +#elif defined(ADC_TEENSY_3_5) || defined(ADC_TEENSY_3_6) +const uint8_t ADC::channel2sc1aADC1[]= { // new version, gives directly the sc1a number. 0x1F=31 deactivates the ADC. + 31, 31, 8, 9, 31, 31, 31, 31, 31, 31, 31, 19, 14, 15, // 0-13, we treat them as A0-A13 + 31, 31, 8, 9, 31, 31, 31, 31, 31, 31, // 14-23 (A0-A9) + 31, 31, 31, 31, 31, 31, 31, // 24-30 + 14, 15, 31, 31, 4, 5, 6, 7, 17, // 31-39 A12-A20 + 31+ADC_SC1A_PIN_DIFF, 19+ADC_SC1A_PIN_DIFF, 31, 23 // 40-43: A10, A11, A21, A22 +}; +#endif + +#if defined(ADC_TEENSY_3_1) // Teensy 3.1 + const ADC_Module::ADC_NLIST ADC::diff_table_ADC0[]= { + {A10, 0+ADC_SC1A_PIN_PGA}, {A12, 3} + }; + const ADC_Module::ADC_NLIST ADC::diff_table_ADC1[]= { + {A10, 3}, {A12, 0+ADC_SC1A_PIN_PGA} + }; +#elif defined(ADC_TEENSY_3_0) // Teensy 3.0 + const ADC_Module::ADC_NLIST ADC::diff_table_ADC0[]= { + {A10, 0}, {A12, 3} + }; +#elif defined(ADC_TEENSY_LC) // Teensy LC + const ADC_Module::ADC_NLIST ADC::diff_table_ADC0[]= { + {A10, 0} + }; +#elif defined(ADC_TEENSY_3_5) || defined(ADC_TEENSY_3_6) // Teensy 3.5// Teensy 3.6 + const ADC_Module::ADC_NLIST ADC::diff_table_ADC0[]= { + {A10, 3} + }; + const ADC_Module::ADC_NLIST ADC::diff_table_ADC1[]= { + {A10, 0} + }; +#endif + + + +// translate SC1A to pin number +///////// ADC0 +#if defined(ADC_TEENSY_3_0) || defined(ADC_TEENSY_3_1) +const uint8_t ADC::sc1a2channelADC0[]= { // new version, gives directly the pin number + 34, 0, 0, 36, 23, 14, 20, 21, 16, 17, 0, 0, 19, 18, // 0-13 + 15, 22, 23, 0, 0, 35, 0, 37, // 14-21 + 39, 40, 0, 0, 38, 41, 42, 43, // VREF_OUT, A14, temp. sensor, bandgap, VREFH, VREFL. + 0 // 31 means disabled, but just in case +}; +#elif defined(ADC_TEENSY_LC) +// Teensy LC +const uint8_t ADC::sc1a2channelADC0[]= { // new version, gives directly the pin number + 24, 0, 0, 0, 25, 14, 20, 21, 16, 17, 0, 23, 19, 18, // 0-13 + 15, 22, 23, 0, 0, 0, 0, 0, // 14-21 + 26, 0, 0, 0, 38, 41, 0, 42, 43, // A12, temp. sensor, bandgap, VREFH, VREFL. + 0 // 31 means disabled, but just in case +}; +#elif defined(ADC_TEENSY_3_5) || defined(ADC_TEENSY_3_6) +const uint8_t ADC::sc1a2channelADC0[]= { // new version, gives directly the pin number + 34, 0, 0, 36, 23, 14, 20, 21, 16, 17, 0, 0, 19, 18, // 0-13 + 15, 22, 23, 0, 0, 35, 0, 37, // 14-21 + 39, 40, 0, 0, 38, 41, 42, 43, // VREF_OUT, A14, temp. sensor, bandgap, VREFH, VREFL. + 0 // 31 means disabled, but just in case +}; +#endif // defined + +///////// ADC1 +#if defined(ADC_TEENSY_3_1) +const uint8_t ADC::sc1a2channelADC1[]= { // new version, gives directly the pin number + 36, 0, 0, 34, 28, 26, 29, 30, 16, 17, 0, 0, 0, 0, // 0-13. 5a=26, 5b=27, 4b=28, 4a=31 + 0, 0, 0, 0, 39, 37, 0, 0, // 14-21 + 0, 0, 0, 0, 38, 41, 0, 42, // 22-29. VREF_OUT, A14, temp. sensor, bandgap, VREFH, VREFL. + 43 +}; +#elif defined(ADC_TEENSY_3_5) || defined(ADC_TEENSY_3_6) +const uint8_t ADC::sc1a2channelADC1[]= { // new version, gives directly the pin number + 36, 0, 0, 34, 28, 26, 29, 30, 16, 17, 0, 0, 0, 0, // 0-13. 5a=26, 5b=27, 4b=28, 4a=31 + 0, 0, 0, 0, 39, 37, 0, 0, // 14-21 + 0, 0, 0, 0, 38, 41, 0, 42, // 22-29. VREF_OUT, A14, temp. sensor, bandgap, VREFH, VREFL. + 43 +}; +#endif + + +// Constructor +ADC::ADC() : // awkward initialization so there are no -Wreorder warnings + adc0_obj(0, channel2sc1aADC0, diff_table_ADC0) + #if ADC_NUM_ADCS>1 + , adc1_obj(1, channel2sc1aADC1, diff_table_ADC1) + #endif + , adc0(&adc0_obj) + #if ADC_NUM_ADCS>1 + , adc1(&adc1_obj) + #endif + { + //ctor + + //digitalWriteFast(LED_BUILTIN, HIGH); + + // make sure the clocks to the ADC are on + SIM_SCGC6 |= SIM_SCGC6_ADC0; + #if ADC_NUM_ADCS>1 + SIM_SCGC3 |= SIM_SCGC3_ADC1; + #endif + +} + + + +/* Set the voltage reference you prefer, +* type can be ADC_REF_3V3, ADC_REF_1V2 (not for Teensy LC) or ADC_REF_EXT +*/ +void ADC::setReference(uint8_t type, int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + adc1->setReference(type); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + #endif + return; + } + adc0->setReference(type); // adc_num isn't changed or has selected ADC0 + return; +} + + +// Change the resolution of the measurement. +/* +* \param bits is the number of bits of resolution. +* For single-ended measurements: 8, 10, 12 or 16 bits. +* For differential measurements: 9, 11, 13 or 16 bits. +* If you want something in between (11 bits single-ended for example) select the inmediate higher +* and shift the result one to the right. +* If you select, for example, 9 bits and then do a single-ended reading, the resolution will be adjusted to 8 bits +* In this case the comparison values will still be correct for analogRead and analogReadDifferential, but not +* for startSingle* or startContinous*, so whenever you change the resolution, change also the comparison values. +*/ +void ADC::setResolution(uint8_t bits, int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + adc1->setResolution(bits); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + #endif + return; + } + adc0->setResolution(bits); // adc_num isn't changed or has selected ADC0 + return; +} + +//! Returns the resolution of the ADC_Module. +uint8_t ADC::getResolution(int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + return adc1->getResolution(); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + #endif + return 0; + } + return adc0->getResolution(); // adc_num isn't changed or has selected ADC0 + +} + +//! Returns the maximum value for a measurement. +uint32_t ADC::getMaxValue(int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + return adc1->getMaxValue(); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + #endif + return 1; + } + return adc0->getMaxValue(); +} + + +// Sets the conversion speed +/* +* \param speed can be ADC_LOW_SPEED, ADC_MED_SPEED or ADC_HIGH_SPEED +* +* It recalibrates at the end. +*/ +void ADC::setConversionSpeed(uint8_t speed, int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + adc1->setConversionSpeed(speed); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + #endif + return; + } + adc0->setConversionSpeed(speed); // adc_num isn't changed or has selected ADC0 + return; + +} + + +// Sets the sampling speed +/* +* \param speed can be ADC_LOW_SPEED, ADC_MED_SPEED or ADC_HIGH_SPEED +* +* It recalibrates at the end. +*/ +void ADC::setSamplingSpeed(uint8_t speed, int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + adc1->setSamplingSpeed(speed); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + #endif + return; + } + adc0->setSamplingSpeed(speed); // adc_num isn't changed or has selected ADC0 + return; + +} + + +// Set the number of averages +/* +* \param num can be 0, 4, 8, 16 or 32. +*/ +void ADC::setAveraging(uint8_t num, int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + adc1->setAveraging(num); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + #endif + return; + } + adc0->setAveraging(num); // adc_num isn't changed or has selected ADC0 + return; +} + + +//! Enable interrupts +/** An IRQ_ADC0 Interrupt will be raised when the conversion is completed +* (including hardware averages and if the comparison (if any) is true). +*/ +void ADC::enableInterrupts(int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + adc1->enableInterrupts(); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + #endif + return; + } + adc0->enableInterrupts(); + return; +} + +//! Disable interrupts +void ADC::disableInterrupts(int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + adc1->disableInterrupts(); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + #endif + return; + } + adc0->disableInterrupts(); + return; +} + + +//! Enable DMA request +/** An ADC DMA request will be raised when the conversion is completed +* (including hardware averages and if the comparison (if any) is true). +*/ +void ADC::enableDMA(int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + adc1->enableDMA(); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + #endif + return; + } + adc0->enableDMA(); + return; +} + +//! Disable ADC DMA request +void ADC::disableDMA(int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + adc1->disableDMA(); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + #endif + return; + } + adc0->disableDMA(); + return; +} + + +// Enable the compare function to a single value +/* A conversion will be completed only when the ADC value +* is >= compValue (greaterThan=true) or < compValue (greaterThan=false) +* Call it after changing the resolution +* Use with interrupts or poll conversion completion with isComplete() +*/ +void ADC::enableCompare(int16_t compValue, bool greaterThan, int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + adc1->enableCompare(compValue, greaterThan); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + #endif + return; + } + adc0->enableCompare(compValue, greaterThan); + return; +} + +// Enable the compare function to a range +/* A conversion will be completed only when the ADC value is inside (insideRange=1) or outside (=0) +* the range given by (lowerLimit, upperLimit),including (inclusive=1) the limits or not (inclusive=0). +* See Table 31-78, p. 617 of the freescale manual. +* Call it after changing the resolution +* Use with interrupts or poll conversion completion with isComplete() +*/ +void ADC::enableCompareRange(int16_t lowerLimit, int16_t upperLimit, bool insideRange, bool inclusive, int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + adc1->enableCompareRange(lowerLimit, upperLimit, insideRange, inclusive); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + #endif + return; + } + adc0->enableCompareRange(lowerLimit, upperLimit, insideRange, inclusive); + return; +} + +//! Disable the compare function +void ADC::disableCompare(int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + adc1->disableCompare(); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + #endif + return; + } + adc0->disableCompare(); + return; +} + + +// Enable and set PGA +/* Enables the PGA and sets the gain +* Use only for signals lower than 1.2 V +* \param gain can be 1, 2, 4, 8, 16, 32 or 64 +* +*/ +void ADC::enablePGA(uint8_t gain, int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + adc1->enablePGA(gain); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + #endif + return; + } + adc0->enablePGA(gain); + return; +} + +//! Returns the PGA level +/** PGA level = 2^gain, from 0 to 64 +*/ +uint8_t ADC::getPGA(int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + return adc1->getPGA(); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + return 1; + #endif + } + return adc0->getPGA(); +} + +//! Disable PGA +void ADC::disablePGA(int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + adc1->disablePGA(); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + #endif + return; + } + adc0->disablePGA(); + return; +} + +//! Is the ADC converting at the moment? +bool ADC::isConverting(int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + return adc1->isConverting(); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + #endif + return false; + } + return adc0->isConverting(); +} + +// Is an ADC conversion ready? +/* +* \return 1 if yes, 0 if not. +* When a value is read this function returns 0 until a new value exists +* So it only makes sense to call it before analogReadContinuous() or readSingle() +*/ +bool ADC::isComplete(int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + return adc1->isComplete(); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + #endif + return false; + } + return adc0->isComplete();; +} + +//! Is the ADC in differential mode? +bool ADC::isDifferential(int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + return adc1->isDifferential(); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + #endif + return false; + } + return adc0->isDifferential(); +} + +//! Is the ADC in continuous mode? +bool ADC::isContinuous(int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + return adc1->isContinuous(); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + #endif + return false; + } + return adc0->isContinuous(); +} + + +/* Returns the analog value of the pin. +* It waits until the value is read and then returns the result. +* If a comparison has been set up and fails, it will return ADC_ERROR_VALUE. +* This function is interrupt safe, so it will restore the adc to the state it was before being called +* If more than one ADC exists, it will select the module with less workload, you can force a selection using +* adc_num. If you select ADC1 in Teensy 3.0 it will return ADC_ERROR_VALUE. +*/ +int ADC::analogRead(uint8_t pin, int8_t adc_num) { + #if ADC_NUM_ADCS==1 + /* Teensy 3.0, LC + */ + if( adc_num==1 ) { // If asked to use ADC1, return error + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + return ADC_ERROR_VALUE; + } + return adc0->analogRead(pin); // use ADC0 + #elif ADC_NUM_ADCS==2 + /* Teensy 3.1 + */ + if( adc_num==-1 ) { // use no ADC in particular + // check which ADC can read the pin + bool adc0Pin = adc0->checkPin(pin); + bool adc1Pin = adc1->checkPin(pin); + + if(adc0Pin && adc1Pin) { // Both ADCs + if( (adc0->num_measurements) > (adc1->num_measurements)) { // use the ADC with less workload + return adc1->analogRead(pin); + } else { + return adc0->analogRead(pin); + } + } else if(adc0Pin) { // ADC0 + return adc0->analogRead(pin); + } else if(adc1Pin) { // ADC1 + return adc1->analogRead(pin); + } else { // pin not valid in any ADC + adc0->fail_flag |= ADC_ERROR_WRONG_PIN; + adc1->fail_flag |= ADC_ERROR_WRONG_PIN; + return ADC_ERROR_VALUE; // all others are invalid + } + } + else if( adc_num==0 ) { // user wants ADC0 + return adc0->analogRead(pin); + } + else if( adc_num==1 ){ // user wants ADC 1 + return adc1->analogRead(pin); + } + adc0->fail_flag |= ADC_ERROR_OTHER; + return ADC_ERROR_VALUE; + #endif +} + +/* Reads the differential analog value of two pins (pinP - pinN). +* It waits until the value is read and then returns the result. +* If a comparison has been set up and fails, it will return ADC_ERROR_VALUE. +* \param pinP must be A10 or A12. +* \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12). +* Other pins will return ADC_ERROR_VALUE. +* This function is interrupt safe, so it will restore the adc to the state it was before being called +* If more than one ADC exists, it will select the module with less workload, you can force a selection using +* adc_num. If you select ADC1 in Teensy 3.0 it will return ADC_ERROR_VALUE. +*/ +int ADC::analogReadDifferential(uint8_t pinP, uint8_t pinN, int8_t adc_num) { + + #if ADC_NUM_ADCS==1 + /* Teensy 3.0, LC + */ + if( adc_num==1 ) { // If asked to use ADC1, return error + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + return ADC_ERROR_VALUE; + } + return adc0->analogReadDifferential(pinP, pinN); // use ADC0 + #elif ADC_NUM_ADCS==2 + /* Teensy 3.1 + */ + if( adc_num==-1 ) { // use no ADC in particular + // check which ADC can read the pin + bool adc0Pin = adc0->checkDifferentialPins(pinP, pinN); + bool adc1Pin = adc1->checkDifferentialPins(pinP, pinN); + + if(adc0Pin && adc1Pin) { // Both ADCs + if( (adc0->num_measurements) > (adc1->num_measurements)) { // use the ADC with less workload + return adc1->analogReadDifferential(pinP, pinN); + } else { + return adc0->analogReadDifferential(pinP, pinN); + } + } else if(adc0Pin) { // ADC0 + return adc0->analogReadDifferential(pinP, pinN); + } else if(adc1Pin) { // ADC1 + return adc1->analogReadDifferential(pinP, pinN); + } else { // pins not valid in any ADC + adc0->fail_flag |= ADC_ERROR_WRONG_PIN; + adc1->fail_flag |= ADC_ERROR_WRONG_PIN; + return ADC_ERROR_VALUE; // all others are invalid + } + } + else if( adc_num==0 ) { // user wants ADC0 + return adc0->analogReadDifferential(pinP, pinN); + } + else if( adc_num==1 ){ // user wants ADC 1 + return adc1->analogReadDifferential(pinP, pinN); + } + adc0->fail_flag |= ADC_ERROR_OTHER; + return ADC_ERROR_VALUE; + #endif +} + + +// Starts an analog measurement on the pin and enables interrupts. +/* It returns immediately, get value with readSingle(). +* If the pin is incorrect it returns ADC_ERROR_VALUE +* This function is interrupt safe. The ADC interrupt will restore the adc to its previous settings and +* restart the adc if it stopped a measurement. If you modify the adc_isr then this won't happen. +*/ +bool ADC::startSingleRead(uint8_t pin, int8_t adc_num) { + #if ADC_NUM_ADCS==1 + /* Teensy 3.0, LC + */ + if( adc_num==1 ) { // If asked to use ADC1, return error + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + return false; + } + return adc0->startSingleRead(pin); // use ADC0 + #elif ADC_NUM_ADCS==2 + /* Teensy 3.1 + */ + if( adc_num==-1 ) { // use no ADC in particular + // check which ADC can read the pin + bool adc0Pin = adc0->checkPin(pin); + bool adc1Pin = adc1->checkPin(pin); + + if(adc0Pin && adc1Pin) { // Both ADCs + + if( (adc0->num_measurements) > (adc1->num_measurements)) { // use the ADC with less workload + return adc1->startSingleRead(pin); + } else { + return adc0->startSingleRead(pin); + } + } else if(adc0Pin) { // ADC0 + return adc0->startSingleRead(pin); + } else if(adc1Pin) { // ADC1 + return adc1->startSingleRead(pin); + } else { // pin not valid in any ADC + adc0->fail_flag |= ADC_ERROR_WRONG_PIN; + adc1->fail_flag |= ADC_ERROR_WRONG_PIN; + return false; // all others are invalid + } + } + else if( adc_num==0 ) { // user wants ADC0 + return adc0->startSingleRead(pin); + } + else if( adc_num==1 ){ // user wants ADC 1 + return adc1->startSingleRead(pin); + } + adc0->fail_flag |= ADC_ERROR_OTHER; + return false; + #endif +} + +// Start a differential conversion between two pins (pinP - pinN) and enables interrupts. +/* It returns inmediately, get value with readSingle(). +* \param pinP must be A10 or A12. +* \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12). +* Other pins will return ADC_ERROR_DIFF_VALUE. +* This function is interrupt safe. The ADC interrupt will restore the adc to its previous settings and +* restart the adc if it stopped a measurement. If you modify the adc_isr then this won't happen. +*/ +bool ADC::startSingleDifferential(uint8_t pinP, uint8_t pinN, int8_t adc_num) { + #if ADC_NUM_ADCS==1 + /* Teensy 3.0, LC + */ + if( adc_num==1 ) { // If asked to use ADC1, return error + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + return false; + } + return adc0->startSingleDifferential(pinP, pinN); // use ADC0 + #elif ADC_NUM_ADCS==2 + /* Teensy 3.1 + */ + if( adc_num==-1 ) { // use no ADC in particular + // check which ADC can read the pin + bool adc0Pin = adc0->checkDifferentialPins(pinP, pinN); + bool adc1Pin = adc1->checkDifferentialPins(pinP, pinN); + + if(adc0Pin && adc1Pin) { // Both ADCs + if( (adc0->num_measurements) > (adc1->num_measurements)) { // use the ADC with less workload + return adc1->startSingleDifferential(pinP, pinN); + } else { + return adc0->startSingleDifferential(pinP, pinN); + } + } else if(adc0Pin) { // ADC0 + return adc0->startSingleDifferential(pinP, pinN); + } else if(adc1Pin) { // ADC1 + return adc1->startSingleDifferential(pinP, pinN); + } else { // pins not valid in any ADC + adc0->fail_flag |= ADC_ERROR_WRONG_PIN; + adc1->fail_flag |= ADC_ERROR_WRONG_PIN; + return false; // all others are invalid + } + } + else if( adc_num==0 ) { // user wants ADC0 + return adc0->startSingleDifferential(pinP, pinN); + } + else if( adc_num==1 ){ // user wants ADC 1 + return adc1->startSingleDifferential(pinP, pinN); + } + adc0->fail_flag |= ADC_ERROR_OTHER; + return false; + #endif +} + +// Reads the analog value of a single conversion. +/* Set the conversion with with startSingleRead(pin) or startSingleDifferential(pinP, pinN). +* \return the converted value. +*/ +int ADC::readSingle(int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + return adc1->readSingle(); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + return ADC_ERROR_VALUE; + #endif + } + return adc0->readSingle(); +} + + +// Starts continuous conversion on the pin. +/* It returns as soon as the ADC is set, use analogReadContinuous() to read the value. +*/ +bool ADC::startContinuous(uint8_t pin, int8_t adc_num) { + + #if ADC_NUM_ADCS==1 + /* Teensy 3.0, LC + */ + if( adc_num==1 ) { // If asked to use ADC1, return error + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + return false; + } + return adc0->startContinuous(pin); // use ADC0 + #elif ADC_NUM_ADCS==2 + /* Teensy 3.1 + */ + if( adc_num==-1 ) { // use no ADC in particular + // check which ADC can read the pin + bool adc0Pin = adc0->checkPin(pin); + bool adc1Pin = adc1->checkPin(pin); + + if(adc0Pin && adc1Pin) { // Both ADCs + if( (adc0->num_measurements) > (adc1->num_measurements)) { // use the ADC with less workload + return adc1->startContinuous(pin); + } else { + return adc0->startContinuous(pin); + } + } else if(adc0Pin) { // ADC0 + return adc0->startContinuous(pin); + } else if(adc1Pin) { // ADC1 + return adc1->startContinuous(pin); + } else { // pin not valid in any ADC + adc0->fail_flag |= ADC_ERROR_WRONG_PIN; + adc1->fail_flag |= ADC_ERROR_WRONG_PIN; + return false; // all others are invalid + } + } + else if( adc_num==0 ) { // user wants ADC0 + return adc0->startContinuous(pin); + } + else if( adc_num==1 ){ // user wants ADC 1 + return adc1->startContinuous(pin); + } + adc0->fail_flag |= ADC_ERROR_OTHER; + return false; + #endif +} + +// Starts continuous conversion between the pins (pinP-pinN). +/* It returns as soon as the ADC is set, use analogReadContinuous() to read the value. +* \param pinP must be A10 or A12. +* \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12). +* Other pins will return ADC_ERROR_DIFF_VALUE. +*/ +bool ADC::startContinuousDifferential(uint8_t pinP, uint8_t pinN, int8_t adc_num) { + #if ADC_NUM_ADCS==1 + /* Teensy 3.0, LC + */ + if( adc_num==1 ) { // If asked to use ADC1, return error + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + return false; + } + return adc0->startContinuousDifferential(pinP, pinN); // use ADC0 + #elif ADC_NUM_ADCS==2 + /* Teensy 3.1 + */ + if( adc_num==-1 ) { // use no ADC in particular + // check which ADC can read the pin + bool adc0Pin = adc0->checkDifferentialPins(pinP, pinN); + bool adc1Pin = adc1->checkDifferentialPins(pinP, pinN); + + if(adc0Pin && adc1Pin) { // Both ADCs + if( (adc0->num_measurements) > (adc1->num_measurements)) { // use the ADC with less workload + return adc1->startContinuousDifferential(pinP, pinN); + } else { + return adc0->startContinuousDifferential(pinP, pinN); + } + } else if(adc0Pin) { // ADC0 + return adc0->startContinuousDifferential(pinP, pinN); + } else if(adc1Pin) { // ADC1 + return adc1->startContinuousDifferential(pinP, pinN); + } else { // pins not valid in any ADC + adc0->fail_flag |= ADC_ERROR_WRONG_PIN; + adc1->fail_flag |= ADC_ERROR_WRONG_PIN; + return false; // all others are invalid + } + } + else if( adc_num==0 ) { // user wants ADC0 + return adc0->startContinuousDifferential(pinP, pinN); + } + else if( adc_num==1 ){ // user wants ADC 1 + return adc1->startContinuousDifferential(pinP, pinN); + } + adc0->fail_flag |= ADC_ERROR_OTHER; + return false; + #endif +} + +//! Reads the analog value of a continuous conversion. +/** Set the continuous conversion with with analogStartContinuous(pin) or startContinuousDifferential(pinP, pinN). +* \return the last converted value. +* If single-ended and 16 bits it's necessary to typecast it to an unsigned type (like uint16_t), +* otherwise values larger than 3.3/2 V are interpreted as negative! +*/ +int ADC::analogReadContinuous(int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 + return adc1->analogReadContinuous(); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + #endif + return false; + } + return adc0->analogReadContinuous(); +} + +//! Stops continuous conversion +void ADC::stopContinuous(int8_t adc_num) { + if(adc_num==1){ // user wants ADC 1, do nothing if it's a Teensy 3.0 + #if ADC_NUM_ADCS>=2 // Teensy 3.1 + adc1->stopContinuous(); + #else + adc0->fail_flag |= ADC_ERROR_WRONG_ADC; + #endif + return; + } + adc0->stopContinuous(); + return; +} + + + +//////////////// SYNCHRONIZED BLOCKING METHODS ////////////////// +///// IF THE BOARD HAS ONLY ONE ADC, THEY ARE EMPYT METHODS ///// +///////////////////////////////////////////////////////////////// + +#if ADC_NUM_ADCS>1 + +/*Returns the analog values of both pins, measured at the same time by the two ADC modules. +* It waits until the value is read and then returns the result as a struct Sync_result, +* use Sync_result.result_adc0 and Sync_result.result_adc1. +* If a comparison has been set up and fails, it will return ADC_ERROR_VALUE in both fields of the struct. +*/ +ADC::Sync_result ADC::analogSynchronizedRead(uint8_t pin0, uint8_t pin1) { + + Sync_result res = {ADC_ERROR_VALUE, ADC_ERROR_VALUE}; + + // check pins + if ( !adc0->checkPin(pin0) ) { + adc0->fail_flag |= ADC_ERROR_WRONG_PIN; + return res; + } + if ( !adc1->checkPin(pin1) ) { + adc1->fail_flag |= ADC_ERROR_WRONG_PIN; + return res; + } + + + // check if we are interrupting a measurement, store setting if so. + // vars to save the current state of the ADC in case it's in use + ADC_Module::ADC_Config old_adc0_config = {0}; + uint8_t wasADC0InUse = adc0->isConverting(); // is the ADC running now? + if(wasADC0InUse) { // this means we're interrupting a conversion + // save the current conversion config, the adc isr will restore the adc + __disable_irq(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc0->saveConfig(&old_adc0_config); + __enable_irq(); + } + ADC_Module::ADC_Config old_adc1_config = {0}; + uint8_t wasADC1InUse = adc1->isConverting(); // is the ADC running now? + if(wasADC1InUse) { // this means we're interrupting a conversion + // save the current conversion config, the adc isr will restore the adc + __disable_irq(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc1->saveConfig(&old_adc1_config); + __enable_irq(); + } + + // no continuous mode + adc0->singleMode(); + adc1->singleMode(); + + // start both measurements + adc0->startReadFast(pin0); + adc1->startReadFast(pin1); + + // wait for both ADCs to finish + while( (adc0->isConverting()) || (adc1->isConverting()) ) { // wait for both to finish + yield(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + } + + + __disable_irq(); // make sure nothing interrupts this part + if ( adc0->isComplete() ) { // conversion succeded + res.result_adc0 = adc0->readSingle(); + } else { // comparison was false + adc0->fail_flag |= ADC_ERROR_COMPARISON; + } + if ( adc1->isComplete() ) { // conversion succeded + res.result_adc1 = adc1->readSingle(); + } else { // comparison was false + adc1->fail_flag |= ADC_ERROR_COMPARISON; + } + __enable_irq(); + + + // if we interrupted a conversion, set it again + if (wasADC0InUse) { + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc0->loadConfig(&old_adc0_config); + } + if (wasADC1InUse) { + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc1->loadConfig(&old_adc1_config); + } + + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + + return res; +} + +/*Returns the diff analog values of both sets of pins, measured at the same time by the two ADC modules. +* It waits until the value is read and then returns the result as a struct Sync_result, +* use Sync_result.result_adc0 and Sync_result.result_adc1. +* If a comparison has been set up and fails, it will return ADC_ERROR_VALUE in both fields of the struct. +*/ +ADC::Sync_result ADC::analogSynchronizedReadDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N) { + + Sync_result res = {ADC_ERROR_VALUE, ADC_ERROR_VALUE};; + + // check pins + if(!adc0->checkDifferentialPins(pin0P, pin0N)) { + adc0->fail_flag |= ADC_ERROR_WRONG_PIN; + return res; // all others are invalid + } + if(!adc1->checkDifferentialPins(pin1P, pin1N)) { + adc1->fail_flag |= ADC_ERROR_WRONG_PIN; + return res; // all others are invalid + } + + uint8_t resolution0 = adc0->getResolution(); + uint8_t resolution1 = adc1->getResolution(); + + // check if we are interrupting a measurement, store setting if so. + // vars to save the current state of the ADC in case it's in use + ADC_Module::ADC_Config old_adc0_config = {0}; + uint8_t wasADC0InUse = adc0->isConverting(); // is the ADC running now? + if(wasADC0InUse) { // this means we're interrupting a conversion + // save the current conversion config, the adc isr will restore the adc + __disable_irq(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc0->saveConfig(&old_adc0_config); + __enable_irq(); + } + ADC_Module::ADC_Config old_adc1_config = {0}; + uint8_t wasADC1InUse = adc1->isConverting(); // is the ADC running now? + if(wasADC1InUse) { // this means we're interrupting a conversion + // save the current conversion config, the adc isr will restore the adc + __disable_irq(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc1->saveConfig(&old_adc1_config); + __enable_irq(); + } + + // no continuous mode + adc0->singleMode(); + adc1->singleMode(); + + // start both measurements + adc0->startDifferentialFast(pin0P, pin0N); + adc1->startDifferentialFast(pin1P, pin1N); + + // wait for both ADCs to finish + while( (adc0->isConverting()) || (adc1->isConverting()) ) { + yield(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + } + __disable_irq(); // make sure nothing interrupts this part + if (adc0->isComplete()) { // conversion succeded + res.result_adc0 = adc0->readSingle(); + if(resolution0==16) { // 16 bit differential is actually 15 bit + 1 bit sign + res.result_adc0 *= 2; // multiply by 2 as if it were really 16 bits, so that getMaxValue gives a correct value. + } + } else { // comparison was false + adc0->fail_flag |= ADC_ERROR_COMPARISON; + } + if (adc1->isComplete()) { // conversion succeded + res.result_adc1 = adc1->readSingle(); + if(resolution1==16) { // 16 bit differential is actually 15 bit + 1 bit sign + res.result_adc1 *= 2; // multiply by 2 as if it were really 16 bits, so that getMaxValue gives a correct value. + } + } else { // comparison was false + adc1->fail_flag |= ADC_ERROR_COMPARISON; + } + __enable_irq(); + + + // if we interrupted a conversion, set it again + if (wasADC0InUse) { + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc0->loadConfig(&old_adc0_config); + } + if (wasADC1InUse) { + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc1->loadConfig(&old_adc1_config); + } + + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + + return res; +} + +/////////////// SYNCHRONIZED NON-BLOCKING METHODS ////////////// + +// Starts an analog measurement at the same time on the two ADC modules +/* It returns inmediately, get value with readSynchronizedSingle(). +* If the pin is incorrect it returns false +* If this function interrupts a measurement, it stores the settings in adc_config +*/ +bool ADC::startSynchronizedSingleRead(uint8_t pin0, uint8_t pin1) { + + // check pins + if ( !adc0->checkPin(pin0) ) { + adc0->fail_flag |= ADC_ERROR_WRONG_PIN; + return false; + } + if ( !adc1->checkPin(pin1) ) { + adc1->fail_flag |= ADC_ERROR_WRONG_PIN; + return false; + } + + // check if we are interrupting a measurement, store setting if so. + adc0->adcWasInUse = adc0->isConverting(); // is the ADC running now? + if(adc0->adcWasInUse) { // this means we're interrupting a conversion + // save the current conversion config, the adc isr will restore the adc + __disable_irq(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc0->saveConfig(&adc0->adc_config); + __enable_irq(); + } + adc1->adcWasInUse = adc1->isConverting(); // is the ADC running now? + if(adc1->adcWasInUse) { // this means we're interrupting a conversion + // save the current conversion config, the adc isr will restore the adc + __disable_irq(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc1->saveConfig(&adc1->adc_config); + __enable_irq(); + } + + // no continuous mode + adc0->singleMode(); + adc1->singleMode(); + + // start both measurements + adc0->startReadFast(pin0); + adc1->startReadFast(pin1); + + + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + return true; + +} + +// Start a differential conversion between two pins (pin0P - pin0N) and (pin1P - pin1N) +/* It returns inmediately, get value with readSynchronizedSingle(). +* \param pinP must be A10 or A12. +* \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12). +* Other pins will return false. +* If this function interrupts a measurement, it stores the settings in adc_config +*/ +bool ADC::startSynchronizedSingleDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N) { + + // check pins + if(!adc0->checkDifferentialPins(pin0P, pin0N)) { + adc0->fail_flag |= ADC_ERROR_WRONG_PIN; + return false; // all others are invalid + } + if(!adc1->checkDifferentialPins(pin1P, pin1N)) { + adc1->fail_flag |= ADC_ERROR_WRONG_PIN; + return false; // all others are invalid + } + + // check if we are interrupting a measurement, store setting if so. + adc0->adcWasInUse = adc0->isConverting(); // is the ADC running now? + if(adc0->adcWasInUse) { // this means we're interrupting a conversion + // save the current conversion config, the adc isr will restore the adc + __disable_irq(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc0->saveConfig(&adc0->adc_config); + __enable_irq(); + } + adc1->adcWasInUse = adc1->isConverting(); // is the ADC running now? + if(adc1->adcWasInUse) { // this means we're interrupting a conversion + // save the current conversion config, the adc isr will restore the adc + __disable_irq(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc1->saveConfig(&adc1->adc_config); + __enable_irq(); + } + + // no continuous mode + adc0->singleMode(); + adc1->singleMode(); + + // start both measurements + adc0->startDifferentialFast(pin0P, pin0N); + adc1->startDifferentialFast(pin1P, pin1N); + + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + + return true; +} + +// Reads the analog value of a single conversion. +/* +* \return the converted value. +*/ +ADC::Sync_result ADC::readSynchronizedSingle() { + ADC::Sync_result res; + + res.result_adc0 = adc0->readSingle(); + res.result_adc1 = adc1->readSingle(); + + return res; +} + + +///////////// SYNCHRONIZED CONTINUOUS CONVERSION METHODS //////////// + +//! Starts a continuous conversion in both ADCs simultaneously +/** Use readSynchronizedContinuous to get the values +* +*/ +bool ADC::startSynchronizedContinuous(uint8_t pin0, uint8_t pin1) { + + // check pins + if ( !adc0->checkPin(pin0) ) { + adc0->fail_flag |= ADC_ERROR_WRONG_PIN; + return false; + } + if ( !adc1->checkPin(pin1) ) { + adc1->fail_flag |= ADC_ERROR_WRONG_PIN; + return false; + } + + adc0->startContinuous(pin0); + adc1->startContinuous(pin1); + + // setup the conversions the usual way, but to make sure that they are + // as synchronized as possible we stop and restart them one after the other. + const uint32_t temp_ADC0_SC1A = ADC0_SC1A; ADC0_SC1A = 0x1F; + const uint32_t temp_ADC1_SC1A = ADC1_SC1A; ADC1_SC1A = 0x1F; + + __disable_irq(); // both measurements should have a maximum delay of an instruction time + ADC0_SC1A = temp_ADC0_SC1A; + ADC1_SC1A = temp_ADC1_SC1A; + __enable_irq(); + + return true; +} + +//! Starts a continuous differential conversion in both ADCs simultaneously +/** Use readSynchronizedContinuous to get the values +* +*/ +bool ADC::startSynchronizedContinuousDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N) { + + // check pins + if(!adc0->checkDifferentialPins(pin0P, pin0N)) { + adc0->fail_flag |= ADC_ERROR_WRONG_PIN; + return false; // all others are invalid + } + if(!adc1->checkDifferentialPins(pin1P, pin1N)) { + adc1->fail_flag |= ADC_ERROR_WRONG_PIN; + return false; // all others are invalid + } + + adc0->startContinuousDifferential(pin0P, pin0N); + adc1->startContinuousDifferential(pin1P, pin1N); + + // setup the conversions the usual way, but to make sure that they are + // as synchronized as possible we stop and restart them one after the other. + const uint32_t temp_ADC0_SC1A = ADC0_SC1A; ADC0_SC1A = 0x1F; + const uint32_t temp_ADC1_SC1A = ADC1_SC1A; ADC1_SC1A = 0x1F; + + __disable_irq(); + ADC0_SC1A = temp_ADC0_SC1A; + ADC1_SC1A = temp_ADC1_SC1A; + __enable_irq(); + + + return true; +} + +//! Returns the values of both ADCs. +ADC::Sync_result ADC::readSynchronizedContinuous() { + ADC::Sync_result res; + + res.result_adc0 = adc0->analogReadContinuous(); + res.result_adc1 = adc1->analogReadContinuous(); + + return res; +} + +//! Stops synchronous continuous conversion +void ADC::stopSynchronizedContinuous() { + + adc0->stopContinuous(); + adc1->stopContinuous(); +} + +#else // ADC_NUM_ADCS=1 +// Empty definitions so code written for all Teensy will compile + +ADC::Sync_result ADC::analogSynchronizedRead(uint8_t pin0, uint8_t pin1) {ADC::Sync_result res={0}; return res;} +ADC::Sync_result ADC::analogSynchronizedReadDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N) { + ADC::Sync_result res={0}; + return res; +} + +bool ADC::startSynchronizedSingleRead(uint8_t pin0, uint8_t pin1) { return false; } +bool ADC::startSynchronizedSingleDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N) { return false; } + +ADC::Sync_result ADC::readSynchronizedSingle() {ADC::Sync_result res={0}; return res;} + +bool ADC::startSynchronizedContinuous(uint8_t pin0, uint8_t pin1) {return false;} +bool ADC::startSynchronizedContinuousDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N) {return false;} +ADC::Sync_result ADC::readSynchronizedContinuous() {ADC::Sync_result res={0}; return res;} +void ADC::stopSynchronizedContinuous() {} + +#endif diff --git a/Firmware_V2/src/libraries/ADC/ADC.h b/Firmware_V2/src/libraries/ADC/ADC.h new file mode 100644 index 0000000..c29a600 --- /dev/null +++ b/Firmware_V2/src/libraries/ADC/ADC.h @@ -0,0 +1,447 @@ +/* Teensy 3.x, LC ADC library + * https://github.com/pedvide/ADC + * Copyright (c) 2015 Pedro Villanueva + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* ADC.h: Control for one (Teensy 3.0, LC) or two ADC modules (Teensy 3.1). + * + */ + +/* TODO +* - Function to measure more that 1 pin consecutively (stream?) +* +* bugs: +* - comparison values in 16 bit differential mode are twice what they should be +*/ + +#ifndef ADC_H +#define ADC_H + +#define ADC_0 0 +#define ADC_1 1 + +// include ADC module class +#include "ADC_Module.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Class ADC: Controls the Teensy 3.x ADC +* +*/ +class ADC +{ + protected: + private: + + // ADCs objects + ADC_Module adc0_obj; + #if ADC_NUM_ADCS>1 + ADC_Module adc1_obj; + #endif + + const uint8_t num_ADCs = ADC_NUM_ADCS; + + + public: + + /** Default constructor */ + ADC(); + + + // create both adc objects + + //! Object to control the ADC0 + ADC_Module *const adc0; // adc object pointer + #if ADC_NUM_ADCS>1 + //! Object to control the ADC1 + ADC_Module *const adc1; // adc object pointer + #endif + + /////////////// METHODS TO SET/GET SETTINGS OF THE ADC //////////////////// + + //! Set the voltage reference you prefer, default is 3.3 V (VCC) + /*! + * \param type can be ADC_REF_3V3, ADC_REF_1V2 (not for Teensy LC) or ADC_REF_EXT. + * + * It re-calibrates at the end. + */ + void setReference(uint8_t type, int8_t adc_num = -1); + + + //! Change the resolution of the measurement. + /*! + * \param bits is the number of bits of resolution. + * For single-ended measurements: 8, 10, 12 or 16 bits. + * For differential measurements: 9, 11, 13 or 16 bits. + * If you want something in between (11 bits single-ended for example) select the immediate higher + * and shift the result one to the right. + * + * Whenever you change the resolution, change also the comparison values (if you use them). + */ + void setResolution(uint8_t bits, int8_t adc_num = -1); + + //! Returns the resolution of the ADC_Module. + uint8_t getResolution(int8_t adc_num = -1); + + //! Returns the maximum value for a measurement: 2^res-1. + uint32_t getMaxValue(int8_t adc_num = -1); + + + //! Sets the conversion speed (changes the ADC clock, ADCK) + /** + * \param speed can be ADC_VERY_LOW_SPEED, ADC_LOW_SPEED, ADC_MED_SPEED, ADC_HIGH_SPEED_16BITS, ADC_HIGH_SPEED or ADC_VERY_HIGH_SPEED. + * + * ADC_VERY_LOW_SPEED is guaranteed to be the lowest possible speed within specs for resolutions less than 16 bits (higher than 1 MHz), + * it's different from ADC_LOW_SPEED only for 24, 4 or 2 MHz bus frequency. + * ADC_LOW_SPEED is guaranteed to be the lowest possible speed within specs for all resolutions (higher than 2 MHz). + * ADC_MED_SPEED is always >= ADC_LOW_SPEED and <= ADC_HIGH_SPEED. + * ADC_HIGH_SPEED_16BITS is guaranteed to be the highest possible speed within specs for all resolutions (lower or eq than 12 MHz). + * ADC_HIGH_SPEED is guaranteed to be the highest possible speed within specs for resolutions less than 16 bits (lower or eq than 18 MHz). + * ADC_VERY_HIGH_SPEED may be out of specs, it's different from ADC_HIGH_SPEED only for 48, 40 or 24 MHz bus frequency. + * + * Additionally the conversion speed can also be ADC_ADACK_2_4, ADC_ADACK_4_0, ADC_ADACK_5_2 and ADC_ADACK_6_2, + * where the numbers are the frequency of the ADC clock (ADCK) in MHz and are independent on the bus speed. + * This is useful if you are using the Teensy at a very low clock frequency but want faster conversions, + * but if F_BUS= compValue (greaterThan=1) or < compValue (greaterThan=0) + * Call it after changing the resolution + * Use with interrupts or poll conversion completion with isComplete() + */ + void enableCompare(int16_t compValue, bool greaterThan, int8_t adc_num = -1); + + //! Enable the compare function to a range + /** A conversion will be completed only when the ADC value is inside (insideRange=1) or outside (=0) + * the range given by (lowerLimit, upperLimit),including (inclusive=1) the limits or not (inclusive=0). + * See Table 31-78, p. 617 of the freescale manual. + * Call it after changing the resolution + * Use with interrupts or poll conversion completion with isComplete() + */ + void enableCompareRange(int16_t lowerLimit, int16_t upperLimit, bool insideRange, bool inclusive, int8_t adc_num = -1); + + //! Disable the compare function + void disableCompare(int8_t adc_num = -1); + + + //! Enable and set PGA + /** Enables the PGA and sets the gain + * Use only for signals lower than 1.2 V + * \param gain can be 1, 2, 4, 8, 16, 32 or 64 + * + */ + void enablePGA(uint8_t gain, int8_t adc_num = -1); + + //! Returns the PGA level + /** PGA level = from 1 to 64 + */ + uint8_t getPGA(int8_t adc_num = -1); + + //! Disable PGA + void disablePGA(int8_t adc_num = -1); + + + + ////////////// INFORMATION ABOUT THE STATE OF THE ADC ///////////////// + + //! Is the ADC converting at the moment? + bool isConverting(int8_t adc_num = -1); + + //! Is an ADC conversion ready? + /** + * \return 1 if yes, 0 if not. + * When a value is read this function returns 0 until a new value exists + * So it only makes sense to call it with continuous or non-blocking methods + */ + bool isComplete(int8_t adc_num = -1); + + //! Is the ADC in differential mode? + bool isDifferential(int8_t adc_num = -1); + + //! Is the ADC in continuous mode? + bool isContinuous(int8_t adc_num = -1); + + + + //////////////// BLOCKING CONVERSION METHODS ////////////////// + + //! Returns the analog value of the pin. + /** It waits until the value is read and then returns the result. + * If a comparison has been set up and fails, it will return ADC_ERROR_VALUE. + * This function is interrupt safe, so it will restore the adc to the state it was before being called + * If more than one ADC exists, it will select the module with less workload, you can force a selection using + * adc_num. If you select ADC1 in Teensy 3.0 it will return ADC_ERROR_VALUE. + * \param pin can be any of the analog pins + * \param adc_num ADC_X ADC module + */ + int analogRead(uint8_t pin, int8_t adc_num = -1); + + //! Reads the differential analog value of two pins (pinP - pinN). + /** It waits until the value is read and then returns the result. + * If a comparison has been set up and fails, it will return ADC_ERROR_VALUE. + * \param pinP must be A10 or A12. + * \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12). + * \param adc_num ADC_X ADC module + * Other pins will return ADC_ERROR_VALUE. + * + * This function is interrupt safe, so it will restore the adc to the state it was before being called + * If more than one ADC exists, it will select the module with less workload, you can force a selection using + * adc_num + */ + int analogReadDifferential(uint8_t pinP, uint8_t pinN, int8_t adc_num = -1); + + + /////////////// NON-BLOCKING CONVERSION METHODS ////////////// + + //! Starts an analog measurement on the pin and enables interrupts. + /** It returns immediately, get value with readSingle(). + * If the pin is incorrect it returns ADC_ERROR_VALUE + * If this function interrupts a measurement, it stores the settings in adc_config + * \param pin can be any of the analog pins + * \param adc_num ADC_X ADC module + */ + bool startSingleRead(uint8_t pin, int8_t adc_num = -1); + + //! Start a differential conversion between two pins (pinP - pinN) and enables interrupts. + /** It returns immediately, get value with readSingle(). + * \param pinP must be A10 or A12. + * \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12). + * \param adc_num ADC_X ADC module + * + * Other pins will return ADC_ERROR_DIFF_VALUE. + * If this function interrupts a measurement, it stores the settings in adc_config + */ + bool startSingleDifferential(uint8_t pinP, uint8_t pinN, int8_t adc_num = -1); + + //! Reads the analog value of a single conversion. + /** Set the conversion with with startSingleRead(pin) or startSingleDifferential(pinP, pinN). + * \param adc_num ADC_X ADC module + * \return the converted value. + */ + int readSingle(int8_t adc_num = -1); + + + + ///////////// CONTINUOUS CONVERSION METHODS //////////// + + //! Starts continuous conversion on the pin. + /** It returns as soon as the ADC is set, use analogReadContinuous() to read the value. + * \param pin can be any of the analog pins + * \param adc_num ADC_X ADC module + */ + bool startContinuous(uint8_t pin, int8_t adc_num = -1); + + //! Starts continuous conversion between the pins (pinP-pinN). + /** It returns as soon as the ADC is set, use analogReadContinuous() to read the value. + * \param pinP must be A10 or A12. + * \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12). + * \param adc_num ADC_X ADC module + * Other pins will return ADC_ERROR_DIFF_VALUE. + */ + bool startContinuousDifferential(uint8_t pinP, uint8_t pinN, int8_t adc_num = -1); + + //! Reads the analog value of a continuous conversion. + /** Set the continuous conversion with with analogStartContinuous(pin) or startContinuousDifferential(pinP, pinN). + * \param adc_num ADC_X ADC module + * \return the last converted value. + * If single-ended and 16 bits it's necessary to typecast it to an unsigned type (like uint16_t), + * otherwise values larger than 3.3/2 V are interpreted as negative! + */ + int analogReadContinuous(int8_t adc_num = -1); + + //! Stops continuous conversion + /** + * \param adc_num ADC_X ADC module + */ + void stopContinuous(int8_t adc_num = -1); + + + + /////////// SYNCHRONIZED METHODS /////////////// + ///// IF THE BOARD HAS ONLY ONE ADC, THEY ARE EMPYT METHODS ///// + + //! Struct for synchronous measurements + /** result_adc0 has the result from ADC0 and result_adc1 from ADC1. + */ + struct Sync_result{ + int32_t result_adc0, result_adc1; + }; + + //////////////// SYNCHRONIZED BLOCKING METHODS ////////////////// + + //! Returns the analog values of both pins, measured at the same time by the two ADC modules. + /** It waits until the values are read and then returns the result as a struct Sync_result, + * use Sync_result.result_adc0 and Sync_result.result_adc1. + * If a comparison has been set up and fails, it will return ADC_ERROR_VALUE in both fields of the struct. + * This function is interrupt safe, so it will restore the adc to the state it was before being called + */ + Sync_result analogSynchronizedRead(uint8_t pin0, uint8_t pin1); + + //! Same as analogSynchronizedRead + Sync_result analogSyncRead(uint8_t pin0, uint8_t pin1) __attribute__((always_inline)) {return analogSynchronizedRead(pin0, pin1);} + + //! Returns the differential analog values of both sets of pins, measured at the same time by the two ADC modules. + /** It waits until the values are read and then returns the result as a struct Sync_result, + * use Sync_result.result_adc0 and Sync_result.result_adc1. + * If a comparison has been set up and fails, it will return ADC_ERROR_VALUE in both fields of the struct. + * This function is interrupt safe, so it will restore the adc to the state it was before being called + */ + Sync_result analogSynchronizedReadDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N); + + //! Same as analogSynchronizedReadDifferential + Sync_result analogSyncReadDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N) __attribute__((always_inline)) { + return analogSynchronizedReadDifferential(pin0P, pin0N, pin1P, pin1N); + } + + /////////////// SYNCHRONIZED NON-BLOCKING METHODS ////////////// + + //! Starts an analog measurement at the same time on the two ADC modules + /** It returns immediately, get value with readSynchronizedSingle(). + * If the pin is incorrect it returns false + * If this function interrupts a measurement, it stores the settings in adc_config + */ + bool startSynchronizedSingleRead(uint8_t pin0, uint8_t pin1); + + //! Start a differential conversion between two pins (pin0P - pin0N) and (pin1P - pin1N) + /** It returns immediately, get value with readSynchronizedSingle(). + * \param pin0P, pin1P must be A10 or A12. + * \param pin0N, pin1N must be A11 (if pinP=A10) or A13 (if pinP=A12). + * Other pins will return false. + * If this function interrupts a measurement, it stores the settings in adc_config + */ + bool startSynchronizedSingleDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N); + + //! Reads the analog value of a single conversion. + /** + * \return the converted value. + */ + Sync_result readSynchronizedSingle(); + + + ///////////// SYNCHRONIZED CONTINUOUS CONVERSION METHODS //////////// + + //! Starts a continuous conversion in both ADCs simultaneously + /** Use readSynchronizedContinuous to get the values + * + */ + bool startSynchronizedContinuous(uint8_t pin0, uint8_t pin1); + + //! Starts a continuous differential conversion in both ADCs simultaneously + /** Use readSynchronizedContinuous to get the values + * + */ + bool startSynchronizedContinuousDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N); + + //! Returns the values of both ADCs. + Sync_result readSynchronizedContinuous(); + + //! Stops synchronous continuous conversion + void stopSynchronizedContinuous(); + + + //! Translate pin number to SC1A nomenclature + // should this be a constexpr? + static const uint8_t channel2sc1aADC0[44]; + #if ADC_NUM_ADCS>1 + //! Translate pin number to SC1A nomenclature + static const uint8_t channel2sc1aADC1[44]; + #endif + + //! Translate pin number to SC1A nomenclature for differential pins + static const uint8_t sc1a2channelADC0[44]; + #if ADC_NUM_ADCS>1 + //! Translate pin number to SC1A nomenclature for differential pins + static const uint8_t sc1a2channelADC1[44]; + #endif + + + // defined in ADC_Module.h +// struct ADC_NLIST { +// uint8_t pin, sc1a; +// }; + //! Translate differential pin number to SC1A nomenclature + static const ADC_Module::ADC_NLIST diff_table_ADC0[ADC_DIFF_PAIRS]; + #if ADC_NUM_ADCS>1 + //! Translate differential pin number to SC1A nomenclature + static const ADC_Module::ADC_NLIST diff_table_ADC1[ADC_DIFF_PAIRS]; + #endif + + +}; + + + + +#ifdef __cplusplus +} +#endif + + +#endif // ADC_H diff --git a/Firmware_V2/src/libraries/ADC/ADC_Module.cpp b/Firmware_V2/src/libraries/ADC/ADC_Module.cpp new file mode 100644 index 0000000..aabb08d --- /dev/null +++ b/Firmware_V2/src/libraries/ADC/ADC_Module.cpp @@ -0,0 +1,1307 @@ +/* Teensy 3.x, LC ADC library + * https://github.com/pedvide/ADC + * Copyright (c) 2015 Pedro Villanueva + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* ADC_Module.cpp: Implements the fuctions of a Teensy 3.x, LC ADC module + * + */ + + + +#include "ADC_Module.h" +//#include "ADC.h" + + +/* Constructor +* Point the registers to the correct ADC module +* Copy the correct channel2sc1a +* Call init +* The very long initializer list could be shorter using some kind of struct? +*/ +ADC_Module::ADC_Module(uint8_t ADC_number, const uint8_t* const a_channel2sc1a, const ADC_NLIST* const a_diff_table) : + ADC_num(ADC_number) + , channel2sc1a(a_channel2sc1a) + , diff_table(a_diff_table) + , adc_offset((uint32_t)0x20000) + , ADC_SC1A(&ADC0_SC1A + adc_offset*ADC_num) + , ADC_SC1B(&ADC0_SC1B + adc_offset*ADC_num) + , ADC_CFG1(&ADC0_CFG1 + adc_offset*ADC_num) + , ADC_CFG2(&ADC0_CFG2 + adc_offset*ADC_num) + , ADC_RA(&ADC0_RA + adc_offset*ADC_num) + , ADC_RB(&ADC0_RB + adc_offset*ADC_num) + , ADC_CV1(&ADC0_CV1 + adc_offset*ADC_num) + , ADC_CV2(&ADC0_CV2 + adc_offset*ADC_num) + , ADC_SC2(&ADC0_SC2 + adc_offset*ADC_num) + , ADC_SC3(&ADC0_SC3 + adc_offset*ADC_num) + , ADC_PGA(&ADC0_PGA + adc_offset*ADC_num) + , ADC_OFS(&ADC0_OFS + adc_offset*ADC_num) + , ADC_PG(&ADC0_PG + adc_offset*ADC_num) + , ADC_MG(&ADC0_MG + adc_offset*ADC_num) + , ADC_CLPD(&ADC0_CLPD + adc_offset*ADC_num) + , ADC_CLPS(&ADC0_CLPS + adc_offset*ADC_num) + , ADC_CLP4(&ADC0_CLP4 + adc_offset*ADC_num) + , ADC_CLP3(&ADC0_CLP3 + adc_offset*ADC_num) + , ADC_CLP2(&ADC0_CLP2 + adc_offset*ADC_num) + , ADC_CLP1(&ADC0_CLP1 + adc_offset*ADC_num) + , ADC_CLP0(&ADC0_CLP0 + adc_offset*ADC_num) + , ADC_CLMD(&ADC0_CLMD + adc_offset*ADC_num) + , ADC_CLMS(&ADC0_CLMS + adc_offset*ADC_num) + , ADC_CLM4(&ADC0_CLM4 + adc_offset*ADC_num) + , ADC_CLM3(&ADC0_CLM3 + adc_offset*ADC_num) + , ADC_CLM2(&ADC0_CLM2 + adc_offset*ADC_num) + , ADC_CLM1(&ADC0_CLM1 + adc_offset*ADC_num) + , ADC_CLM0(&ADC0_CLM0 + adc_offset*ADC_num) + , PDB0_CHnC1(&PDB0_CH0C1 + ADC_num*0xA) + , IRQ_ADC(IRQ_ADC0 + ADC_num*1) + { + + + + + + // call our init + analog_init(); + + + +} + +/* Initialize stuff: +* - Start Vref module +* - Clear all fail flags +* - Internal reference (default: external vcc) +* - Mux between a and b channels (b channels) +* - Calibrate with 32 averages and low speed +* - When first calibration is done it sets: +* - Resolution (default: 10 bits) +* - Conversion speed and sampling time (both set to medium speed) +* - Averaging (set to 4) +*/ +void ADC_Module::analog_init() { + + // default settings: + /* + - 10 bits resolution + - 4 averages + - vcc reference + - no interrupts + - pga gain=1 + - conversion speed = medium + - sampling speed = medium + initiate to 0 (or 1) so the corresponding functions change it to the correct value + */ + analog_res_bits = 0; + analog_max_val = 0; + analog_num_average = 0; + analog_reference_internal = 2; + var_enableInterrupts = 0; + pga_value = 1; + + conversion_speed = 0; + sampling_speed = 0; + + calibrating = 0; + + fail_flag = ADC_ERROR_CLEAR; // clear all errors + + num_measurements = 0; + + // select b channels + // *ADC_CFG2_muxsel = 1; + setBit(ADC_CFG2, ADC_CFG2_MUXSEL_BIT); + + // set reference to vcc + setReference(ADC_REF_3V3); + + // set resolution to 10 + setResolution(10); + + // the first calibration will use 32 averages and lowest speed, + // when this calibration is over the averages and speed will be set to default by wait_for_cal and init_calib will be cleared. + init_calib = 1; + setAveraging(32); + setConversionSpeed(ADC_LOW_SPEED); + setSamplingSpeed(ADC_LOW_SPEED); + + // begin init calibration + calibrate(); +} + +// starts calibration +void ADC_Module::calibrate() { + + __disable_irq(); + + calibrating = 1; + // *ADC_SC3_cal = 0; // stop possible previous calibration + clearBit(ADC_SC3, ADC_SC3_CAL_BIT); + // *ADC_SC3_calf = 1; // clear possible previous error + setBit(ADC_SC3, ADC_SC3_CALF_BIT); + // *ADC_SC3_cal = 1; // start calibration + setBit(ADC_SC3, ADC_SC3_CAL_BIT); + + __enable_irq(); +} + + +/* Waits until calibration is finished and writes the corresponding registers +* +*/ +void ADC_Module::wait_for_cal(void) { + uint16_t sum; + + while(getBit(ADC_SC3, ADC_SC3_CAL_BIT)) { // Bit ADC_SC3_CAL in register ADC0_SC3 cleared when calib. finishes. + yield(); + } + + if(getBit(ADC_SC3, ADC_SC3_CALF_BIT)) { // calibration failed + fail_flag |= ADC_ERROR_CALIB; // the user should know and recalibrate manually + } + + __disable_irq(); + if (calibrating) { + sum = *ADC_CLPS + *ADC_CLP4 + *ADC_CLP3 + *ADC_CLP2 + *ADC_CLP1 + *ADC_CLP0; + sum = (sum / 2) | 0x8000; + *ADC_PG = sum; + + sum = *ADC_CLMS + *ADC_CLM4 + *ADC_CLM3 + *ADC_CLM2 + *ADC_CLM1 + *ADC_CLM0; + sum = (sum / 2) | 0x8000; + *ADC_MG = sum; + + calibrating = 0; + } + __enable_irq(); + + // the first calibration uses 32 averages and lowest speed, + // when this calibration is over, set the averages and speed to default. + if(init_calib) { + + // set conversion speed to medium + setConversionSpeed(ADC_MED_SPEED); + + // set sampling speed to medium + setSamplingSpeed(ADC_MED_SPEED); + + // number of averages to 4 + setAveraging(4); + + init_calib = 0; // clear + } + +} + +//! Starts the calibration sequence, waits until it's done and writes the results +/** Usually it's not necessary to call this function directly, but do it if the "enviroment" changed +* significantly since the program was started. +*/ +void ADC_Module::recalibrate() { + + calibrate(); + + wait_for_cal(); +} + + + +/////////////// METHODS TO SET/GET SETTINGS OF THE ADC //////////////////// + + +/* Set the voltage reference you prefer, default is 3.3V +* It needs to recalibrate +* Use ADC_REF_3V3, ADC_REF_1V2 (not for Teensy LC) or ADC_REF_EXT +*/ +void ADC_Module::setReference(uint8_t type) { + if (analog_reference_internal==type) { // don't need to change anything + return; + } + + + + if (type == ADC_REF_ALT) { // 1.2V ref for Teensy 3.x, 3.3 VDD for Teensy LC + // internal reference requested + + startInternalReference(); // enable VREF if Teensy 3.x + + analog_reference_internal = ADC_REF_ALT; + + // *ADC_SC2_ref = 1; // uses bitband: atomic + setBit(ADC_SC2, ADC_SC2_REFSEL0_BIT); + + } else if(type == ADC_REF_DEFAULT) { // ext ref for all Teensys, vcc also for Teensy 3.x + // vcc or external reference requested + + stopInternalReference(); // disable 1.2V reference source when using the external ref (p. 102, 3.7.1.7) + + analog_reference_internal = ADC_REF_DEFAULT; + + // *ADC_SC2_ref = 0; // uses bitband: atomic + clearBit(ADC_SC2, ADC_SC2_REFSEL0_BIT); + } + + calibrate(); +} + +//! Start the 1.2V internal reference (if present) +void ADC_Module::startInternalReference() { +#if ADC_USE_INTERNAL + VREF_TRM = VREF_TRM_CHOPEN | 0x20; // enable module and set the trimmer to medium (max=0x3F=63) + VREF_SC = VREF_SC_VREFEN | VREF_SC_REGEN | VREF_SC_ICOMPEN | VREF_SC_MODE_LV(1); // (=0xE1) enable 1.2 volt ref with all compensations +#endif +} + +//! Stops the internal reference +void ADC_Module::stopInternalReference() { +#if ADC_USE_INTERNAL + VREF_SC = 0; +#endif +} + + +/* Change the resolution of the measurement +* For single-ended measurements: 8, 10, 12 or 16 bits. +* For differential measurements: 9, 11, 13 or 16 bits. +* If you want something in between (11 bits single-ended for example) select the inmediate higher +* and shift the result one to the right. +* +* It doesn't recalibrate +*/ +void ADC_Module::setResolution(uint8_t bits) { + + if(analog_res_bits==bits) { + return; + } + + uint8_t config; + + if (calibrating) wait_for_cal(); + + if (bits <8) { + config = 8; + } else if (bits >= 14) { + config = 16; + } else { + config = bits; + } + + // conversion resolution + // single-ended 8 bits is the same as differential 9 bits, etc. + if ( (config == 8) || (config == 9) ) { + // *ADC_CFG1_mode1 = 0; + // *ADC_CFG1_mode0 = 0; + clearBit(ADC_CFG1, ADC_CFG1_MODE1_BIT); + clearBit(ADC_CFG1, ADC_CFG1_MODE0_BIT); + analog_max_val = 255; // diff mode 9 bits has 1 bit for sign, so max value is the same as single 8 bits + } else if ( (config == 10 )|| (config == 11) ) { + // *ADC_CFG1_mode1 = 1; + // *ADC_CFG1_mode0 = 0; + setBit(ADC_CFG1, ADC_CFG1_MODE1_BIT); + clearBit(ADC_CFG1, ADC_CFG1_MODE0_BIT); + analog_max_val = 1023; + } else if ( (config == 12 )|| (config == 13) ) { + // *ADC_CFG1_mode1 = 0; + // *ADC_CFG1_mode0 = 1; + clearBit(ADC_CFG1, ADC_CFG1_MODE1_BIT); + setBit(ADC_CFG1, ADC_CFG1_MODE0_BIT); + analog_max_val = 4095; + } else { + // *ADC_CFG1_mode1 = 1; + // *ADC_CFG1_mode0 = 1; + setBit(ADC_CFG1, ADC_CFG1_MODE1_BIT); + setBit(ADC_CFG1, ADC_CFG1_MODE0_BIT); + analog_max_val = 65535; + } + + analog_res_bits = config; + + // no recalibration is needed when changing the resolution, p. 619 + +} + +/* Returns the resolution of the ADC +* +*/ +uint8_t ADC_Module::getResolution() { + return analog_res_bits; +} + +/* Returns the maximum value for a measurement, that is: 2^resolution-1 +* +*/ +uint32_t ADC_Module::getMaxValue() { + return analog_max_val; +} + + +// Sets the conversion speed +/* +* \param speed can be ADC_VERY_LOW_SPEED, ADC_LOW_SPEED, ADC_MED_SPEED, ADC_HIGH_SPEED_16BITS, ADC_HIGH_SPEED or ADC_VERY_HIGH_SPEED + ADC_VERY_LOW_SPEED is guaranteed to be the lowest possible speed within specs for resolutions less than 16 bits (higher than 1 MHz), + it's different from ADC_LOW_SPEED only for 24, 4 or 2 MHz. + ADC_LOW_SPEED is guaranteed to be the lowest possible speed within specs for all resolutions (higher than 2 MHz). + ADC_MED_SPEED is always >= ADC_LOW_SPEED and <= ADC_HIGH_SPEED. + ADC_HIGH_SPEED_16BITS is guaranteed to be the highest possible speed within specs for all resolutions (lower or eq than 12 MHz). + ADC_HIGH_SPEED is guaranteed to be the highest possible speed within specs for resolutions less than 16 bits (lower or eq than 18 MHz). + ADC_VERY_HIGH_SPEED may be out of specs, it's different from ADC_HIGH_SPEED only for 48, 40 or 24 MHz. +* It doesn't recalibrate at the end. +*/ +void ADC_Module::setConversionSpeed(uint8_t speed) { + + if(speed==conversion_speed) { // no change + return; + } + + if (calibrating) wait_for_cal(); + + // internal asynchronous clock settings: fADK = 2.4, 4.0, 5.2 or 6.2 MHz + if(speed >= ADC_ADACK_2_4) { + setBit(ADC_CFG2, ADC_CFG2_ADACKEN_BIT); // enable ADACK (takes max 5us to be ready) + setBit(ADC_CFG1, ADC_CFG1_ADICLK1_BIT); // select ADACK as clock source + setBit(ADC_CFG1, ADC_CFG1_ADICLK0_BIT); + + clearBit(ADC_CFG1, ADC_CFG1_ADIV0_BIT); // select divider 1 + clearBit(ADC_CFG1, ADC_CFG1_ADIV1_BIT); // we could divide this clk, but it would be too small for ADC use. + + if(speed == ADC_ADACK_2_4) { + clearBit(ADC_CFG2, ADC_CFG2_ADHSC_BIT); + setBit(ADC_CFG1, ADC_CFG1_ADLPC_BIT); + } else if(speed == ADC_ADACK_4_0) { + setBit(ADC_CFG2, ADC_CFG2_ADHSC_BIT); + setBit(ADC_CFG1, ADC_CFG1_ADLPC_BIT); + } else if(speed == ADC_ADACK_5_2) { + clearBit(ADC_CFG2, ADC_CFG2_ADHSC_BIT); + clearBit(ADC_CFG1, ADC_CFG1_ADLPC_BIT); + } else if(speed == ADC_ADACK_6_2) { + setBit(ADC_CFG2, ADC_CFG2_ADHSC_BIT); + clearBit(ADC_CFG1, ADC_CFG1_ADLPC_BIT); + } + conversion_speed = speed; + return; + } + + + // normal bus clock used + + // *ADC_CFG2_adacken = 0; // disable the internal asynchronous clock + clearBit(ADC_CFG2, ADC_CFG2_ADACKEN_BIT); + + uint32_t ADC_CFG1_speed; // store the clock and divisor + + if(speed == ADC_VERY_LOW_SPEED) { + // *ADC_CFG2_adhsc = 0; // no high-speed config + // *ADC_CFG1_adlpc = 1; // use low power conf. + clearBit(ADC_CFG2, ADC_CFG2_ADHSC_BIT); + setBit(ADC_CFG1, ADC_CFG1_ADLPC_BIT); + + ADC_CFG1_speed = ADC_CFG1_VERY_LOW_SPEED; + + } else if(speed == ADC_LOW_SPEED) { + // *ADC_CFG2_adhsc = 0; // no high-speed config + // *ADC_CFG1_adlpc = 1; // use low power conf. + clearBit(ADC_CFG2, ADC_CFG2_ADHSC_BIT); + setBit(ADC_CFG1, ADC_CFG1_ADLPC_BIT); + + ADC_CFG1_speed = ADC_CFG1_LOW_SPEED; + + } else if(speed == ADC_MED_SPEED) { + // *ADC_CFG2_adhsc = 0; // no high-speed config + // *ADC_CFG1_adlpc = 0; // no low power conf. + clearBit(ADC_CFG2, ADC_CFG2_ADHSC_BIT); + clearBit(ADC_CFG1, ADC_CFG1_ADLPC_BIT); + + ADC_CFG1_speed = ADC_CFG1_MED_SPEED; + + } else if(speed == ADC_HIGH_SPEED_16BITS) { + // *ADC_CFG2_adhsc = 1; // high-speed config: add 2 ADCK + // *ADC_CFG1_adlpc = 0; // no low power conf. + setBit(ADC_CFG2, ADC_CFG2_ADHSC_BIT); + clearBit(ADC_CFG1, ADC_CFG1_ADLPC_BIT); + + ADC_CFG1_speed = ADC_CFG1_HI_SPEED_16_BITS; + + } else if(speed == ADC_HIGH_SPEED) { + // *ADC_CFG2_adhsc = 1; // high-speed config: add 2 ADCK + // *ADC_CFG1_adlpc = 0; // no low power conf. + setBit(ADC_CFG2, ADC_CFG2_ADHSC_BIT); + clearBit(ADC_CFG1, ADC_CFG1_ADLPC_BIT); + + ADC_CFG1_speed = ADC_CFG1_HI_SPEED; + + } else if(speed == ADC_VERY_HIGH_SPEED) { // this speed is most likely out of specs, so accurancy can be bad + // *ADC_CFG2_adhsc = 1; // high-speed config: add 2 ADCK + // *ADC_CFG1_adlpc = 0; // no low power conf. + setBit(ADC_CFG2, ADC_CFG2_ADHSC_BIT); + clearBit(ADC_CFG1, ADC_CFG1_ADLPC_BIT); + + ADC_CFG1_speed = ADC_CFG1_VERY_HIGH_SPEED; + + } else { + fail_flag |= ADC_ERROR_OTHER; + return; + } + + // clock source is bus or bus/2 + // *ADC_CFG1_adiclk1 = !!(ADC_CFG1_speed & ADC_CFG1_ADICLK_MASK_1); // !!x converts the number x to either 0 or 1. + // *ADC_CFG1_adiclk0 = !!(ADC_CFG1_speed & ADC_CFG1_ADICLK_MASK_0); + changeBit(ADC_CFG1, ADC_CFG1_ADICLK1_BIT, !!(ADC_CFG1_speed & ADC_CFG1_ADICLK_MASK_1)); + changeBit(ADC_CFG1, ADC_CFG1_ADICLK0_BIT, !!(ADC_CFG1_speed & ADC_CFG1_ADICLK_MASK_0)); + + // divisor for the clock source: 1, 2, 4 or 8. + // so total speed can be: bus, bus/2, bus/4, bus/8 or bus/16. + // *ADC_CFG1_adiv1 = !!(ADC_CFG1_speed & ADC_CFG1_ADIV_MASK_1); + // *ADC_CFG1_adiv0 = !!(ADC_CFG1_speed & ADC_CFG1_ADIV_MASK_0); + changeBit(ADC_CFG1, ADC_CFG1_ADIV1_BIT, !!(ADC_CFG1_speed & ADC_CFG1_ADIV_MASK_1)); + changeBit(ADC_CFG1, ADC_CFG1_ADIV0_BIT, !!(ADC_CFG1_speed & ADC_CFG1_ADIV_MASK_0)); + + conversion_speed = speed; + +} + + +// Sets the sampling speed +/* Increase the sampling speed for low impedance sources, decrease it for higher impedance ones. +* \param speed can be ADC_VERY_LOW_SPEED, ADC_LOW_SPEED, ADC_MED_SPEED, ADC_HIGH_SPEED or ADC_VERY_HIGH_SPEED + ADC_VERY_LOW_SPEED is the lowest possible sampling speed (+24 ADCK). + ADC_LOW_SPEED adds +16 ADCK. + ADC_MED_SPEED adds +10 ADCK. + ADC_HIGH_SPEED (or ADC_HIGH_SPEED_16BITS) adds +6 ADCK. + ADC_VERY_HIGH_SPEED is the highest possible sampling speed (0 ADCK added). +* It doesn't recalibrate at the end. +*/ +void ADC_Module::setSamplingSpeed(uint8_t speed) { + + if(speed==sampling_speed) { // no change + return; + } + + if (calibrating) wait_for_cal(); + + // Select between the settings + if(speed == ADC_VERY_LOW_SPEED) { + // *ADC_CFG1_adlsmp = 1; // long sampling time enable + // *ADC_CFG2_adlsts1 = 0; // maximum sampling time (+24 ADCK) + // *ADC_CFG2_adlsts0 = 0; + setBit(ADC_CFG1, ADC_CFG1_ADLSMP_BIT); + clearBit(ADC_CFG2, ADC_CFG2_ADLSTS1_BIT); + clearBit(ADC_CFG2, ADC_CFG2_ADLSTS0_BIT); + + } else if(speed == ADC_LOW_SPEED) { + // *ADC_CFG1_adlsmp = 1; // long sampling time enable + // *ADC_CFG2_adlsts1 = 0;// high sampling time (+16 ADCK) + // *ADC_CFG2_adlsts0 = 1; + setBit(ADC_CFG1, ADC_CFG1_ADLSMP_BIT); + clearBit(ADC_CFG2, ADC_CFG2_ADLSTS1_BIT); + setBit(ADC_CFG2, ADC_CFG2_ADLSTS0_BIT); + + } else if(speed == ADC_MED_SPEED) { + // *ADC_CFG1_adlsmp = 1; // long sampling time enable + // *ADC_CFG2_adlsts1 = 1;// medium sampling time (+10 ADCK) + // *ADC_CFG2_adlsts0 = 0; + setBit(ADC_CFG1, ADC_CFG1_ADLSMP_BIT); + setBit(ADC_CFG2, ADC_CFG2_ADLSTS1_BIT); + clearBit(ADC_CFG2, ADC_CFG2_ADLSTS0_BIT); + + } else if( (speed == ADC_HIGH_SPEED) || (speed == ADC_HIGH_SPEED_16BITS) ) { + // *ADC_CFG1_adlsmp = 1; // long sampling time enable + // *ADC_CFG2_adlsts1 = 1;// low sampling time (+6 ADCK) + // *ADC_CFG2_adlsts0 = 1; + setBit(ADC_CFG1, ADC_CFG1_ADLSMP_BIT); + setBit(ADC_CFG2, ADC_CFG2_ADLSTS1_BIT); + setBit(ADC_CFG2, ADC_CFG2_ADLSTS0_BIT); + + } else if(speed == ADC_VERY_HIGH_SPEED) { + // *ADC_CFG1_adlsmp = 0; // shortest sampling time + clearBit(ADC_CFG1, ADC_CFG1_ADLSMP_BIT); + + } else { // incorrect speeds have no effect. + return; + } + + sampling_speed = speed; + +} + + +/* Set the number of averages: 0, 4, 8, 16 or 32. +* +*/ +void ADC_Module::setAveraging(uint8_t num) { + + if (calibrating) wait_for_cal(); + + if (num <= 1) { + num = 0; + // *ADC_SC3_avge = 0; + clearBit(ADC_SC3, ADC_SC3_AVGE_BIT); + } else { + // *ADC_SC3_avge = 1; + setBit(ADC_SC3, ADC_SC3_AVGE_BIT); + if (num <= 4) { + num = 4; + // *ADC_SC3_avgs0 = 0; + // *ADC_SC3_avgs1 = 0; + clearBit(ADC_SC3, ADC_SC3_AVGS0_BIT); + clearBit(ADC_SC3, ADC_SC3_AVGS1_BIT); + } else if (num <= 8) { + num = 8; + // *ADC_SC3_avgs0 = 1; + // *ADC_SC3_avgs1 = 0; + setBit(ADC_SC3, ADC_SC3_AVGS0_BIT); + clearBit(ADC_SC3, ADC_SC3_AVGS1_BIT); + } else if (num <= 16) { + num = 16; + // *ADC_SC3_avgs0 = 0; + // *ADC_SC3_avgs1 = 1; + clearBit(ADC_SC3, ADC_SC3_AVGS0_BIT); + setBit(ADC_SC3, ADC_SC3_AVGS1_BIT); + } else { + num = 32; + // *ADC_SC3_avgs0 = 1; + // *ADC_SC3_avgs1 = 1; + setBit(ADC_SC3, ADC_SC3_AVGS0_BIT); + setBit(ADC_SC3, ADC_SC3_AVGS1_BIT); + } + } + analog_num_average = num; +} + + +/* Enable interrupts: An ADC Interrupt will be raised when the conversion is completed +* (including hardware averages and if the comparison (if any) is true). +*/ +void ADC_Module::enableInterrupts() { + + if (calibrating) wait_for_cal(); + + var_enableInterrupts = 1; + // *ADC_SC1A_aien = 1; + setBit(ADC_SC1A, ADC_SC1A_AIEN_BIT); + + NVIC_ENABLE_IRQ(IRQ_ADC); +} + +/* Disable interrupts +* +*/ +void ADC_Module::disableInterrupts() { + + var_enableInterrupts = 0; + // *ADC_SC1A_aien = 0; + clearBit(ADC_SC1A, ADC_SC1A_AIEN_BIT); + + NVIC_DISABLE_IRQ(IRQ_ADC); + +} + + +/* Enable DMA request: An ADC DMA request will be raised when the conversion is completed +* (including hardware averages and if the comparison (if any) is true). +*/ +void ADC_Module::enableDMA() { + + if (calibrating) wait_for_cal(); + + // *ADC_SC2_dma = 1; + setBit(ADC_SC2, ADC_SC2_DMAEN_BIT); +} + +/* Disable ADC DMA request +* +*/ +void ADC_Module::disableDMA() { + + // *ADC_SC2_dma = 0; + clearBit(ADC_SC2, ADC_SC2_DMAEN_BIT); +} + + +/* Enable the compare function: A conversion will be completed only when the ADC value +* is >= compValue (greaterThan=1) or < compValue (greaterThan=0) +* Call it after changing the resolution +* Use with interrupts or poll conversion completion with isADC_Complete() +*/ +void ADC_Module::enableCompare(int16_t compValue, bool greaterThan) { + + if (calibrating) wait_for_cal(); // if we modify the adc's registers when calibrating, it will fail + + // *ADC_SC2_cfe = 1; // enable compare + // *ADC_SC2_cfgt = (int32_t)greaterThan; // greater or less than? + setBit(ADC_SC2, ADC_SC2_ACFE_BIT); + changeBit(ADC_SC2, ADC_SC2_ACFGT_BIT, greaterThan); + + *ADC_CV1 = (int16_t)compValue; // comp value +} + +/* Enable the compare function: A conversion will be completed only when the ADC value +* is inside (insideRange=1) or outside (=0) the range given by (lowerLimit, upperLimit), +* including (inclusive=1) the limits or not (inclusive=0). +* See Table 31-78, p. 617 of the freescale manual. +* Call it after changing the resolution +*/ +void ADC_Module::enableCompareRange(int16_t lowerLimit, int16_t upperLimit, bool insideRange, bool inclusive) { + + if (calibrating) wait_for_cal(); // if we modify the adc's registers when calibrating, it will fail + + // *ADC_SC2_cfe = 1; // enable compare + // *ADC_SC2_cren = 1; // enable compare range + setBit(ADC_SC2, ADC_SC2_ACFE_BIT); + setBit(ADC_SC2, ADC_SC2_ACREN_BIT); + + if(insideRange && inclusive) { // True if value is inside the range, including the limits. CV1 <= CV2 and ACFGT=1 + // *ADC_SC2_cfgt = 1; + setBit(ADC_SC2, ADC_SC2_ACFGT_BIT); + + *ADC_CV1 = (int16_t)lowerLimit; + *ADC_CV2 = (int16_t)upperLimit; + } else if(insideRange && !inclusive) {// True if value is inside the range, excluding the limits. CV1 > CV2 and ACFGT=0 + // *ADC_SC2_cfgt = 0; + clearBit(ADC_SC2, ADC_SC2_ACFGT_BIT); + + *ADC_CV2 = (int16_t)lowerLimit; + *ADC_CV1 = (int16_t)upperLimit; + } else if(!insideRange && inclusive) { // True if value is outside of range or is equal to either limit. CV1 > CV2 and ACFGT=1 + // *ADC_SC2_cfgt = 1; + setBit(ADC_SC2, ADC_SC2_ACFGT_BIT); + + *ADC_CV2 = (int16_t)lowerLimit; + *ADC_CV1 = (int16_t)upperLimit; + } else if(!insideRange && !inclusive) { // True if value is outside of range and not equal to either limit. CV1 > CV2 and ACFGT=0 + // *ADC_SC2_cfgt = 0; + clearBit(ADC_SC2, ADC_SC2_ACFGT_BIT); + + *ADC_CV1 = (int16_t)lowerLimit; + *ADC_CV2 = (int16_t)upperLimit; + } +} + +/* Disable the compare function +* +*/ +void ADC_Module::disableCompare() { + + // *ADC_SC2_cfe = 0; + clearBit(ADC_SC2, ADC_SC2_ACFE_BIT); +} + +/* Enables the PGA and sets the gain +* Use only for signals lower than 1.2 V +* \param gain can be 1, 2, 4, 8, 16 32 or 64 +* +*/ +void ADC_Module::enablePGA(uint8_t gain) { +#if ADC_USE_PGA + + if (calibrating) wait_for_cal(); + + uint8_t setting; + if(gain <= 1) { + setting = 0; + } else if(gain<=2) { + setting = 1; + } else if(gain<=4) { + setting = 2; + } else if(gain<=8) { + setting = 3; + } else if(gain<=16) { + setting = 4; + } else if(gain<=32) { + setting = 5; + } else { // 64 + setting = 6; + } + + *ADC_PGA = ADC_PGA_PGAEN | ADC_PGA_PGAG(setting); + pga_value=1<ADC_MAX_PIN) { + return false; // all others are invalid + } + + // translate pin number to SC1A number, that also contains MUX a or b info. + const uint8_t sc1a_pin = channel2sc1a[pin]; + + // check for valid pin + if( (sc1a_pin&ADC_SC1A_CHANNELS) == ADC_SC1A_PIN_INVALID ) { + return false; // all others are invalid + } + + return true; +} + +// check whether the pins are a valid analog differential pins (including PGA if enabled) +bool ADC_Module::checkDifferentialPins(uint8_t pinP, uint8_t pinN) { + if(pinP>ADC_MAX_PIN) { + return false; // all others are invalid + } + + // translate pinP number to SC1A number, to make sure it's differential + uint8_t sc1a_pin = channel2sc1a[pinP]; + + if( !(sc1a_pin&ADC_SC1A_PIN_DIFF) ) { + return false; // all others are invalid + } + + // get SC1A number, also whether it can do PGA + sc1a_pin = getDifferentialPair(pinP); + + // the pair can't be measured with this ADC + if( (sc1a_pin&ADC_SC1A_CHANNELS) == ADC_SC1A_PIN_INVALID ) { + fail_flag |= ADC_ERROR_WRONG_PIN; + return false; // all others are invalid + } + + #if ADC_USE_PGA + // check if PGA is enabled, and whether the pin has access to it in this ADC module + if( isPGAEnabled() && !(sc1a_pin&ADC_SC1A_PIN_PGA) ) { + return false; + } + #endif // ADC_USE_PGA + + return true; +} + + +//////////////// HELPER METHODS FOR CONVERSION ///////////////// + +// Starts a single-ended conversion on the pin (sets the mux correctly) +// Doesn't do any of the checks on the pin +// It doesn't change the continuous conversion bit +void ADC_Module::startReadFast(uint8_t pin) { + + // translate pin number to SC1A number, that also contains MUX a or b info. + const uint8_t sc1a_pin = channel2sc1a[pin]; + + if(sc1a_pin&ADC_SC1A_PIN_MUX) { // mux a + clearBit(ADC_CFG2, ADC_CFG2_MUXSEL_BIT); + } else { // mux b + setBit(ADC_CFG2, ADC_CFG2_MUXSEL_BIT); + } + + // select pin for single-ended mode and start conversion, enable interrupts if requested + __disable_irq(); + *ADC_SC1A = (sc1a_pin&ADC_SC1A_CHANNELS) + var_enableInterrupts*ADC_SC1_AIEN; + __enable_irq(); + +} + +// Starts a differential conversion on the pair of pins +// Doesn't do any of the checks on the pins +// It doesn't change the continuous conversion bit +void ADC_Module::startDifferentialFast(uint8_t pinP, uint8_t pinN) { + + // get SC1A number + uint8_t sc1a_pin = getDifferentialPair(pinP); + + #if ADC_USE_PGA + // check if PGA is enabled + if(isPGAEnabled()) { + sc1a_pin = 0x2; // PGA always uses DAD2 + } + #endif // ADC_USE_PGA + + __disable_irq(); + *ADC_SC1A = ADC_SC1_DIFF + (sc1a_pin&ADC_SC1A_CHANNELS) + var_enableInterrupts*ADC_SC1_AIEN; + __enable_irq(); + +} + + + +//////////////// BLOCKING CONVERSION METHODS ////////////////// +/* + This methods are implemented like this: + + 1. Check that the pin is correct + 2. if calibrating, wait for it to finish before modifiying any ADC register + 3. Check if we're interrupting a measurement, if so store the settings. + 4. Disable continuous conversion mode and start the current measurement + 5. Wait until it's done, and check whether the comparison (if any) was succesful. + 6. Get the result. + 7. If step 3. is true, restore the previous ADC settings + +*/ + + +/* Reads the analog value of the pin. +* It waits until the value is read and then returns the result. +* If a comparison has been set up and fails, it will return ADC_ERROR_VALUE. +* Set the resolution, number of averages and voltage reference using the appropriate functions. +*/ +int ADC_Module::analogRead(uint8_t pin) { + + //digitalWriteFast(LED_BUILTIN, HIGH); + + // check whether the pin is correct + if(!checkPin(pin)) { + fail_flag |= ADC_ERROR_WRONG_PIN; + return ADC_ERROR_VALUE; + } + + // increase the counter of measurements + num_measurements++; + + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN)); + + if (calibrating) wait_for_cal(); + + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN)); + + // check if we are interrupting a measurement, store setting if so. + // vars to save the current state of the ADC in case it's in use + ADC_Config old_config = {0}; + uint8_t wasADCInUse = isConverting(); // is the ADC running now? + + if(wasADCInUse) { // this means we're interrupting a conversion + // save the current conversion config, we don't want any other interrupts messing up the configs + __disable_irq(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + saveConfig(&old_config); + __enable_irq(); + } + + + // no continuous mode + singleMode(); + + startReadFast(pin); // start single read + + // wait for the ADC to finish + while(isConverting()) { + yield(); + } + + // it's done, check if the comparison (if any) was true + int32_t result; + __disable_irq(); // make sure nothing interrupts this part + if (isComplete()) { // conversion succeded + result = (uint16_t)*ADC_RA; + } else { // comparison was false + fail_flag |= ADC_ERROR_COMPARISON; + result = ADC_ERROR_VALUE; + } + __enable_irq(); + + // if we interrupted a conversion, set it again + if (wasADCInUse) { + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + __disable_irq(); + loadConfig(&old_config); + __enable_irq(); + } + + num_measurements--; + return result; + +} // analogRead + + + +/* Reads the differential analog value of two pins (pinP - pinN) +* It waits until the value is read and then returns the result +* If a comparison has been set up and fails, it will return ADC_ERROR_DIFF_VALUE +* Set the resolution, number of averages and voltage reference using the appropriate functions +*/ +int ADC_Module::analogReadDifferential(uint8_t pinP, uint8_t pinN) { + + if(!checkDifferentialPins(pinP, pinN)) { + fail_flag |= ADC_ERROR_WRONG_PIN; + return ADC_ERROR_VALUE; // all others are invalid + } + + // increase the counter of measurements + num_measurements++; + + // check for calibration before setting channels, + // because conversion will start as soon as we write to *ADC_SC1A + if (calibrating) wait_for_cal(); + + uint8_t res = getResolution(); + + // vars to saved the current state of the ADC in case it's in use + ADC_Config old_config = {0}; + uint8_t wasADCInUse = isConverting(); // is the ADC running now? + + if(wasADCInUse) { // this means we're interrupting a conversion + // save the current conversion config, we don't want any other interrupts messing up the configs + __disable_irq(); + saveConfig(&old_config); + __enable_irq(); + } + + // no continuous mode + singleMode(); + + startDifferentialFast(pinP, pinN); // start conversion + + // wait for the ADC to finish + while( isConverting() ) { + yield(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + } + + // it's done, check if the comparison (if any) was true + int32_t result; + __disable_irq(); // make sure nothing interrupts this part + if (isComplete()) { // conversion succeded + result = (int16_t)(int32_t)(*ADC_RA); // cast to 32 bits + if(res==16) { // 16 bit differential is actually 15 bit + 1 bit sign + result *= 2; // multiply by 2 as if it were really 16 bits, so that getMaxValue gives a correct value. + } + } else { // comparison was false + result = ADC_ERROR_VALUE; + fail_flag |= ADC_ERROR_COMPARISON; + } + __enable_irq(); + + // if we interrupted a conversion, set it again + if (wasADCInUse) { + __disable_irq(); + loadConfig(&old_config); + __enable_irq(); + } + + num_measurements--; + return result; + +} // analogReadDifferential + + + +/////////////// NON-BLOCKING CONVERSION METHODS ////////////// +/* + This methods are implemented like this: + + 1. Check that the pin is correct + 2. if calibrating, wait for it to finish before modifiying any ADC register + 3. Check if we're interrupting a measurement, if so store the settings (in a member of the class, so it can be accessed). + 4. Disable continuous conversion mode and start the current measurement + + The fast methods only do step 4. + +*/ + + +/* Starts an analog measurement on the pin. +* It returns inmediately, read value with readSingle(). +* If the pin is incorrect it returns ADC_ERROR_VALUE. +*/ +bool ADC_Module::startSingleRead(uint8_t pin) { + + // check whether the pin is correct + if(!checkPin(pin)) { + fail_flag |= ADC_ERROR_WRONG_PIN; + return false; + } + + if (calibrating) wait_for_cal(); + + // save the current state of the ADC in case it's in use + adcWasInUse = isConverting(); // is the ADC running now? + + if(adcWasInUse) { // this means we're interrupting a conversion + // save the current conversion config, the adc isr will restore the adc + __disable_irq(); + saveConfig(&adc_config); + __enable_irq(); + } + + // no continuous mode + singleMode(); + + // start measurement + startReadFast(pin); + + return true; +} + + +/* Start a differential conversion between two pins (pinP - pinN). +* It returns inmediately, get value with readSingle(). +* Incorrect pins will return ADC_ERROR_DIFF_VALUE. +* Set the resolution, number of averages and voltage reference using the appropriate functions +*/ +bool ADC_Module::startSingleDifferential(uint8_t pinP, uint8_t pinN) { + + if(!checkDifferentialPins(pinP, pinN)) { + fail_flag |= ADC_ERROR_WRONG_PIN; + return false; // all others are invalid + } + + // check for calibration before setting channels, + // because conversion will start as soon as we write to *ADC_SC1A + if (calibrating) wait_for_cal(); + + // vars to saved the current state of the ADC in case it's in use + adcWasInUse = isConverting(); // is the ADC running now? + + if(adcWasInUse) { // this means we're interrupting a conversion + // save the current conversion config, we don't want any other interrupts messing up the configs + __disable_irq(); + saveConfig(&adc_config); + __enable_irq(); + } + + // no continuous mode + singleMode(); + + // start the conversion + startDifferentialFast(pinP, pinN); + + return true; +} + + + +///////////// CONTINUOUS CONVERSION METHODS //////////// +/* + This methods are implemented like this: + + 1. Check that the pin is correct + 2. If calibrating, wait for it to finish before modifiying any ADC register + 4. Enable continuous conversion mode and start the current measurement + +*/ + +/* Starts continuous conversion on the pin + * It returns as soon as the ADC is set, use analogReadContinuous() to read the values + * Set the resolution, number of averages and voltage reference using the appropriate functions BEFORE calling this function +*/ +bool ADC_Module::startContinuous(uint8_t pin) { + + // check whether the pin is correct + if(!checkPin(pin)) { + fail_flag |= ADC_ERROR_WRONG_PIN; + return false; + } + + // check for calibration before setting channels, + if (calibrating) wait_for_cal(); + + // increase the counter of measurements + num_measurements++; + + // set continuous conversion flag + continuousMode(); + + startReadFast(pin); + + return true; +} + + +/* Starts continuous and differential conversion between the pins (pinP-pinN) + * It returns as soon as the ADC is set, use analogReadContinuous() to read the value + * Set the resolution, number of averages and voltage reference using the appropriate functions BEFORE calling this function +*/ +bool ADC_Module::startContinuousDifferential(uint8_t pinP, uint8_t pinN) { + + if(!checkDifferentialPins(pinP, pinN)) { + fail_flag |= ADC_ERROR_WRONG_PIN; + return false; // all others are invalid + } + + // increase the counter of measurements + num_measurements++; + + // check for calibration before setting channels, + // because conversion will start as soon as we write to *ADC_SC1A + if (calibrating) wait_for_cal(); + + // save the current state of the ADC in case it's in use + uint8_t wasADCInUse = isConverting(); // is the ADC running now? + + if(wasADCInUse) { // this means we're interrupting a conversion + // save the current conversion config, we don't want any other interrupts messing up the configs + __disable_irq(); + saveConfig(&adc_config); + __enable_irq(); + } + + // set continuous mode + continuousMode(); + + // start conversions + startDifferentialFast(pinP, pinN); + + return true; +} + + +/* Stops continuous conversion +*/ +void ADC_Module::stopContinuous() { + + // set channel select to all 1's (31) to stop it. + *ADC_SC1A = 0x1F + var_enableInterrupts*ADC_SC1_AIEN;; + + // decrease the counter of measurements (unless it's 0) + if(!num_measurements) { + num_measurements--; + } + + + return; +} + +//////////// PDB //////////////// +//// Only works for Teensy 3.0 and 3.1, not LC (it doesn't have PDB) + +#if ADC_USE_PDB + +// frequency in Hz +void ADC_Module::startPDB(uint32_t freq) { + + if (!(SIM_SCGC6 & SIM_SCGC6_PDB)) { // setup PDB + SIM_SCGC6 |= SIM_SCGC6_PDB; // enable pdb clock + } + + if(freq>F_BUS) return; // too high + if(freq<1) return; // too low + + // mod will have to be a 16 bit value + // we detect if it's higher than 0xFFFF and scale it back acordingly. + uint32_t mod = (F_BUS / freq); + + uint8_t prescaler = 0; // from 0 to 7: factor of 1, 2, 4, 8, 16, 32, 64 or 128 + uint8_t mult = 0; // from 0 to 3, factor of 1, 10, 20 or 40 + + // if mod is too high we need to use prescaler and mult to bring it down to a 16 bit number + const uint32_t min_level = 0xFFFF; + if(mod>min_level) { + if( mod < 2*min_level ) { + prescaler = 1; + } + else if( mod < 4*min_level ) { + prescaler = 2; + } + else if( mod < 8*min_level ) { + prescaler = 3; + } + else if( mod < 10*min_level ) { + mult = 1; + } + else if( mod < 16*min_level ) { + prescaler = 4; + } + else if( mod < 20*min_level ) { + mult = 2; + } + else if( mod < 32*min_level ) { + prescaler = 5; + } + else if( mod < 40*min_level ) { + mult = 3; + } + else if( mod < 64*min_level ) { + prescaler = 6; + } + else if( mod < 128*min_level ) { + prescaler = 7; + } + else if( mod < 160*min_level ) { // 16*10 + prescaler = 4; + mult = 1; + } + else if( mod < 320*min_level ) { // 16*20 + prescaler = 4; + mult = 2; + } + else if( mod < 640*min_level ) { // 16*40 + prescaler = 4; + mult = 3; + } + else if( mod < 1280*min_level ) { // 32*40 + prescaler = 5; + mult = 3; + } + else if( mod < 2560*min_level ) { // 64*40 + prescaler = 6; + mult = 3; + } + else if( mod < 5120*min_level ) { // 128*40 + prescaler = 7; + mult = 3; + } + else { // frequency too low + return; + } + + mod >>= prescaler; + if(mult>0) { + mod /= 10; + mod >>= (mult-1); + } + } + + setHardwareTrigger(); // trigger ADC with hardware + + PDB0_IDLY = 1; // the pdb interrupt happens when IDLY is equal to CNT+1 + + PDB0_MOD = (uint16_t)(mod-1); + + PDB0_SC = PDB_CONFIG | PDB_SC_PRESCALER(prescaler) | PDB_SC_MULT(mult) | PDB_SC_LDOK; // load all new values + + PDB0_SC = PDB_CONFIG | PDB_SC_PRESCALER(prescaler) | PDB_SC_MULT(mult) | PDB_SC_SWTRIG; // start the counter! + + *PDB0_CHnC1 = PDB_CHnC1_TOS_1 | PDB_CHnC1_EN_1; // enable pretrigger 0 (SC1A) + + NVIC_ENABLE_IRQ(IRQ_PDB); + +} + +void ADC_Module::stopPDB() { + if (!(SIM_SCGC6 & SIM_SCGC6_PDB)) { // if PDB clock wasn't on, return + setSoftwareTrigger(); + return; + } + PDB0_SC = 0; + setSoftwareTrigger(); + + NVIC_DISABLE_IRQ(IRQ_PDB); +} + +#endif diff --git a/Firmware_V2/src/libraries/ADC/ADC_Module.h b/Firmware_V2/src/libraries/ADC/ADC_Module.h new file mode 100644 index 0000000..2d89dec --- /dev/null +++ b/Firmware_V2/src/libraries/ADC/ADC_Module.h @@ -0,0 +1,1000 @@ +/* Teensy 3.x, LC ADC library + * https://github.com/pedvide/ADC + * Copyright (c) 2015 Pedro Villanueva + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* ADC_Module.h: Declarations of the fuctions of a Teensy 3.x, LC ADC module + * + */ + + +#ifndef ADC_MODULE_H +#define ADC_MODULE_H + +#include + +// Easier names for the boards +#if defined(__MK20DX256__) // Teensy 3.1 +#define ADC_TEENSY_3_1 +#elif defined(__MK20DX128__) // Teensy 3.0 +#define ADC_TEENSY_3_0 +#elif defined(__MKL26Z64__) // Teensy LC +#define ADC_TEENSY_LC +#elif defined(__MK64FX512__) // Teensy 3.5 +#define ADC_TEENSY_3_5 +#elif defined(__MK66FX1M0__) // Teensy 3.6 +#define ADC_TEENSY_3_6 +#else +#error "Board not supported!" +#endif + + + +// Teensy 3.1 has 2 ADCs, Teensy 3.0 and LC only 1. +#if defined(ADC_TEENSY_3_1) // Teensy 3.1 + #define ADC_NUM_ADCS 2 +#elif defined(ADC_TEENSY_3_0) // Teensy 3.0 + #define ADC_NUM_ADCS 1 +#elif defined(ADC_TEENSY_LC) // Teensy LC + #define ADC_NUM_ADCS 1 +#elif defined(ADC_TEENSY_3_5) // Teensy 3.5 + #define ADC_NUM_ADCS 2 +#elif defined(ADC_TEENSY_3_6) // Teensy 3.6 + #define ADC_NUM_ADCS 2 +#endif + +// Use DMA? +#if defined(ADC_TEENSY_3_1) // Teensy 3.1 + #define ADC_USE_DMA 1 +#elif defined(ADC_TEENSY_3_0) // Teensy 3.0 + #define ADC_USE_DMA 1 +#elif defined(ADC_TEENSY_LC) // Teensy LC + #define ADC_USE_DMA 1 +#elif defined(ADC_TEENSY_3_5) // Teensy 3.5 + #define ADC_USE_DMA 1 +#elif defined(ADC_TEENSY_3_6) // Teensy 3.6 + #define ADC_USE_DMA 1 +#endif + +// Use PGA? +#if defined(ADC_TEENSY_3_1) // Teensy 3.1 + #define ADC_USE_PGA 1 +#elif defined(ADC_TEENSY_3_0) // Teensy 3.0 + #define ADC_USE_PGA 0 +#elif defined(ADC_TEENSY_LC) // Teensy LC + #define ADC_USE_PGA 0 +#elif defined(ADC_TEENSY_3_5) // Teensy 3.5 + #define ADC_USE_PGA 0 +#elif defined(ADC_TEENSY_3_6) // Teensy 3.6 + #define ADC_USE_PGA 0 +#endif + +// Use PDB? +#if defined(ADC_TEENSY_3_1) // Teensy 3.1 + #define ADC_USE_PDB 1 +#elif defined(ADC_TEENSY_3_0) // Teensy 3.0 + #define ADC_USE_PDB 1 +#elif defined(ADC_TEENSY_LC) // Teensy LC + #define ADC_USE_PDB 0 +#elif defined(ADC_TEENSY_3_5) // Teensy 3.5 + #define ADC_USE_PDB 1 +#elif defined(ADC_TEENSY_3_6) // Teensy 3.6 + #define ADC_USE_PDB 1 +#endif + +// Has internal reference? +#if defined(ADC_TEENSY_3_1) // Teensy 3.1 + #define ADC_USE_INTERNAL 1 +#elif defined(ADC_TEENSY_3_0) // Teensy 3.0 + #define ADC_USE_INTERNAL 1 +#elif defined(ADC_TEENSY_LC) // Teensy LC + #define ADC_USE_INTERNAL 0 +#elif defined(ADC_TEENSY_3_5) // Teensy 3.5 + #define ADC_USE_INTERNAL 1 +#elif defined(ADC_TEENSY_3_6) // Teensy 3.6 + #define ADC_USE_INTERNAL 1 +#endif + +// Select the voltage reference sources for ADC. +#define ADC_REF_DEFAULT 0 +#define ADC_REF_ALT 1 +#if defined(ADC_TEENSY_3_0) || defined(ADC_TEENSY_3_1) || defined(ADC_TEENSY_3_5) || defined(ADC_TEENSY_3_6) +// default is the external, that is connected to the 3.3V supply. +// To use the external simply connect AREF to a different voltage +// alt is connected to the 1.2 V ref. +#define ADC_REF_3V3 ADC_REF_DEFAULT +#define ADC_REF_1V2 ADC_REF_ALT +#define ADC_REF_EXT ADC_REF_DEFAULT + +#elif defined(ADC_TEENSY_LC) +// alt is the internal ref, 3.3 V +// the default is AREF +#define ADC_REF_3V3 ADC_REF_ALT +#define ADC_REF_EXT ADC_REF_DEFAULT +#endif + +// max number of pins, size of channel2sc1aADCx +#if defined(ADC_TEENSY_3_1) // Teensy 3.1 + #define ADC_MAX_PIN (40) +#elif defined(ADC_TEENSY_3_0) // Teensy 3.0 + #define ADC_MAX_PIN (37) +#elif defined(ADC_TEENSY_LC) // Teensy LC + #define ADC_MAX_PIN (26) +#elif defined(ADC_TEENSY_3_5) // Teensy 3.5 + #define ADC_MAX_PIN (43) +#elif defined(ADC_TEENSY_3_6) // Teensy 3.6 + #define ADC_MAX_PIN (43) +#endif + + +// number of differential pairs PER ADC!! +#if defined(ADC_TEENSY_3_1) // Teensy 3.1 + #define ADC_DIFF_PAIRS (2) // normal and with PGA +#elif defined(ADC_TEENSY_3_0) // Teensy 3.0 + #define ADC_DIFF_PAIRS (2) +#elif defined(ADC_TEENSY_LC) // Teensy LC + #define ADC_DIFF_PAIRS (1) +#elif defined(ADC_TEENSY_3_5) // Teensy 3.5 + #define ADC_DIFF_PAIRS (1) +#elif defined(ADC_TEENSY_3_6) // Teensy 3.6 + #define ADC_DIFF_PAIRS (1) +#endif + +/* MK20DX256 Datasheet: +The 16-bit accuracy specifications listed in Table 24 and Table 25 are achievable on the +differential pins ADCx_DP0, ADCx_DM0 +All other ADC channels meet the 13-bit differential/12-bit single-ended accuracy +specifications. + +The results in this data sheet were derived from a system which has < 8 Ohm analog source resistance. The RAS/CAS +time constant should be kept to < 1ns. + +ADC clock should be 2 to 12 MHz for 16 bit mode +ADC clock should be 1 to 18 MHz for 8-12 bit mode, and 1-24 MHz for Teensy 3.6 (NOT 3.5) +To use the maximum ADC conversion clock frequency, the ADHSC bit must be set and the ADLPC bit must be clear + +The ADHSC bit is used to configure a higher clock input frequency. This will allow +faster overall conversion times. To meet internal ADC timing requirements, the ADHSC +bit adds additional ADCK cycles. Conversions with ADHSC = 1 take two more ADCK +cycles. ADHSC should be used when the ADCLK exceeds the limit for ADHSC = 0. + +*/ +// the alternate clock is connected to OSCERCLK (16 MHz). +// datasheet says ADC clock should be 2 to 12 MHz for 16 bit mode +// datasheet says ADC clock should be 1 to 18 MHz for 8-12 bit mode, and 1-24 MHz for Teensy 3.6 (NOT 3.5) +// calibration works best when averages are 32 and speed is less than 4 MHz +// ADC_CFG1_ADICLK: 0=bus, 1=bus/2, 2=(alternative clk) altclk, 3=(async. clk) adack +// See below for an explanation of VERY_LOW_SPEED, LOW_SPEED, etc. +#if F_BUS == 108000000 + #define ADC_CFG1_3_375MHZ (ADC_CFG1_ADIV(3) + ADC_CFG1_ADICLK(1)) + #define ADC_CFG1_6_75MHZ (ADC_CFG1_ADIV(2) + ADC_CFG1_ADICLK(1)) + #define ADC_CFG1_13_5MHZ (ADC_CFG1_ADIV(1) + ADC_CFG1_ADICLK(1)) + #define ADC_CFG1_27MHZ (ADC_CFG1_ADIV(0) + ADC_CFG1_ADICLK(1)) + + #define ADC_CFG1_VERY_LOW_SPEED ADC_CFG1_LOW_SPEED + #define ADC_CFG1_LOW_SPEED (ADC_CFG1_3_375MHZ) + #define ADC_CFG1_MED_SPEED (ADC_CFG1_6_75MHZ) + #define ADC_CFG1_HI_SPEED_16_BITS (ADC_CFG1_6_75MHZ) + #define ADC_CFG1_HI_SPEED (ADC_CFG1_13_5MHZ) + #define ADC_CFG1_VERY_HIGH_SPEED (ADC_CFG1_27MHZ) +#elif F_BUS == 60000000 + #define ADC_CFG1_3_75MHZ (ADC_CFG1_ADIV(3) + ADC_CFG1_ADICLK(1)) + #define ADC_CFG1_7_5MHZ (ADC_CFG1_ADIV(2) + ADC_CFG1_ADICLK(1)) + #define ADC_CFG1_15MHZ (ADC_CFG1_ADIV(1) + ADC_CFG1_ADICLK(1)) + + #define ADC_CFG1_VERY_LOW_SPEED ADC_CFG1_LOW_SPEED + #define ADC_CFG1_LOW_SPEED (ADC_CFG1_3_75MHZ) + #define ADC_CFG1_MED_SPEED (ADC_CFG1_7_5MHZ) + #define ADC_CFG1_HI_SPEED_16_BITS (ADC_CFG1_7_5MHZ) + #define ADC_CFG1_HI_SPEED (ADC_CFG1_15MHZ) + #define ADC_CFG1_VERY_HIGH_SPEED ADC_CFG1_HI_SPEED + +#elif F_BUS == 56000000 || F_BUS == 54000000 // frequency numbers are good for 56 MHz and slightly smaller for 54 MHz + #define ADC_CFG1_3_5MHZ (ADC_CFG1_ADIV(3) + ADC_CFG1_ADICLK(1)) + #define ADC_CFG1_7MHZ (ADC_CFG1_ADIV(2) + ADC_CFG1_ADICLK(1)) + #define ADC_CFG1_14MHZ (ADC_CFG1_ADIV(1) + ADC_CFG1_ADICLK(1)) + #define ADC_CFG1_28MHZ (ADC_CFG1_ADIV(0) + ADC_CFG1_ADICLK(1)) + + #define ADC_CFG1_VERY_LOW_SPEED ADC_CFG1_LOW_SPEED + #define ADC_CFG1_LOW_SPEED (ADC_CFG1_3_5MHZ) + #define ADC_CFG1_MED_SPEED (ADC_CFG1_7MHZ) + #define ADC_CFG1_HI_SPEED_16_BITS (ADC_CFG1_7MHZ) + #define ADC_CFG1_HI_SPEED (ADC_CFG1_14MHZ) + #define ADC_CFG1_VERY_HIGH_SPEED (ADC_CFG1_28MHZ) + +#elif F_BUS == 48000000 + #define ADC_CFG1_3MHZ (ADC_CFG1_ADIV(3) + ADC_CFG1_ADICLK(1)) // Clock divide select: 3=div8 + Input clock: 1=bus/2 + #define ADC_CFG1_6MHZ (ADC_CFG1_ADIV(2) + ADC_CFG1_ADICLK(1)) // Clock divide select: 2=div4 + Input clock: 1=bus/2 + #define ADC_CFG1_12MHZ (ADC_CFG1_ADIV(1) + ADC_CFG1_ADICLK(1)) // Clock divide select: 1=div2 Input clock: 1=bus/2 + #define ADC_CFG1_24MHZ (ADC_CFG1_ADIV(0) + ADC_CFG1_ADICLK(1)) // this is way too fast, so accuracy is not guaranteed, except for T3.6 + + #define ADC_CFG1_VERY_LOW_SPEED ADC_CFG1_LOW_SPEED + #define ADC_CFG1_LOW_SPEED (ADC_CFG1_3MHZ) + #define ADC_CFG1_MED_SPEED (ADC_CFG1_6MHZ) + #define ADC_CFG1_HI_SPEED_16_BITS (ADC_CFG1_12MHZ) + #define ADC_CFG1_HI_SPEED (ADC_CFG1_12MHZ) + #define ADC_CFG1_VERY_HIGH_SPEED (ADC_CFG1_24MHZ) + +#elif F_BUS == 40000000 + #define ADC_CFG1_2_5MHZ (ADC_CFG1_ADIV(3) + ADC_CFG1_ADICLK(1)) + #define ADC_CFG1_5MHZ (ADC_CFG1_ADIV(2) + ADC_CFG1_ADICLK(1)) + #define ADC_CFG1_10MHZ (ADC_CFG1_ADIV(1) + ADC_CFG1_ADICLK(1)) + #define ADC_CFG1_20MHZ (ADC_CFG1_ADIV(0) + ADC_CFG1_ADICLK(1)) // this is too fast, so accuracy is not guaranteed + + #define ADC_CFG1_VERY_LOW_SPEED ADC_CFG1_LOW_SPEED + #define ADC_CFG1_LOW_SPEED (ADC_CFG1_2_5MHZ) + #define ADC_CFG1_MED_SPEED (ADC_CFG1_5MHZ) + #define ADC_CFG1_HI_SPEED_16_BITS (ADC_CFG1_10MHZ) + #define ADC_CFG1_HI_SPEED (ADC_CFG1_10MHZ) + #define ADC_CFG1_VERY_HIGH_SPEED (ADC_CFG1_20MHZ) + +#elif F_BUS == 36000000 + #define ADC_CFG1_2_25MHZ (ADC_CFG1_ADIV(3) + ADC_CFG1_ADICLK(1)) + #define ADC_CFG1_4_5MHZ (ADC_CFG1_ADIV(2) + ADC_CFG1_ADICLK(1)) + #define ADC_CFG1_9MHZ (ADC_CFG1_ADIV(1) + ADC_CFG1_ADICLK(1)) + #define ADC_CFG1_18MHZ (ADC_CFG1_ADIV(0) + ADC_CFG1_ADICLK(1)) + + #define ADC_CFG1_VERY_LOW_SPEED ADC_CFG1_LOW_SPEED + #define ADC_CFG1_LOW_SPEED (ADC_CFG1_2_25MHZ) + #define ADC_CFG1_MED_SPEED (ADC_CFG1_9MHZ) + #define ADC_CFG1_HI_SPEED_16_BITS (ADC_CFG1_9MHZ) + #define ADC_CFG1_HI_SPEED (ADC_CFG1_18MHZ) + #define ADC_CFG1_VERY_HIGH_SPEED ADC_CFG1_HI_SPEED + +#elif F_BUS == 24000000 + #define ADC_CFG1_1_5MHZ (ADC_CFG1_ADIV(3) + ADC_CFG1_ADICLK(1)) // Clock divide select: 3=div8 + Input clock: 1=bus/2 + #define ADC_CFG1_3MHZ (ADC_CFG1_ADIV(3) + ADC_CFG1_ADICLK(0)) // Clock divide select: 3=div8 + Input clock: 0=bus + #define ADC_CFG1_6MHZ (ADC_CFG1_ADIV(2) + ADC_CFG1_ADICLK(0)) // Clock divide select: 2=div4 + Input clock: 0=bus + #define ADC_CFG1_12MHZ (ADC_CFG1_ADIV(1) + ADC_CFG1_ADICLK(0)) // Clock divide select: 1=div2 + Input clock: 0=bus + #define ADC_CFG1_24MHZ (ADC_CFG1_ADIV(0) + ADC_CFG1_ADICLK(0)) // this is way too fast, so accuracy is not guaranteed + + #define ADC_CFG1_VERY_LOW_SPEED (ADC_CFG1_1_5MHZ) + #define ADC_CFG1_LOW_SPEED (ADC_CFG1_3MHZ) + #define ADC_CFG1_MED_SPEED (ADC_CFG1_6MHZ) + #define ADC_CFG1_HI_SPEED_16_BITS (ADC_CFG1_12MHZ) + #define ADC_CFG1_HI_SPEED (ADC_CFG1_12MHZ) + #define ADC_CFG1_VERY_HIGH_SPEED (ADC_CFG1_24MHZ) + +#elif F_BUS == 4000000 + #define ADC_CFG1_1MHZ (ADC_CFG1_ADIV(2) + ADC_CFG1_ADICLK(0)) + #define ADC_CFG1_2MHZ (ADC_CFG1_ADIV(1) + ADC_CFG1_ADICLK(0)) + #define ADC_CFG1_4MHZ (ADC_CFG1_ADIV(0) + ADC_CFG1_ADICLK(0)) + + #define ADC_CFG1_VERY_LOW_SPEED (ADC_CFG1_1MHZ) + #define ADC_CFG1_LOW_SPEED (ADC_CFG1_2MHZ) + #define ADC_CFG1_MED_SPEED (ADC_CFG1_4MHZ) + #define ADC_CFG1_HI_SPEED_16_BITS (ADC_CFG1_4MHZ) + #define ADC_CFG1_HI_SPEED (ADC_CFG1_4MHZ) + #define ADC_CFG1_VERY_HIGH_SPEED ADC_CFG1_HI_SPEED + +#elif F_BUS == 2000000 + #define ADC_CFG1_1MHZ (ADC_CFG1_ADIV(1) + ADC_CFG1_ADICLK(0)) + #define ADC_CFG1_2MHZ (ADC_CFG1_ADIV(0) + ADC_CFG1_ADICLK(0)) + + #define ADC_CFG1_VERY_LOW_SPEED (ADC_CFG1_1MHZ) + #define ADC_CFG1_LOW_SPEED (ADC_CFG1_2MHZ) + #define ADC_CFG1_MED_SPEED (ADC_CFG1_2MHZ) + #define ADC_CFG1_HI_SPEED_16_BITS (ADC_CFG1_2MHZ) + #define ADC_CFG1_HI_SPEED (ADC_CFG1_2MHZ) + #define ADC_CFG1_VERY_HIGH_SPEED ADC_CFG1_HI_SPEED + +#else +#error "F_BUS must be 108, 60, 56, 54, 48, 40, 36, 24, 4 or 2 MHz" +#endif + +// mask the important bit in each register +#define ADC_CFG1_ADICLK_MASK_1 (1<<1) +#define ADC_CFG1_ADICLK_MASK_0 (1<<0) + +#define ADC_CFG1_ADIV_MASK_1 (1<<6) +#define ADC_CFG1_ADIV_MASK_0 (1<<5) + +// Settings for the power/speed of conversions/sampling +/* For conversion speeds: + ADC_VERY_LOW_SPEED is guaranteed to be the lowest possible speed within specs for resolutions less than 16 bits (higher than 1 MHz), + it's different from ADC_LOW_SPEED only for 24, 4 or 2 MHz. + ADC_LOW_SPEED is guaranteed to be the lowest possible speed within specs for all resolutions (higher than 2 MHz). + ADC_MED_SPEED is always >= ADC_LOW_SPEED and <= ADC_HIGH_SPEED. + ADC_HIGH_SPEED_16BITS is guaranteed to be the highest possible speed within specs for all resolutions (lower or eq than 12 MHz). + ADC_HIGH_SPEED is guaranteed to be the highest possible speed within specs for resolutions less than 16 bits (lower or eq than 18 MHz). + ADC_VERY_HIGH_SPEED may be out of specs, it's different from ADC_HIGH_SPEED only for 48, 40 or 24 MHz. +*/ +/* For sampling speeds: + ADC_VERY_LOW_SPEED is the lowest possible sampling speed (+24 ADCK). + ADC_LOW_SPEED adds +16 ADCK. + ADC_MED_SPEED adds +10 ADCK. + ADC_HIGH_SPEED (or ADC_HIGH_SPEED_16BITS) adds +6 ADCK. + ADC_VERY_HIGH_SPEED is the highest possible sampling speed (0 ADCK added). +*/ +#define ADC_VERY_LOW_SPEED 0 +#define ADC_LOW_SPEED 1 +#define ADC_MED_SPEED 2 +#define ADC_HIGH_SPEED_16BITS 3 +#define ADC_HIGH_SPEED 4 +#define ADC_VERY_HIGH_SPEED 5 + +// Alternative asynchronous clock for ADC. +// 2.4, 4.0, 5.2 and 6.2 MHz clock independent on the bus frequency. +#define ADC_ADACK_2_4 16 +#define ADC_ADACK_4_0 17 +#define ADC_ADACK_5_2 18 +#define ADC_ADACK_6_2 19 + + +// Mask for the channel selection in ADCx_SC1A, +// useful if you want to get the channel number from ADCx_SC1A +#define ADC_SC1A_CHANNELS (0x1F) +// 0x1F=31 in the channel2sc1aADCx means the pin doesn't belong to the ADC module +#define ADC_SC1A_PIN_INVALID (0x1F) +// Muxsel mask, pins in channel2sc1aADCx with bit 7 set use mux A. +#define ADC_SC1A_PIN_MUX (0x80) +// Differential pin mask, pins in channel2sc1aADCx with bit 6 set are differential pins. +#define ADC_SC1A_PIN_DIFF (0x40) +// PGA mask. The pins can use PGA on that ADC +#define ADC_SC1A_PIN_PGA (0x80) + + + + +// Error codes for analogRead and analogReadDifferential +#define ADC_ERROR_DIFF_VALUE (-70000) +#define ADC_ERROR_VALUE ADC_ERROR_DIFF_VALUE + +// Error flag masks. +// Possible errors are: other, calibration, wrong pin, analogRead, analogDifferentialRead, continuous, continuousDifferential +// To globalLy disable an error simply change (1<= ADC_LOW_SPEED and <= ADC_HIGH_SPEED. + * ADC_HIGH_SPEED_16BITS is guaranteed to be the highest possible speed within specs for all resolutions (lower or eq than 12 MHz). + * ADC_HIGH_SPEED is guaranteed to be the highest possible speed within specs for resolutions less than 16 bits (lower or eq than 18 MHz). + * ADC_VERY_HIGH_SPEED may be out of specs, it's different from ADC_HIGH_SPEED only for 48, 40 or 24 MHz bus frequency. + * + * Additionally the conversion speed can also be ADC_ADACK_2_4, ADC_ADACK_4_0, ADC_ADACK_5_2 and ADC_ADACK_6_2, + * where the numbers are the frequency of the ADC clock (ADCK) in MHz and are independent on the bus speed. + * This is useful if you are using the Teensy at a very low clock frequency but want faster conversions, + * but if F_BUS= compValue (greaterThan=1) or < compValue (greaterThan=0) + * Call it after changing the resolution + * Use with interrupts or poll conversion completion with isComplete() + */ + void enableCompare(int16_t compValue, bool greaterThan); + + //! Enable the compare function to a range + /** A conversion will be completed only when the ADC value is inside (insideRange=1) or outside (=0) + * the range given by (lowerLimit, upperLimit),including (inclusive=1) the limits or not (inclusive=0). + * See Table 31-78, p. 617 of the freescale manual. + * Call it after changing the resolution + * Use with interrupts or poll conversion completion with isComplete() + */ + void enableCompareRange(int16_t lowerLimit, int16_t upperLimit, bool insideRange, bool inclusive); + + //! Disable the compare function + void disableCompare(); + + + //! Enable and set PGA + /** Enables the PGA and sets the gain + * Use only for signals lower than 1.2 V + * \param gain can be 1, 2, 4, 8, 16, 32 or 64 + * + */ + void enablePGA(uint8_t gain); + + //! Returns the PGA level + /** PGA from 1 to 64 + */ + uint8_t getPGA(); + + //! Disable PGA + void disablePGA(); + + + //! Set continuous conversion mode + void continuousMode() __attribute__((always_inline)) { + setBit(ADC_SC3, ADC_SC3_ADCO_BIT); + } + //! Set single-shot conversion mode + void singleMode() __attribute__((always_inline)) { + clearBit(ADC_SC3, ADC_SC3_ADCO_BIT); + } + + //! Use software to trigger the ADC, this is the most common setting + void setSoftwareTrigger() __attribute__((always_inline)) { + clearBit(ADC_SC2, ADC_SC2_ADTRG_BIT); + } + + //! Use hardware to trigger the ADC + void setHardwareTrigger() __attribute__((always_inline)) { + setBit(ADC_SC2, ADC_SC2_ADTRG_BIT); + } + + + ////////////// INFORMATION ABOUT THE STATE OF THE ADC ///////////////// + + //! Is the ADC converting at the moment? + volatile bool isConverting() __attribute__((always_inline)) { + //return (*ADC_SC2_adact); + return getBit(ADC_SC2, ADC_SC2_ADACT_BIT); + //return ((*ADC_SC2) & ADC_SC2_ADACT) >> 7; + } + + //! Is an ADC conversion ready? + /** + * \return 1 if yes, 0 if not. + * When a value is read this function returns 0 until a new value exists + * So it only makes sense to call it before analogReadContinuous() or readSingle() + */ + volatile bool isComplete() __attribute__((always_inline)) { + //return (*ADC_SC1A_coco); + return getBit(ADC_SC1A, ADC_SC1A_COCO_BIT); + //return ((*ADC_SC1A) & ADC_SC1_COCO) >> 7; + } + + //! Is the ADC in differential mode? + volatile bool isDifferential() __attribute__((always_inline)) { + //return ((*ADC_SC1A) & ADC_SC1_DIFF) >> 5; + return getBit(ADC_SC1A, ADC_SC1_DIFF_BIT); + } + + //! Is the ADC in continuous mode? + volatile bool isContinuous() __attribute__((always_inline)) { + //return (*ADC_SC3_adco); + return getBit(ADC_SC3, ADC_SC3_ADCO_BIT); + //return ((*ADC_SC3) & ADC_SC3_ADCO) >> 3; + } + + //! Is the PGA function enabled? + volatile bool isPGAEnabled() __attribute__((always_inline)) { + return getBit(ADC_PGA, ADC_PGA_PGAEN_BIT); + } + + + //////////////// INFORMATION ABOUT VALID PINS ////////////////// + + //! Check whether the pin is a valid analog pin + bool checkPin(uint8_t pin); + + //! Check whether the pins are a valid analog differential pair of pins + /** If PGA is enabled it also checks that this ADCx can use PGA on this pins + */ + bool checkDifferentialPins(uint8_t pinP, uint8_t pinN); + + + //////////////// HELPER METHODS FOR CONVERSION ///////////////// + + //! Starts a single-ended conversion on the pin + /** It sets the mux correctly, doesn't do any of the checks on the pin and + * doesn't change the continuous conversion bit. + */ + void startReadFast(uint8_t pin); // helper method + + //! Starts a differential conversion on the pair of pins + /** It sets the mux correctly, doesn't do any of the checks on the pin and + * doesn't change the continuous conversion bit. + */ + void startDifferentialFast(uint8_t pinP, uint8_t pinN); + + + //////////////// BLOCKING CONVERSION METHODS ////////////////// + + //! Returns the analog value of the pin. + /** It waits until the value is read and then returns the result. + * If a comparison has been set up and fails, it will return ADC_ERROR_VALUE. + * This function is interrupt safe, so it will restore the adc to the state it was before being called + */ + int analogRead(uint8_t pin); + + + //! Reads the differential analog value of two pins (pinP - pinN). + /** It waits until the value is read and then returns the result. + * If a comparison has been set up and fails, it will return ADC_ERROR_DIFF_VALUE. + * \param pinP must be A10 or A12. + * \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12).. + * Other pins will return ADC_ERROR_DIFF_VALUE. + * This function is interrupt safe, so it will restore the adc to the state it was before being called + */ + int analogReadDifferential(uint8_t pinP, uint8_t pinN); + + + /////////////// NON-BLOCKING CONVERSION METHODS ////////////// + + //! Starts an analog measurement on the pin and enables interrupts. + /** It returns immediately, get value with readSingle(). + * If the pin is incorrect it returns ADC_ERROR_VALUE + * If this function interrupts a measurement, it stores the settings in adc_config + */ + bool startSingleRead(uint8_t pin); + + //! Start a differential conversion between two pins (pinP - pinN) and enables interrupts. + /** It returns immediately, get value with readSingle(). + * \param pinP must be A10 or A12. + * \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12). + * Other pins will return ADC_ERROR_DIFF_VALUE. + * If this function interrupts a measurement, it stores the settings in adc_config + */ + bool startSingleDifferential(uint8_t pinP, uint8_t pinN); + + //! Reads the analog value of a single conversion. + /** Set the conversion with with startSingleRead(pin) or startSingleDifferential(pinP, pinN). + * \return the converted value. + */ + int readSingle() __attribute__((always_inline)) { + return analogReadContinuous(); + } + + + ///////////// CONTINUOUS CONVERSION METHODS //////////// + + //! Starts continuous conversion on the pin. + /** It returns as soon as the ADC is set, use analogReadContinuous() to read the value. + */ + bool startContinuous(uint8_t pin); + + //! Starts continuous conversion between the pins (pinP-pinN). + /** It returns as soon as the ADC is set, use analogReadContinuous() to read the value. + * \param pinP must be A10 or A12. + * \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12). + * Other pins will return ADC_ERROR_DIFF_VALUE. + */ + bool startContinuousDifferential(uint8_t pinP, uint8_t pinN); + + //! Reads the analog value of a continuous conversion. + /** Set the continuous conversion with with analogStartContinuous(pin) or startContinuousDifferential(pinP, pinN). + * \return the last converted value. + * If single-ended and 16 bits it's necessary to typecast it to an unsigned type (like uint16_t), + * otherwise values larger than 3.3/2 V are interpreted as negative! + */ + int analogReadContinuous() __attribute__((always_inline)) { + return (int16_t)(int32_t)*ADC_RA; + } + + //! Stops continuous conversion + void stopContinuous(); + + + //////////// PDB //////////////// + //// Only works for Teensy 3.0 and 3.1, not LC (it doesn't have PDB) + #if ADC_USE_PDB + + // software trigger enable PDB PDB interrupt + #define PDB_CONFIG (PDB_SC_TRGSEL(15) | PDB_SC_PDBEN | PDB_SC_PDBIE \ + | PDB_SC_CONT | PDB_SC_LDMOD(0)) + // continuous mode load immediately + + #define PDB_CHnC1_TOS_1 0x0100 + #define PDB_CHnC1_EN_1 0x01 + + //! Start PDB triggering the ADC at the frequency + /** Call analogRead on the pin that you want to measure before calling this function. + * See the example. + * @param freq is the frequency of the ADC conversion, it can't be lower that 1 Hz + */ + void startPDB(uint32_t freq); + + //! Stop the PDB + /** + */ + void stopPDB(); + + #endif + + + //////// OTHER STUFF /////////// + + //! Store the config of the adc + struct ADC_Config { + uint32_t savedSC1A, savedSC2, savedSC3, savedCFG1, savedCFG2; + } adc_config; + + //! Was the adc in use before a call? + uint8_t adcWasInUse; + + //! Save config of the ADC to the ADC_Config struct + void saveConfig(ADC_Config* config) { + config->savedSC1A = *ADC_SC1A; + config->savedCFG1 = *ADC_CFG1; + config->savedCFG2 = *ADC_CFG2; + config->savedSC2 = *ADC_SC2; + config->savedSC3 = *ADC_SC3; + } + + //! Load config to the ADC + void loadConfig(const ADC_Config* config) { + *ADC_CFG1 = config->savedCFG1; + *ADC_CFG2 = config->savedCFG2; + *ADC_SC2 = config->savedSC2; + *ADC_SC3 = config->savedSC3; + *ADC_SC1A = config->savedSC1A; // restore last + } + + + //! Number of measurements that the ADC is performing + uint8_t num_measurements; + + + //! This flag indicates that some kind of error took place + /** Use the defines at the beggining of this file to find out what caused the fail. + */ + volatile uint16_t fail_flag; + + + //! Which adc is this? + uint8_t ADC_num; + + + +private: + + // is set to 1 when the calibration procedure is taking place + volatile uint8_t calibrating; + + // the first calibration will use 32 averages and lowest speed, + // when this calibration is over the averages and speed will be set to default. + volatile uint8_t init_calib; + + // resolution + volatile uint8_t analog_res_bits; + + // maximum value possible 2^res-1 + volatile uint32_t analog_max_val; + + // num of averages + volatile uint8_t analog_num_average; + + // reference can be internal or external + volatile uint8_t analog_reference_internal; + + // are interrupts enabled? + volatile uint8_t var_enableInterrupts; + + // value of the pga + volatile uint8_t pga_value; + + // conversion speed + volatile uint8_t conversion_speed; + + // sampling speed + volatile uint8_t sampling_speed; + + // translate pin number to SC1A nomenclature + const uint8_t* const channel2sc1a; + + // same for differential pins + const ADC_NLIST* const diff_table; + + + //! Get the SC1A value of the differential pair for this pin + uint8_t getDifferentialPair(uint8_t pin) { + for(int i=0; ipin = pin; +} + + +void Bounce::write(int new_state) + { + this->state = new_state; + digitalWrite(pin,state); + } + + +void Bounce::interval(unsigned long interval_millis) +{ + this->interval_millis = interval_millis; + this->rebounce_millis = 0; +} + +void Bounce::rebounce(unsigned long interval) +{ + this->rebounce_millis = interval; +} + + + +int Bounce::update() +{ + if ( debounce() ) { + rebounce(0); + return stateChanged = 1; + } + + // We need to rebounce, so simulate a state change + + if ( rebounce_millis && (millis() - previous_millis >= rebounce_millis) ) { + previous_millis = millis(); + rebounce(0); + return stateChanged = 1; + } + + return stateChanged = 0; +} + + +unsigned long Bounce::duration() +{ + return millis() - previous_millis; +} + + +int Bounce::read() +{ + return (int)state; +} + + +// Protected: debounces the pin +int Bounce::debounce() { + + uint8_t newState = digitalRead(pin); + if (state != newState ) { + if (millis() - previous_millis >= interval_millis) { + previous_millis = millis(); + state = newState; + return 1; + } + } + + return 0; + +} + +// The risingEdge method is true for one scan after the de-bounced input goes from off-to-on. +bool Bounce::risingEdge() { return stateChanged && state; } +// The fallingEdge method it true for one scan after the de-bounced input goes from on-to-off. +bool Bounce::fallingEdge() { return stateChanged && !state; } + diff --git a/Firmware_V2/src/libraries/Bounce/Bounce.h b/Firmware_V2/src/libraries/Bounce/Bounce.h new file mode 100644 index 0000000..7be294c --- /dev/null +++ b/Firmware_V2/src/libraries/Bounce/Bounce.h @@ -0,0 +1,70 @@ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * + Main code by Thomas O Fredericks + Rebounce and duration functions contributed by Eric Lowry + Write function contributed by Jim Schimpf + risingEdge and fallingEdge contributed by Tom Harkaway +* * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef Bounce_h +#define Bounce_h + +#include + +class Bounce +{ + +public: + // Initialize + Bounce(uint8_t pin, unsigned long interval_millis ); + // Sets the debounce interval + void interval(unsigned long interval_millis); + // Updates the pin + // Returns 1 if the state changed + // Returns 0 if the state did not change + int update(); + // Forces the pin to signal a change (through update()) in X milliseconds + // even if the state does not actually change + // Example: press and hold a button and have it repeat every X milliseconds + void rebounce(unsigned long interval); + // Returns the updated pin state + int read(); + // Sets the stored pin state + void write(int new_state); + // Returns the number of milliseconds the pin has been in the current state + unsigned long duration(); + // The risingEdge method is true for one scan after the de-bounced input goes from off-to-on. + bool risingEdge(); + // The fallingEdge method it true for one scan after the de-bounced input goes from on-to-off. + bool fallingEdge(); + +protected: + int debounce(); + unsigned long previous_millis, interval_millis, rebounce_millis; + uint8_t state; + uint8_t pin; + uint8_t stateChanged; +}; + +#endif + + diff --git a/Firmware_V2/src/libraries/EEPROM/EEPROM.cpp b/Firmware_V2/src/libraries/EEPROM/EEPROM.cpp new file mode 100644 index 0000000..ec716dd --- /dev/null +++ b/Firmware_V2/src/libraries/EEPROM/EEPROM.cpp @@ -0,0 +1 @@ +// this file no longer used diff --git a/Firmware_V2/src/libraries/EEPROM/EEPROM.h b/Firmware_V2/src/libraries/EEPROM/EEPROM.h new file mode 100644 index 0000000..4ca29ca --- /dev/null +++ b/Firmware_V2/src/libraries/EEPROM/EEPROM.h @@ -0,0 +1,156 @@ +/* + EEPROM.h - EEPROM library + Original Copyright (c) 2006 David A. Mellis. All right reserved. + New version by Christopher Andrews 2015. + This copy has minor modificatons for use with Teensy, by Paul Stoffregen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef EEPROM_h +#define EEPROM_h + +#include +#include +#include + + +/*** + EERef class. + + This object references an EEPROM cell. + Its purpose is to mimic a typical byte of RAM, however its storage is the EEPROM. + This class has an overhead of two bytes, similar to storing a pointer to an EEPROM cell. +***/ + +struct EERef{ + + EERef( const int index ) + : index( index ) {} + + //Access/read members. + uint8_t operator*() const { return eeprom_read_byte( (uint8_t*) index ); } + operator const uint8_t() const { return **this; } + + //Assignment/write members. + EERef &operator=( const EERef &ref ) { return *this = *ref; } + EERef &operator=( uint8_t in ) { return eeprom_write_byte( (uint8_t*) index, in ), *this; } + EERef &operator +=( uint8_t in ) { return *this = **this + in; } + EERef &operator -=( uint8_t in ) { return *this = **this - in; } + EERef &operator *=( uint8_t in ) { return *this = **this * in; } + EERef &operator /=( uint8_t in ) { return *this = **this / in; } + EERef &operator ^=( uint8_t in ) { return *this = **this ^ in; } + EERef &operator %=( uint8_t in ) { return *this = **this % in; } + EERef &operator &=( uint8_t in ) { return *this = **this & in; } + EERef &operator |=( uint8_t in ) { return *this = **this | in; } + EERef &operator <<=( uint8_t in ) { return *this = **this << in; } + EERef &operator >>=( uint8_t in ) { return *this = **this >> in; } + + EERef &update( uint8_t in ) { return in != *this ? *this = in : *this; } + + /** Prefix increment/decrement **/ + EERef& operator++() { return *this += 1; } + EERef& operator--() { return *this -= 1; } + + /** Postfix increment/decrement **/ + uint8_t operator++ (int) { + uint8_t ret = **this; + return ++(*this), ret; + } + + uint8_t operator-- (int) { + uint8_t ret = **this; + return --(*this), ret; + } + + int index; //Index of current EEPROM cell. +}; + +/*** + EEPtr class. + + This object is a bidirectional pointer to EEPROM cells represented by EERef objects. + Just like a normal pointer type, this can be dereferenced and repositioned using + increment/decrement operators. +***/ + +struct EEPtr{ + + EEPtr( const int index ) + : index( index ) {} + + operator const int() const { return index; } + EEPtr &operator=( int in ) { return index = in, *this; } + + //Iterator functionality. + bool operator!=( const EEPtr &ptr ) { return index != ptr.index; } + EERef operator*() { return index; } + + /** Prefix & Postfix increment/decrement **/ + EEPtr& operator++() { return ++index, *this; } + EEPtr& operator--() { return --index, *this; } + EEPtr operator++ (int) { return index++; } + EEPtr operator-- (int) { return index--; } + + int index; //Index of current EEPROM cell. +}; + +/*** + EEPROMClass class. + + This object represents the entire EEPROM space. + It wraps the functionality of EEPtr and EERef into a basic interface. + This class is also 100% backwards compatible with earlier Arduino core releases. +***/ + +struct EEPROMClass{ + +#if defined(__arm__) && defined(TEENSYDUINO) + EEPROMClass() { eeprom_initialize(); } +#endif + + //Basic user access methods. + EERef operator[]( const int idx ) { return idx; } + uint8_t read( int idx ) { return EERef( idx ); } + void write( int idx, uint8_t val ) { (EERef( idx )) = val; } + void update( int idx, uint8_t val ) { EERef( idx ).update( val ); } + + //STL and C++11 iteration capability. + EEPtr begin() { return 0x00; } + EEPtr end() { return length(); } //Standards requires this to be the item after the last valid entry. The returned pointer is invalid. + uint16_t length() { return E2END + 1; } + + //Functionality to 'get' and 'put' objects to and from EEPROM. + template< typename T > T &get( int idx, T &t ){ + EEPtr e = idx; + uint8_t *ptr = (uint8_t*) &t; + for( int count = sizeof(T) ; count ; --count, ++e ) *ptr++ = *e; + return t; + } + + template< typename T > const T &put( int idx, const T &t ){ + const uint8_t *ptr = (const uint8_t*) &t; +#ifdef __arm__ + eeprom_write_block(ptr, (void *)idx, sizeof(T)); +#else + EEPtr e = idx; + for( int count = sizeof(T) ; count ; --count, ++e ) (*e).update( *ptr++ ); +#endif + return t; + } +}; + +static EEPROMClass EEPROM __attribute__ ((unused)); +#endif diff --git a/Firmware_V2/src/libraries/I2C/i2c_t3.cpp b/Firmware_V2/src/libraries/I2C/i2c_t3.cpp new file mode 100644 index 0000000..11e8bdb --- /dev/null +++ b/Firmware_V2/src/libraries/I2C/i2c_t3.cpp @@ -0,0 +1,1915 @@ +/* + ------------------------------------------------------------------------------------------------------ + i2c_t3 - I2C library for Teensy 3.x & LC + + - (v9.2) Modified 29Dec16 by Brian (nox771 at gmail.com) + - improved resetBus() function to reset C1 state (thanks hw999) + + - (v9.1) Modified 16Oct16 by Brian (nox771 at gmail.com) + - applied two fixes due to bug reports: + - removed I2C_F_DIV120 setting (120 divide-ratio) for I2C clock + - disabled I2C_AUTO_RETRY by default (setting remains but must be manually enabled) + + - (v9) Modified 01Jul16 by Brian (nox771 at gmail.com) + - Added support for Teensy 3.5/3.6: + - fully supported (Master/Slave modes, IMM/ISR/DMA operation) + - supports all available pin/bus options on Wire/Wire1/Wire2/Wire3 + - Fixed LC slave bug, whereby it was incorrectly detecting STOPs directed to other slaves + - I2C rate is now set using a much more flexible method than previously used (this is partially + motivated by increasing device count and frequencies). As a result, the fixed set of rate + enums are no longer needed (however they are currently still supported), and desired I2C + frequency can be directly specified, eg. for 400kHz, I2C_RATE_400 can be replaced by 400000. + Some setRate() functions are deprecated due to these changes. + + - (v8) Modified 02Apr15 by Brian (nox771 at gmail.com) + - added support for Teensy LC: + - fully supported (Master/Slave modes, IMM/ISR/DMA operation) + - Wire: pins 16/17 or 18/19, rate limited to I2C_RATE_1200 + - Wire1: pins 22/23, rate limited to I2C_RATE_2400 + - added timeout on acquiring bus (prevents lockup when bus cannot be acquired) + - added setDefaultTimeout() function for setting the default timeout to apply to all commands + - added resetBus() function for toggling SCL to release stuck Slave devices + - added setRate(rate) function, similar to setClock(freq), but using rate specifiers (does not + require specifying busFreq) + - added I2C_AUTO_RETRY user define + + - (v7) Modified 09Jan15 by Brian (nox771 at gmail.com) + - added support for F_BUS frequencies: 60MHz, 56MHz, 48MHz, 36MHz, 24MHz, 16MHz, 8MHz, 4MHz, 2MHz + - added new rates: I2C_RATE_1800, I2C_RATE_2800, I2C_RATE_3000 + - added new priority escalation - in cases where I2C ISR is blocked by having a lower priority than + calling function, the I2C will either adjust I2C ISR to a higher priority, + or switch to Immediate mode as needed. + - added new operating mode control - I2C can be set to operate in ISR mode, DMA mode (Master only), + or Immediate Mode (Master only) + - added new begin() functions to allow setting the initial operating mode: + - begin(i2c_mode mode, uint8_t address, i2c_pins pins, i2c_pullup pullup, i2c_rate rate, i2c_op_mode opMode) + - begin(i2c_mode mode, uint8_t address1, uint8_t address2, i2c_pins pins, i2c_pullup pullup, i2c_rate rate, i2c_op_mode opMode) + - added new functions: + - uint8_t setOpMode(i2c_op_mode opMode) - used to change operating mode on the fly (only when bus is idle) + - void sendTransmission() - non-blocking Tx with implicit I2C_STOP, added for symmetry with endTransmission() + - uint8_t setRate(uint32_t busFreq, i2c_rate rate) - used to set I2C clock dividers to get desired rate, i2c_rate argument + - uint8_t setRate(uint32_t busFreq, uint32_t i2cFreq) - used to set I2C clock dividers to get desired SCL freq, uint32_t argument + (quantized to nearest i2c_rate) + - added new Wire compatibility functions: + - void setClock(uint32_t i2cFreq) - (note: degenerate form of setRate() with busFreq == F_BUS) + - uint8_t endTransmission(uint8_t sendStop) + - uint8_t requestFrom(uint8_t addr, uint8_t len) + - uint8_t requestFrom(uint8_t addr, uint8_t len, uint8_t sendStop) + - fixed bug in Slave Range code whereby onRequest() callback occurred prior to updating rxAddr instead of after + - fixed bug in arbitration, was missing from Master Tx mode + - removed I2C1 defines (now included in kinetis.h) + - removed all debug code (eliminates rbuf dependency) + + - (v6) Modified 16Jan14 by Brian (nox771 at gmail.com) + - all new structure using dereferenced pointers instead of hardcoding. This allows functions + (including ISRs) to be reused across multiple I2C buses. Most functions moved to static, + which in turn are called by inline user functions. Added new struct (i2cData) for holding all + bus information. + - added support for Teensy 3.1 and I2C1 interface on pins 29/30 and 26/31. + - added header define (I2C_BUS_ENABLE n) to control number of enabled buses (eg. both I2C0 & I2C1 + or just I2C0). When using only I2C0 the code and ram usage will be lower. + - added interrupt flag (toggles pin high during ISR) with independent defines for I2C0 and + I2C1 (refer to header file), useful for logic analyzer trigger + + - (v5) Modified 09Jun13 by Brian (nox771 at gmail.com) + - fixed bug in ISR timeout code in which timeout condition could fail to reset in certain cases + - fixed bug in Slave mode in sda_rising_isr attach, whereby it was not getting attached on the addr byte + - moved debug routines so they are entirely defined internal to the library (no end user code req'd) + - debug routines now use IntervalTimer library + - added support for range of Slave addresses + - added getRxAddr() for Slave using addr range to determine its called address + - removed virtual keyword from all functions (is not a base class) + + - (v1-v4) Modified 26Feb13 by Brian (nox771 at gmail.com) + - Reworked begin function: + - added option for pins to use (SCL:SDA on 19:18 or 16:17 - note pin order difference) + - added option for internal pullup - as mentioned in previous code pullup is very strong, + approx 190 ohms, but is possibly useful for high speed I2C + - added option for rates - 100kHz, 200kHz, 300kHz, 400kHz, 600kHz, 800kHz, 1MHz, 1.2MHz, <-- 24/48MHz bus + 1.5MHz, 2.0MHz, 2.4MHz <-- 48MHz bus only + - Removed string.h dependency (memcpy) + - Changed Master modes to interrupt driven + - Added non-blocking Tx/Rx routines, and status/done/finish routines: + - sendTransmission() - non-blocking transmit + - sendRequest() - non-blocking receive + - status() - reports current status + - done() - indicates Tx/Rx complete (for main loop polling if I2C is running in background) + - finish() - loops until Tx/Rx complete or bus error + - Added readByte()/peekByte() for uint8_t return values (note: returns 0 instead of -1 if buf empty) + - Added fixes for Slave Rx mode - in short Slave Rx on this part is fubar + (as proof, notice the difference in the I2Cx_FLT register in the KL25 Sub-Family parts) + - the SDA-rising ISR hack can work but only detects STOP conditons. + A slave Rx followed by RepSTART won't be detected since bus remains busy. + To fix this if IAAS occurs while already in Slave Rx mode then it will + assume RepSTART occurred and trigger onReceive callback. + - Separated Tx/Rx buffer sizes for asymmetric devices (adjustable in i2c_t3.h) + - Changed Tx/Rx buffer indicies to size_t to allow for large (>256 byte) buffers + - Left debug routines in place (controlled via header defines - default is OFF). If debug is + enabled, note that it can easily overrun the Debug queue on large I2C transfers, yielding + garbage output. Adjust ringbuf size (in rbuf.h) and possibly PIT interrupt rate to adjust + data flow to Serial (note also the buffer in Serial can overflow if written too quickly). + - Added getError() function to return Wire error code + - Added pinConfigure() function for changing pins on the fly (only when bus not busy) + - Added timeouts to endTransmission(), requestFrom(), and finish() + ------------------------------------------------------------------------------------------------------ + Some code segments derived from: + TwoWire.cpp - TWI/I2C library for Wiring & Arduino + Copyright (c) 2006 Nicholas Zambetti. All right reserved. + Modified 2012 by Todd Krein (todd@krein.org) to implement repeated starts + ------------------------------------------------------------------------------------------------------ + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__) || \ + defined(__MK64FX512__) || defined(__MK66FX1M0__) // 3.0/3.1-3.2/LC/3.5/3.6 + +#include "i2c_t3.h" + + +// ------------------------------------------------------------------------------------------------------ +// Static inits +// +#define I2C_STRUCT(a1,f,c1,s,d,c2,flt,ra,smb,a2,slth,sltl,pins) \ + {a1, f, c1, s, d, c2, flt, ra, smb, a2, slth, sltl, {}, 0, 0, {}, 0, 0, I2C_OP_MODE_ISR, I2C_MASTER, pins, \ + I2C_PULLUP_EXT, 100000, I2C_STOP, I2C_WAITING, 0, 0, 0, 0, I2C_DMA_OFF, nullptr, nullptr, nullptr, 0} + +struct i2cStruct i2c_t3::i2cData[] = +{ + I2C_STRUCT(&I2C0_A1, &I2C0_F, &I2C0_C1, &I2C0_S, &I2C0_D, &I2C0_C2, &I2C0_FLT, &I2C0_RA, &I2C0_SMB, &I2C0_A2, &I2C0_SLTH, &I2C0_SLTL, I2C_PINS_18_19) +#if (I2C_BUS_NUM >= 2) && defined(__MK20DX256__) // 3.1/3.2 + ,I2C_STRUCT(&I2C1_A1, &I2C1_F, &I2C1_C1, &I2C1_S, &I2C1_D, &I2C1_C2, &I2C1_FLT, &I2C1_RA, &I2C1_SMB, &I2C1_A2, &I2C1_SLTH, &I2C1_SLTL, I2C_PINS_29_30) +#elif (I2C_BUS_NUM >= 2) && defined(__MKL26Z64__) // LC + ,I2C_STRUCT(&I2C1_A1, &I2C1_F, &I2C1_C1, &I2C1_S, &I2C1_D, &I2C1_C2, &I2C1_FLT, &I2C1_RA, &I2C1_SMB, &I2C1_A2, &I2C1_SLTH, &I2C1_SLTL, I2C_PINS_22_23) +#elif (I2C_BUS_NUM >= 2) && (defined(__MK64FX512__) || defined(__MK66FX1M0__)) // 3.5/3.6 + ,I2C_STRUCT(&I2C1_A1, &I2C1_F, &I2C1_C1, &I2C1_S, &I2C1_D, &I2C1_C2, &I2C1_FLT, &I2C1_RA, &I2C1_SMB, &I2C1_A2, &I2C1_SLTH, &I2C1_SLTL, I2C_PINS_37_38) +#endif +#if (I2C_BUS_NUM >= 3) && (defined(__MK64FX512__) || defined(__MK66FX1M0__)) // 3.5/3.6 + ,I2C_STRUCT(&I2C2_A1, &I2C2_F, &I2C2_C1, &I2C2_S, &I2C2_D, &I2C2_C2, &I2C2_FLT, &I2C2_RA, &I2C2_SMB, &I2C2_A2, &I2C2_SLTH, &I2C2_SLTL, I2C_PINS_3_4) +#endif +#if (I2C_BUS_NUM >= 4) && defined(__MK66FX1M0__) // 3.6 + ,I2C_STRUCT(&I2C3_A1, &I2C3_F, &I2C3_C1, &I2C3_S, &I2C3_D, &I2C3_C2, &I2C3_FLT, &I2C3_RA, &I2C3_SMB, &I2C3_A2, &I2C3_SLTH, &I2C3_SLTL, I2C_PINS_56_57) +#endif +}; + + +// ------------------------------------------------------------------------------------------------------ +// Constructor/Destructor +// +i2c_t3::i2c_t3(uint8_t i2c_bus) +{ + bus = i2c_bus; + i2c = &i2cData[bus]; +} +i2c_t3::~i2c_t3() +{ + // if DMA active, delete DMA object + if(i2c->opMode == I2C_OP_MODE_DMA) + delete i2c->DMA; +} + + +// ------------------------------------------------------------------------------------------------------ +// Initialize I2C - initializes I2C as Master or address range Slave +// return: none +// parameters: +// mode = I2C_MASTER, I2C_SLAVE +// address1 = 1st 7bit address for specifying Slave address range (ignored for Master mode) +// address2 = 2nd 7bit address for specifying Slave address range (ignored for Master mode) +// pins = pins to use, options are: +// Interface Devices Pin Name SCL SDA +// --------- ------- -------------- ----- ----- (note: in almost all cases SCL is the +// Wire All I2C_PINS_16_17 16 17 lower pin #, except cases marked *) +// Wire All I2C_PINS_18_19 19 18 * +// Wire 3.5/3.6 I2C_PINS_7_8 7 8 +// Wire 3.5/3.6 I2C_PINS_33_34 33 34 +// Wire 3.5/3.6 I2C_PINS_47_48 47 48 +// Wire1 LC I2C_PINS_22_23 22 23 +// Wire1 3.1/3.2 I2C_PINS_26_31 26 31 +// Wire1 3.1/3.2 I2C_PINS_29_30 29 30 +// Wire1 3.5/3.6 I2C_PINS_37_38 37 38 +// Wire2 3.5/3.6 I2C_PINS_3_4 3 4 +// Wire3 3.6 I2C_PINS_56_57 57 56 * +// pullup = I2C_PULLUP_EXT, I2C_PULLUP_INT +// rate = I2C frequency to use, can be specified directly in Hz, eg. 400000 for 400kHz, or using one of the +// following enum values (deprecated): +// I2C_RATE_100, I2C_RATE_200, I2C_RATE_300, I2C_RATE_400, +// I2C_RATE_600, I2C_RATE_800, I2C_RATE_1000, I2C_RATE_1200, +// I2C_RATE_1500, I2C_RATE_1800, I2C_RATE_2000, I2C_RATE_2400, +// I2C_RATE_2800, I2C_RATE_3000 +// opMode = I2C_OP_MODE_IMM, I2C_OP_MODE_ISR, I2C_OP_MODE_DMA (ignored for Slave mode, defaults to ISR) +// +void i2c_t3::begin_(struct i2cStruct* i2c, uint8_t bus, i2c_mode mode, uint8_t address1, uint8_t address2, + i2c_pins pins, i2c_pullup pullup, uint32_t rate, i2c_op_mode opMode) +{ + // Enable I2C internal clock + if(bus == 0) + SIM_SCGC4 |= SIM_SCGC4_I2C0; + #if I2C_BUS_NUM >= 2 + if(bus == 1) + SIM_SCGC4 |= SIM_SCGC4_I2C1; + #endif + #if I2C_BUS_NUM >= 3 + if(bus == 2) + SIM_SCGC1 |= SIM_SCGC1_I2C2; + #endif + #if I2C_BUS_NUM >= 4 + if(bus == 3) + SIM_SCGC1 |= SIM_SCGC1_I2C3; + #endif + + i2c->currentMode = mode; // Set mode + i2c->currentStatus = I2C_WAITING; // reset status + + // Set Master/Slave address (zeroed in Master to prevent accidental Rx when setup is changed dynamically) + if(i2c->currentMode == I2C_MASTER) + { + *(i2c->C2) = I2C_C2_HDRS; // Set high drive select + *(i2c->A1) = 0; + *(i2c->RA) = 0; + } + else + { + *(i2c->C2) = (address2) ? (I2C_C2_HDRS|I2C_C2_RMEN) // Set high drive select and range-match enable + : I2C_C2_HDRS; // Set high drive select + // set Slave address, if two addresses are given, setup range and put lower address in A1, higher in RA + *(i2c->A1) = (address2) ? ((address1 < address2) ? (address1<<1) : (address2<<1)) + : (address1<<1); + *(i2c->RA) = (address2) ? ((address1 < address2) ? (address2<<1) : (address1<<1)) + : 0; + } + + // Setup pins and options (note: does not "unset" unused pins if changed). As noted in + // original TwoWire.cpp, internal 3.0/3.1 pullup is strong (about 190 ohms), but it can + // work if other devices on bus have strong enough pulldown devices (usually true). + // + pinConfigure_(i2c, bus, pins, pullup, 0); + + // Set I2C rate + #if defined(__MKL26Z64__) // LC + if(bus == 1) + setRate_(i2c, (uint32_t)F_CPU, rate); // LC Wire1 bus uses system clock (F_CPU) instead of bus clock (F_BUS) + else + setRate_(i2c, (uint32_t)F_BUS, rate); + #else + setRate_(i2c, (uint32_t)F_BUS, rate); + #endif + + // Set config registers and operating mode + setOpMode_(i2c, bus, opMode); + if(i2c->currentMode == I2C_MASTER) + *(i2c->C1) = I2C_C1_IICEN; // Master - enable I2C (hold in Rx mode, intr disabled) + else + *(i2c->C1) = I2C_C1_IICEN|I2C_C1_IICIE; // Slave - enable I2C and interrupts +} + + +// Get Default Pins - this obtains the default pins to use when using simplified begin() calls, +// intended for internal use only +// return: i2c_pins - default pin setting: +// Wire: I2C_PINS_18_19 +// Wire1: I2C_PINS_29_30 (3.1/3.2), I2C_PINS_22_23 (LC), I2C_PINS_37_38 (3.5/3.6) +// Wire2: I2C_PINS_3_4 (3.5/3.6) +// Wire3: I2C_PINS_56_57 (3.6) +// +i2c_pins i2c_t3::getDefaultPins_(uint8_t bus) +{ + i2c_pins defpins = I2C_PINS_18_19; + #if defined(__MKL26Z64__) + defpins = (bus == 1) ? I2C_PINS_22_23 : I2C_PINS_18_19; // LC + #elif defined(__MK20DX128__) + defpins = I2C_PINS_18_19; // 3.0 + #elif defined(__MK20DX256__) + defpins = (bus == 1) ? I2C_PINS_29_30 : I2C_PINS_18_19; // 3.1/3.2 + #elif defined(__MK64FX512__) + defpins = (bus == 2) ? I2C_PINS_3_4 : + (bus == 1) ? I2C_PINS_37_38 : + I2C_PINS_18_19; // 3.5 + #elif defined(__MK66FX1M0__) + defpins = (bus == 3) ? I2C_PINS_56_57 : + (bus == 2) ? I2C_PINS_3_4 : + (bus == 1) ? I2C_PINS_37_38 : + I2C_PINS_18_19; // 3.6 + #endif + return defpins; +} + + +// Set Operating Mode - this configures operating mode of the I2C as either Immediate, ISR, or DMA. +// By default Arduino-style begin() calls will initialize to ISR mode. This can +// only be called when the bus is idle (no changing mode in the middle of Tx/Rx). +// Note that Slave mode can only use ISR operation. +// return: 1=success, 0=fail (bus busy) +// parameters: +// opMode = I2C_OP_MODE_ISR, I2C_OP_MODE_DMA, I2C_OP_MODE_IMM +// +uint8_t i2c_t3::setOpMode_(struct i2cStruct* i2c, uint8_t bus, i2c_op_mode opMode) +{ + if(*(i2c->S) & I2C_S_BUSY) return 0; // return immediately if bus busy + + *(i2c->C1) = I2C_C1_IICEN; // reset I2C modes, stop intr, stop DMA + *(i2c->S) = I2C_S_IICIF | I2C_S_ARBL; // clear status flags just in case + + // Slaves can only use ISR + if(i2c->currentMode == I2C_SLAVE) opMode = I2C_OP_MODE_ISR; + + if(opMode == I2C_OP_MODE_IMM) + { + i2c->opMode = I2C_OP_MODE_IMM; + } + if(opMode == I2C_OP_MODE_ISR || opMode == I2C_OP_MODE_DMA) + { + // Nested Vec Interrupt Ctrl - enable I2C interrupt + if(bus == 0) + { + NVIC_ENABLE_IRQ(IRQ_I2C0); + I2C0_INTR_FLAG_INIT; // init I2C0 interrupt flag if used + } + #if I2C_BUS_NUM >= 2 + if(bus == 1) + { + NVIC_ENABLE_IRQ(IRQ_I2C1); + I2C1_INTR_FLAG_INIT; // init I2C1 interrupt flag if used + } + #endif + #if I2C_BUS_NUM >= 3 + if(bus == 2) + { + NVIC_ENABLE_IRQ(IRQ_I2C2); + I2C2_INTR_FLAG_INIT; // init I2C2 interrupt flag if used + } + #endif + #if I2C_BUS_NUM >= 4 + if(bus == 3) + { + NVIC_ENABLE_IRQ(IRQ_I2C3); + I2C3_INTR_FLAG_INIT; // init I2C3 interrupt flag if used + } + #endif + if(opMode == I2C_OP_MODE_DMA) + { + // attempt to get a DMA Channel (if not already allocated) + if(i2c->DMA == nullptr) + i2c->DMA = new DMAChannel(); + // check if object created but no available channel + if(i2c->DMA != nullptr && i2c->DMA->channel == DMA_NUM_CHANNELS) + { + // revert to ISR mode if no DMA channels avail + delete i2c->DMA; + i2c->DMA = nullptr; + i2c->opMode = I2C_OP_MODE_ISR; + } + else + { + // DMA object has valid channel + if(bus == 0) + { + // setup static DMA settings + i2c->DMA->disableOnCompletion(); + i2c->DMA->attachInterrupt(i2c0_isr); + i2c->DMA->interruptAtCompletion(); + i2c->DMA->triggerAtHardwareEvent(DMAMUX_SOURCE_I2C0); + } + #if I2C_BUS_NUM >= 2 + if(bus == 1) + { + // setup static DMA settings + i2c->DMA->disableOnCompletion(); + i2c->DMA->attachInterrupt(i2c1_isr); + i2c->DMA->interruptAtCompletion(); + i2c->DMA->triggerAtHardwareEvent(DMAMUX_SOURCE_I2C1); + } + #endif + #if I2C_BUS_NUM >= 3 + // note: on T3.6 I2C2 shares DMAMUX with I2C1 + if(bus == 2) + { + // setup static DMA settings + i2c->DMA->disableOnCompletion(); + i2c->DMA->attachInterrupt(i2c2_isr); + i2c->DMA->interruptAtCompletion(); + i2c->DMA->triggerAtHardwareEvent(DMAMUX_SOURCE_I2C2); + } + #endif + #if I2C_BUS_NUM >= 4 + // note: on T3.6 I2C3 shares DMAMUX with I2C0 + if(bus == 3) + { + // setup static DMA settings + i2c->DMA->disableOnCompletion(); + i2c->DMA->attachInterrupt(i2c3_isr); + i2c->DMA->interruptAtCompletion(); + i2c->DMA->triggerAtHardwareEvent(DMAMUX_SOURCE_I2C3); + } + #endif + i2c->activeDMA = I2C_DMA_OFF; + i2c->opMode = I2C_OP_MODE_DMA; + } + } + else + i2c->opMode = I2C_OP_MODE_ISR; + } + return 1; +} + + +// Set I2C rate - reconfigures I2C frequency divider based on supplied bus freq and desired I2C freq. +// This will be done assuming an idealized I2C rate, even though at high I2C rates +// the actual throughput is much lower than theoretical value. +// +// Since the division ratios are quantized with non-uniform spacing, the selected rate +// will be the one using the nearest available divider. +// return: none +// parameters: +// busFreq = bus frequency, typically F_BUS unless reconfigured +// freq = desired I2C frequency (will be quantized to nearest rate), or can be I2C_RATE_XXX enum (deprecated), +// such as I2C_RATE_100, I2C_RATE_400, etc... +// +// Max I2C rate is 1/20th F_BUS. Some examples: +// +// F_CPU F_BUS Max I2C +// (MHz) (MHz) Rate +// ------------- ----- ---------- +// 240/120 120 6.0M bus overclock +// 216 108 5.4M bus overclock +// 192/96 96 4.8M bus overclock +// 180 90 4.5M bus overclock +// 240 80 4.0M bus overclock +// 216/144/72 72 3.6M bus overclock +// 192 64 3.2M bus overclock +// 240/180/120 60 3.0M +// 168 56 2.8M +// 216 54 2.7M +// 192/144/96/48 48 2.4M +// 72 36 1.8M +// 24 24 1.2M +// 16 16 800k +// 8 8 400k +// 4 4 200k +// 2 2 100k +// +void i2c_t3::setRate_(struct i2cStruct* i2c, uint32_t busFreq, uint32_t i2cFreq) +{ + int32_t target_div = ((busFreq/1000)<<8)/(i2cFreq/1000); + size_t idx; + // find closest divide ratio + for(idx=0; idx < sizeof(i2c_div_num)/sizeof(i2c_div_num[0]) && (i2c_div_num[idx]<<8) <= target_div; idx++); + if(idx && abs(target_div-(i2c_div_num[idx-1]<<8)) <= abs(target_div-(i2c_div_num[idx]<<8))) idx--; + // Set divider to set rate + *(i2c->F) = i2c_div_ratio[idx]; + // save current rate setting + i2c->currentRate = busFreq/i2c_div_num[idx]; + + // Set filter + if(busFreq >= 48000000) + *(i2c->FLT) = 4; + else + *(i2c->FLT) = busFreq/12000000; +} + + +// ------------------------------------------------------------------------------------------------------ +// Configure I2C pins - reconfigures active I2C pins on-the-fly (only works when bus is idle). If reconfig +// set then inactive pins will switch to input mode using same pullup configuration. +// return: 1=success, 0=fail (bus busy or incompatible pins) +// parameters: +// pins = pins to use, options are: +// Interface Devices Pin Name SCL SDA +// --------- ------- -------------- ----- ----- (note: in almost all cases SCL is the +// Wire All I2C_PINS_16_17 16 17 lower pin #, except cases marked *) +// Wire All I2C_PINS_18_19 19 18 * +// Wire 3.5/3.6 I2C_PINS_7_8 7 8 +// Wire 3.5/3.6 I2C_PINS_33_34 33 34 +// Wire 3.5/3.6 I2C_PINS_47_48 47 48 +// Wire1 LC I2C_PINS_22_23 22 23 +// Wire1 3.1/3.2 I2C_PINS_26_31 26 31 +// Wire1 3.1/3.2 I2C_PINS_29_30 29 30 +// Wire1 3.5/3.6 I2C_PINS_37_38 37 38 +// Wire2 3.5/3.6 I2C_PINS_3_4 3 4 +// Wire3 3.6 I2C_PINS_56_57 57 56 * +// pullup = I2C_PULLUP_EXT, I2C_PULLUP_INT +// reconfig = 1=reconfigure old pins, 0=do not reconfigure old pins (base routine only) +// +#define PIN_CONFIG_ALT(name,alt) uint32_t name = (pullup == I2C_PULLUP_EXT) ? (PORT_PCR_MUX(alt)|PORT_PCR_ODE|PORT_PCR_SRE|PORT_PCR_DSE) \ + : (PORT_PCR_MUX(alt)|PORT_PCR_PE|PORT_PCR_PS) + +uint8_t i2c_t3::pinConfigure_(struct i2cStruct* i2c, uint8_t bus, i2c_pins pins, i2c_pullup pullup, uint8_t reconfig) +{ + uint8_t retval = 0; + + if(reconfig && (*(i2c->S) & I2C_S_BUSY)) return 0; // if reconfig return immediately if bus busy (otherwise assume initial setup) + + // Create config settings + // + PIN_CONFIG_ALT(pinConfigAlt2,2); + #if defined(__MK20DX256__) // 3.1/3.2 + PIN_CONFIG_ALT(pinConfigAlt6,6); + #endif + #if defined(__MK64FX512__) || defined(__MK66FX1M0__) // 3.5/3.6 + PIN_CONFIG_ALT(pinConfigAlt5,5); + PIN_CONFIG_ALT(pinConfigAlt7,7); + #endif + + // Verify new pin setting is valid + // + if(bus == 0) + { + switch(pins) + { + case I2C_PINS_16_17: retval = 1; break; + case I2C_PINS_18_19: retval = 1; break; + #if defined(__MK64FX512__) || defined(__MK66FX1M0__) // 3.5/3.6 + case I2C_PINS_7_8: retval = 1; break; + case I2C_PINS_33_34: retval = 1; break; + case I2C_PINS_47_48: retval = 1; break; + #endif + default: break; + } + } + #if I2C_BUS_NUM >= 2 + if(bus == 1) + { + switch(pins) + { + #if defined(__MKL26Z64__) // LC + case I2C_PINS_22_23: retval = 1; break; + #endif + #if defined(__MK20DX256__) // 3.1/3.2 + case I2C_PINS_26_31: retval = 1; break; + case I2C_PINS_29_30: retval = 1; break; + #endif + #if defined(__MK64FX512__) || defined(__MK66FX1M0__) // 3.5/3.6 + case I2C_PINS_37_38: retval = 1; break; + #endif + default: break; + } + } + #endif + #if I2C_BUS_NUM >= 3 + if(bus == 2) + { + switch(pins) + { + #if defined(__MK64FX512__) || defined(__MK66FX1M0__) // 3.5/3.6 + case I2C_PINS_3_4: retval = 1; break; + #endif + default: break; + } + } + #endif + #if I2C_BUS_NUM >= 4 + if(bus == 3) + { + switch(pins) + { + #if defined(__MK66FX1M0__) // 3.6 + case I2C_PINS_56_57: retval = 1; break; + #endif + default: break; + } + } + #endif + + // If compatible pin setting found then reconfig (break-before-make) and update. + // + // If pins are given an impossible value (eg. I2C0 with I2C_PINS_26_31) then the function will return fail, + // and there will be no change to the configuration. + // + if(retval) + { + // If reconfig set, switch previous pins to non-I2C + if(reconfig) + { + uint8_t old_mode = (i2c->currentPullup == I2C_PULLUP_EXT) ? INPUT : INPUT_PULLUP; + switch(i2c->currentPins) + { + case I2C_PINS_16_17: + pinMode(17,old_mode); + pinMode(16,old_mode); + break; + case I2C_PINS_18_19: + pinMode(18,old_mode); + pinMode(19,old_mode); + break; + #if defined(__MKL26Z64__) // LC + case I2C_PINS_22_23: + pinMode(23,old_mode); + pinMode(22,old_mode); + break; + #endif + #if defined(__MK20DX256__) // 3.1/3.2 + case I2C_PINS_29_30: + pinMode(30,old_mode); + pinMode(29,old_mode); + break; + case I2C_PINS_26_31: + pinMode(31,old_mode); + pinMode(26,old_mode); + break; + #endif + #if defined(__MK64FX512__) || defined(__MK66FX1M0__) // 3.5/3.6 + case I2C_PINS_3_4: + pinMode(4,old_mode); + pinMode(3,old_mode); + break; + case I2C_PINS_7_8: + pinMode(8,old_mode); + pinMode(7,old_mode); + break; + case I2C_PINS_33_34: + pinMode(34,old_mode); + pinMode(33,old_mode); + break; + case I2C_PINS_37_38: + pinMode(38,old_mode); + pinMode(37,old_mode); + break; + case I2C_PINS_47_48: + pinMode(48,old_mode); + pinMode(47,old_mode); + break; + #endif + #if defined(__MK66FX1M0__) // 3.6 + case I2C_PINS_56_57: + pinMode(56,old_mode); + pinMode(57,old_mode); + break; + #endif + default: break; + } + } + + // Configure new pins + // + if(bus == 0) + { + switch(pins) + { + case I2C_PINS_16_17: + CORE_PIN17_CONFIG = pinConfigAlt2; + CORE_PIN16_CONFIG = pinConfigAlt2; + break; + case I2C_PINS_18_19: + CORE_PIN18_CONFIG = pinConfigAlt2; + CORE_PIN19_CONFIG = pinConfigAlt2; + break; + #if defined(__MK64FX512__) || defined(__MK66FX1M0__) // 3.5/3.6 + case I2C_PINS_7_8: + CORE_PIN8_CONFIG = pinConfigAlt7; + CORE_PIN7_CONFIG = pinConfigAlt7; + break; + case I2C_PINS_33_34: + CORE_PIN34_CONFIG = pinConfigAlt5; + CORE_PIN33_CONFIG = pinConfigAlt5; + break; + case I2C_PINS_47_48: + CORE_PIN48_CONFIG = pinConfigAlt2; + CORE_PIN47_CONFIG = pinConfigAlt2; + break; + #endif + default: break; + } + } + #if I2C_BUS_NUM >= 2 + if(bus == 1) + { + switch(pins) + { + #if defined(__MKL26Z64__) // LC + case I2C_PINS_22_23: + CORE_PIN23_CONFIG = pinConfigAlt2; + CORE_PIN22_CONFIG = pinConfigAlt2; + break; + #endif + #if defined(__MK20DX256__) // 3.1/3.2 + case I2C_PINS_26_31: + CORE_PIN31_CONFIG = pinConfigAlt6; + CORE_PIN26_CONFIG = pinConfigAlt6; + break; + case I2C_PINS_29_30: + CORE_PIN30_CONFIG = pinConfigAlt2; + CORE_PIN29_CONFIG = pinConfigAlt2; + break; + #endif + #if defined(__MK64FX512__) || defined(__MK66FX1M0__) // 3.5/3.6 + case I2C_PINS_37_38: + CORE_PIN38_CONFIG = pinConfigAlt2; + CORE_PIN37_CONFIG = pinConfigAlt2; + break; + #endif + default: break; + } + } + #endif + #if I2C_BUS_NUM >= 3 + if(bus == 2) + { + switch(pins) + { + #if defined(__MK64FX512__) || defined(__MK66FX1M0__) // 3.5/3.6 + case I2C_PINS_3_4: + CORE_PIN4_CONFIG = pinConfigAlt5; + CORE_PIN3_CONFIG = pinConfigAlt5; + break; + #endif + default: break; + } + } + #endif + #if I2C_BUS_NUM >= 4 + if(bus == 3) + { + switch(pins) + { + #if defined(__MK66FX1M0__) // 3.6 + case I2C_PINS_56_57: + CORE_PIN56_CONFIG = pinConfigAlt2; + CORE_PIN57_CONFIG = pinConfigAlt2; + break; + #endif + default: break; + } + } + #endif + + // Update settings + i2c->currentPins = pins; + i2c->currentPullup = pullup; + } + + return retval; +} + + +// ------------------------------------------------------------------------------------------------------ +// Acquire Bus - acquires bus in Master mode and escalates priority as needed, intended +// for internal use only +// return: 1=success, 0=fail (cannot acquire bus) +// parameters: +// timeout = timeout in microseconds +// forceImm = flag to indicate if immediate mode is required +// +uint8_t i2c_t3::acquireBus_(struct i2cStruct* i2c, uint8_t bus, uint32_t timeout, uint8_t& forceImm) +{ + elapsedMicros deltaT; + int irqPriority, currPriority; + + // update timeout + timeout = (timeout == 0) ? i2c->defTimeout : timeout; + + // TODO may need to check bus busy before issuing START if multi-master + + // start timer, then take control of the bus + deltaT = 0; + if(*(i2c->C1) & I2C_C1_MST) + { + // we are already the bus master, so send a repeated start + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_RSTA | I2C_C1_TX; + } + else + { + while(timeout == 0 || deltaT < timeout) + { + // we are not currently the bus master, so check if bus ready + if(!(*(i2c->S) & I2C_S_BUSY)) + { + // become the bus master in transmit mode (send start) + i2c->currentMode = I2C_MASTER; + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX; + break; + } + } + #if defined(I2C_AUTO_RETRY) + // if not master and auto-retry set, then reset bus and try one last time + if(!(*(i2c->C1) & I2C_C1_MST)) + { + resetBus_(i2c,bus); + if(!(*(i2c->S) & I2C_S_BUSY)) + { + // become the bus master in transmit mode (send start) + i2c->currentMode = I2C_MASTER; + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX; + } + } + #endif + // check if not master + if(!(*(i2c->C1) & I2C_C1_MST)) + { + i2c->currentStatus = I2C_TIMEOUT; // bus not acquired, mark as timeout + return 0; + } + } + + // For ISR operation, check if current routine has higher priority than I2C IRQ, and if so + // either escalate priority of I2C IRQ or send I2C using immediate mode + if(i2c->opMode == I2C_OP_MODE_ISR || i2c->opMode == I2C_OP_MODE_DMA) + { + currPriority = nvic_execution_priority(); + switch(bus) + { + case 0: irqPriority = NVIC_GET_PRIORITY(IRQ_I2C0); break; + #if defined(__MKL26Z64__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.1/3.2/3.5/3.6 + case 1: irqPriority = NVIC_GET_PRIORITY(IRQ_I2C1); break; + #endif + #if defined(__MK64FX512__) || defined(__MK66FX1M0__) // 3.5/3.6 + case 2: irqPriority = NVIC_GET_PRIORITY(IRQ_I2C2); break; + #endif + #if defined(__MK66FX1M0__) // 3.6 + case 3: irqPriority = NVIC_GET_PRIORITY(IRQ_I2C3); break; + #endif + default: irqPriority = NVIC_GET_PRIORITY(IRQ_I2C0); break; + } + if(currPriority <= irqPriority) + { + if(currPriority < 16) + forceImm = 1; // current priority cannot be surpassed, force Immediate mode + else + { + switch(bus) + { + case 0: NVIC_SET_PRIORITY(IRQ_I2C0, currPriority-16); break; + #if defined(__MKL26Z64__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.1/3.2/3.5/3.6 + case 1: NVIC_SET_PRIORITY(IRQ_I2C1, currPriority-16); break; + #endif + #if defined(__MK64FX512__) || defined(__MK66FX1M0__) // 3.5/3.6 + case 2: NVIC_SET_PRIORITY(IRQ_I2C2, currPriority-16); break; + #endif + #if defined(__MK66FX1M0__) // 3.6 + case 3: NVIC_SET_PRIORITY(IRQ_I2C3, currPriority-16); break; + #endif + default: NVIC_SET_PRIORITY(IRQ_I2C0, currPriority-16); break; + } + } + } + } + return 1; +} + + +// ------------------------------------------------------------------------------------------------------ +// Reset Bus - toggles SCL until SDA line is released (9 clocks max). This is used to correct +// a hung bus in which a Slave device missed some clocks and remains stuck outputting +// a low signal on SDA (thereby preventing START/STOP signaling). +// return: none +// +void i2c_t3::resetBus_(struct i2cStruct* i2c, uint8_t bus) +{ + uint8_t scl=0, sda=0, count=0; + + switch(i2c->currentPins) + { + case I2C_PINS_16_17: sda = 17; scl = 16; break; + case I2C_PINS_18_19: sda = 18; scl = 19; break; + #if defined(__MK64FX512__) || defined(__MK66FX1M0__) // 3.5/3.6 + case I2C_PINS_7_8: sda = 8; scl = 7; break; + case I2C_PINS_33_34: sda = 34; scl = 33; break; + case I2C_PINS_47_48: sda = 48; scl = 47; break; + #endif + #if defined(__MKL26Z64__) // LC + case I2C_PINS_22_23: sda = 23; scl = 22; break; + #endif + #if defined(__MK20DX256__) // 3.1/3.2 + case I2C_PINS_29_30: sda = 30; scl = 29; break; + case I2C_PINS_26_31: sda = 31; scl = 26; break; + #endif + #if defined(__MK64FX512__) || defined(__MK66FX1M0__) // 3.5/3.6 + case I2C_PINS_37_38: sda = 38; scl = 37; break; + case I2C_PINS_3_4: sda = 4; scl = 3; break; + #endif + #if defined(__MK66FX1M0__) // 3.6 + case I2C_PINS_56_57: sda = 56; scl = 57; break; + #endif + } + if(sda && scl) + { + // change pin mux to digital I/O + pinMode(sda,((i2c->currentPullup == I2C_PULLUP_EXT) ? INPUT : INPUT_PULLUP)); + digitalWrite(scl,HIGH); + pinMode(scl,OUTPUT); + + while(digitalRead(sda) == 0 && count++ < 10) + { + digitalWrite(scl,LOW); + delayMicroseconds(5); // 10us period == 100kHz + digitalWrite(scl,HIGH); + delayMicroseconds(5); + } + + // reconfigure pins for I2C + pinConfigure_(i2c, bus, i2c->currentPins, i2c->currentPullup, 0); + + // reset config and status + if(*(i2c->S) & 0x7F) // reset config if any residual status bits are set + { + *(i2c->C1) = 0x00; // disable I2C, intr disabled + delayMicroseconds(5); + *(i2c->C1) = I2C_C1_IICEN; // enable I2C, intr disabled, Rx mode + delayMicroseconds(5); + } + i2c->currentStatus = I2C_WAITING; + } +} + + +// ------------------------------------------------------------------------------------------------------ +// Setup Master Transmit - initialize Tx buffer for transmit to slave at address +// return: none +// parameters: +// address = target 7bit slave address +// +void i2c_t3::beginTransmission(uint8_t address) +{ + i2c->txBuffer[0] = (address << 1); // store target addr + i2c->txBufferLength = 1; + clearWriteError(); // clear any previous write error + i2c->currentStatus = I2C_WAITING; // reset status +} + + +// ------------------------------------------------------------------------------------------------------ +// Master Transmit - blocking routine with timeout, transmits Tx buffer to slave. i2c_stop parameter can be used +// to indicate if command should end with a STOP(I2C_STOP) or not (I2C_NOSTOP). +// return: 0=success, 1=data too long, 2=recv addr NACK, 3=recv data NACK, 4=other error +// parameters: +// i2c_stop = I2C_NOSTOP, I2C_STOP +// timeout = timeout in microseconds +// +uint8_t i2c_t3::endTransmission(struct i2cStruct* i2c, uint8_t bus, i2c_stop sendStop, uint32_t timeout) +{ + sendTransmission_(i2c, bus, sendStop, timeout); + + // wait for completion or timeout + finish_(i2c, bus, timeout); + + return getError(); +} + + +// ------------------------------------------------------------------------------------------------------ +// Send Master Transmit - non-blocking routine, starts transmit of Tx buffer to slave. i2c_stop parameter can be +// used to indicate if command should end with a STOP (I2C_STOP) or not (I2C_NOSTOP). Use +// done() or finish() to determine completion and status() to determine success/fail. +// return: none +// parameters: +// i2c_stop = I2C_NOSTOP, I2C_STOP +// timeout = timeout in microseconds (only used for Immediate operation) +// +void i2c_t3::sendTransmission_(struct i2cStruct* i2c, uint8_t bus, i2c_stop sendStop, uint32_t timeout) +{ + uint8_t status, forceImm=0; + size_t idx; + + // exit immediately if sending 0 bytes + if(i2c->txBufferLength == 0) return; + + // update timeout + timeout = (timeout == 0) ? i2c->defTimeout : timeout; + + // clear the status flags + #if defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.5/3.6 + *(i2c->FLT) |= I2C_FLT_STOPF | I2C_FLT_STARTF; // clear STOP/START intr + *(i2c->FLT) &= ~I2C_FLT_SSIE; // disable STOP/START intr (not used in Master mode) + #endif + *(i2c->S) = I2C_S_IICIF | I2C_S_ARBL; // clear intr, arbl + + // try to take control of the bus + if(!acquireBus_(i2c, bus, timeout, forceImm)) return; + + // + // Immediate mode - blocking + // + if(i2c->opMode == I2C_OP_MODE_IMM || forceImm) + { + elapsedMicros deltaT; + i2c->currentStatus = I2C_SENDING; + i2c->currentStop = sendStop; + + for(idx=0; idx < i2c->txBufferLength && (timeout == 0 || deltaT < timeout); idx++) + { + // send data, wait for done + *(i2c->D) = i2c->txBuffer[idx]; + i2c_wait_(i2c); + status = *(i2c->S); + + // check arbitration + if(status & I2C_S_ARBL) + { + i2c->currentStatus = I2C_ARB_LOST; + *(i2c->S) = I2C_S_ARBL; // clear arbl flag + // TODO: this is clearly not right, after ARBL it should drop into IMM slave mode if IAAS=1 + // Right now Rx message would be ignored regardless of IAAS + *(i2c->C1) = I2C_C1_IICEN; // change to Rx mode, intr disabled (does this send STOP if ARBL flagged?) + return; + } + // check if slave ACK'd + else if(status & I2C_S_RXAK) + { + if(idx == 0) + i2c->currentStatus = I2C_ADDR_NAK; // NAK on Addr + else + i2c->currentStatus = I2C_DATA_NAK; // NAK on Data + *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled + return; + } + } + + // Set final status + if(idx < i2c->txBufferLength) + i2c->currentStatus = I2C_TIMEOUT; // Tx incomplete, mark as timeout + else + i2c->currentStatus = I2C_WAITING; // Tx complete, change to waiting state + + // send STOP if configured + if(i2c->currentStop == I2C_STOP) + *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled + else + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX; // no STOP, stay in Tx mode, intr disabled + } + // + // ISR/DMA mode - non-blocking + // + else if(i2c->opMode == I2C_OP_MODE_ISR || i2c->opMode == I2C_OP_MODE_DMA) + { + // send target addr and enable interrupts + i2c->currentStatus = I2C_SENDING; + i2c->currentStop = sendStop; + i2c->txBufferIndex = 0; + if(i2c->opMode == I2C_OP_MODE_DMA && i2c->txBufferLength >= 5) // limit transfers less than 5 bytes to ISR method + { + // init DMA, let the hack begin + i2c->activeDMA = I2C_DMA_ADDR; + i2c->DMA->sourceBuffer(&i2c->txBuffer[2],i2c->txBufferLength-3); // DMA sends all except first/second/last bytes + i2c->DMA->destination(*(i2c->D)); + } + // start ISR + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TX; // enable intr + *(i2c->D) = i2c->txBuffer[0]; // writing first data byte will start ISR + } +} + + +// ------------------------------------------------------------------------------------------------------ +// Master Receive - blocking routine with timeout, requests length bytes from slave at address. Receive data will +// be placed in the Rx buffer. i2c_stop parameter can be used to indicate if command should end +// with a STOP (I2C_STOP) or not (I2C_NOSTOP). +// return: #bytes received = success, 0=fail (0 length request, NAK, timeout, or bus error) +// parameters: +// address = target 7bit slave address +// length = number of bytes requested +// i2c_stop = I2C_NOSTOP, I2C_STOP +// timeout = timeout in microseconds +// +size_t i2c_t3::requestFrom_(struct i2cStruct* i2c, uint8_t bus, uint8_t addr, size_t len, i2c_stop sendStop, uint32_t timeout) +{ + // exit immediately if request for 0 bytes + if(len == 0) return 0; + + sendRequest_(i2c, bus, addr, len, sendStop, timeout); + + // wait for completion or timeout + if(finish_(i2c, bus, timeout)) + return i2c->rxBufferLength; + else + return 0; // NAK, timeout or bus error +} + + +// ------------------------------------------------------------------------------------------------------ +// Start Master Receive - non-blocking routine, starts request for length bytes from slave at address. Receive +// data will be placed in the Rx buffer. i2c_stop parameter can be used to indicate if +// command should end with a STOP (I2C_STOP) or not (I2C_NOSTOP). Use done() or finish() +// to determine completion and status() to determine success/fail. +// return: none +// parameters: +// address = target 7bit slave address +// length = number of bytes requested +// i2c_stop = I2C_NOSTOP, I2C_STOP +// timeout = timeout in microseconds (only used for Immediate operation) +// +void i2c_t3::sendRequest_(struct i2cStruct* i2c, uint8_t bus, uint8_t addr, size_t len, i2c_stop sendStop, uint32_t timeout) +{ + uint8_t status, data, chkTimeout=0, forceImm=0; + + // exit immediately if request for 0 bytes or request too large + if(len == 0) return; + if(len > I2C_RX_BUFFER_LENGTH) { i2c->currentStatus=I2C_BUF_OVF; return; } + + i2c->reqCount = len; // store request length + i2c->rxBufferIndex = 0; // reset buffer + i2c->rxBufferLength = 0; + timeout = (timeout == 0) ? i2c->defTimeout : timeout; + + // clear the status flags + #if defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.5/3.6 + *(i2c->FLT) |= I2C_FLT_STOPF | I2C_FLT_STARTF; // clear STOP/START intr + *(i2c->FLT) &= ~I2C_FLT_SSIE; // disable STOP/START intr (not used in Master mode) + #endif + *(i2c->S) = I2C_S_IICIF | I2C_S_ARBL; // clear intr, arbl + + // try to take control of the bus + if(!acquireBus_(i2c, bus, timeout, forceImm)) return; + + // + // Immediate mode - blocking + // + if(i2c->opMode == I2C_OP_MODE_IMM || forceImm) + { + elapsedMicros deltaT; + i2c->currentStatus = I2C_SEND_ADDR; + i2c->currentStop = sendStop; + + // Send target address + *(i2c->D) = (addr << 1) | 1; // address + READ + i2c_wait_(i2c); + status = *(i2c->S); + + // check arbitration + if(status & I2C_S_ARBL) + { + i2c->currentStatus = I2C_ARB_LOST; + *(i2c->S) = I2C_S_ARBL; // clear arbl flag + // TODO: this is clearly not right, after ARBL it should drop into IMM slave mode if IAAS=1 + // Right now Rx message would be ignored regardless of IAAS + *(i2c->C1) = I2C_C1_IICEN; // change to Rx mode, intr disabled (does this send STOP if ARBL flagged?) + return; + } + // check if slave ACK'd + else if(status & I2C_S_RXAK) + { + i2c->currentStatus = I2C_ADDR_NAK; // NAK on Addr + *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled + return; + } + else + { + // Slave addr ACK, change to Rx mode + i2c->currentStatus = I2C_RECEIVING; + if(i2c->reqCount == 1) + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TXAK; // no STOP, Rx, NAK on recv + else + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST; // no STOP, change to Rx + data = *(i2c->D); // dummy read + + // Master receive loop + while(i2c->rxBufferLength < i2c->reqCount && i2c->currentStatus == I2C_RECEIVING) + { + i2c_wait_(i2c); + chkTimeout = (timeout != 0 && deltaT >= timeout); + // check if 2nd to last byte or timeout + if((i2c->rxBufferLength+2) == i2c->reqCount || (chkTimeout && !i2c->timeoutRxNAK)) + { + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TXAK; // no STOP, Rx, NAK on recv + } + // if last byte or timeout send STOP + if((i2c->rxBufferLength+1) >= i2c->reqCount || (chkTimeout && i2c->timeoutRxNAK)) + { + i2c->timeoutRxNAK = 0; // clear flag + // change to Tx mode + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX; + // grab last data + data = *(i2c->D); + i2c->rxBuffer[i2c->rxBufferLength++] = data; + if(chkTimeout) + i2c->currentStatus = I2C_TIMEOUT; // Rx incomplete, mark as timeout + else + i2c->currentStatus = I2C_WAITING; // Rx complete, change to waiting state + if(i2c->currentStop == I2C_STOP) // NAK then STOP + { + delayMicroseconds(1); // empirical patch, lets things settle before issuing STOP + *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled + } + // else NAK no STOP + } + else + { + // grab next data, not last byte, will ACK + data = *(i2c->D); + i2c->rxBuffer[i2c->rxBufferLength++] = data; + } + if(chkTimeout) i2c->timeoutRxNAK = 1; // set flag to indicate NAK sent + } + } + } + // + // ISR/DMA mode - non-blocking + // + else if(i2c->opMode == I2C_OP_MODE_ISR || i2c->opMode == I2C_OP_MODE_DMA) + { + // send 1st data and enable interrupts + i2c->currentStatus = I2C_SEND_ADDR; + i2c->currentStop = sendStop; + if(i2c->opMode == I2C_OP_MODE_DMA && i2c->reqCount >= 5) // limit transfers less than 5 bytes to ISR method + { + // init DMA, let the hack begin + i2c->activeDMA = I2C_DMA_ADDR; + i2c->DMA->source(*(i2c->D)); + i2c->DMA->destinationBuffer(&i2c->rxBuffer[0],i2c->reqCount-1); // DMA gets all except last byte + } + // start ISR + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TX; // enable intr + *(i2c->D) = (addr << 1) | 1; // address + READ + } +} + + +// ------------------------------------------------------------------------------------------------------ +// Get Wire Error - returns "Wire" error code from a failed Tx/Rx command +// return: 0=success, 1=data too long, 2=recv addr NACK, 3=recv data NACK, 4=other error (timeout, arb lost) +// +uint8_t i2c_t3::getError(void) +{ + // convert status to Arduino return values (give these a higher priority than buf overflow error) + switch(i2c->currentStatus) + { + case I2C_BUF_OVF: return 1; + case I2C_ADDR_NAK: return 2; + case I2C_DATA_NAK: return 3; + case I2C_ARB_LOST: return 4; + case I2C_TIMEOUT: return 4; + default: break; + } + if(getWriteError()) return 1; // if write_error was set then flag as buffer overflow + return 0; // no errors +} + + +// ------------------------------------------------------------------------------------------------------ +// Done Check - returns simple complete/not-complete value to indicate I2C status +// return: 1=Tx/Rx complete (with or without errors), 0=still running +// +uint8_t i2c_t3::done_(struct i2cStruct* i2c) +{ + return (i2c->currentStatus==I2C_WAITING || + i2c->currentStatus==I2C_ADDR_NAK || + i2c->currentStatus==I2C_DATA_NAK || + i2c->currentStatus==I2C_ARB_LOST || + i2c->currentStatus==I2C_TIMEOUT || + i2c->currentStatus==I2C_BUF_OVF); +} + + +// ------------------------------------------------------------------------------------------------------ +// Finish - blocking routine with timeout, loops until Tx/Rx is complete or timeout occurs +// return: 1=success (Tx or Rx completed, no error), 0=fail (NAK, timeout or Arb Lost) +// parameters: +// timeout = timeout in microseconds +// +uint8_t i2c_t3::finish_(struct i2cStruct* i2c, uint8_t bus, uint32_t timeout) +{ + elapsedMicros deltaT; + + // update timeout + timeout = (timeout == 0) ? i2c->defTimeout : timeout; + + // wait for completion or timeout + deltaT = 0; + while(!done_(i2c) && (timeout == 0 || deltaT < timeout)); + + // DMA mode and timeout + if(timeout != 0 && deltaT >= timeout && i2c->opMode == I2C_OP_MODE_DMA && i2c->activeDMA != I2C_DMA_OFF) + { + // If DMA mode times out, then wait for transfer to end then mark it as timeout. + // This is done this way because abruptly ending the DMA seems to cause + // the I2C_S_BUSY flag to get stuck, and I cannot find a reliable way to clear it. + while(!done_(i2c)); + i2c->currentStatus = I2C_TIMEOUT; + } + + // check exit status, if still Tx/Rx then timeout occurred + if(i2c->currentStatus == I2C_SENDING || + i2c->currentStatus == I2C_SEND_ADDR || + i2c->currentStatus == I2C_RECEIVING) + i2c->currentStatus = I2C_TIMEOUT; // set to timeout state + + // delay to allow bus to settle (eg. allow STOP to complete and be recognized, + // not just on our side, but on slave side also) + delayMicroseconds(4); + if(i2c->currentStatus == I2C_WAITING) return 1; + return 0; +} + + +// ------------------------------------------------------------------------------------------------------ +// Write - write data to Tx buffer +// return: #bytes written = success, 0=fail +// parameters: +// data = data byte +// +size_t i2c_t3::write(uint8_t data) +{ + if(i2c->txBufferLength < I2C_TX_BUFFER_LENGTH) + { + i2c->txBuffer[i2c->txBufferLength++] = data; + return 1; + } + setWriteError(); + return 0; +} + + +// ------------------------------------------------------------------------------------------------------ +// Write Array - write length number of bytes from data array to Tx buffer +// return: #bytes written = success, 0=fail +// parameters: +// data = pointer to uint8_t array of data +// length = number of bytes to write +// +size_t i2c_t3::write(const uint8_t* data, size_t quantity) +{ + if(i2c->txBufferLength < I2C_TX_BUFFER_LENGTH) + { + size_t avail = I2C_TX_BUFFER_LENGTH - i2c->txBufferLength; + uint8_t* dest = i2c->txBuffer + i2c->txBufferLength; + + if(quantity > avail) + { + quantity = avail; // truncate to space avail if needed + setWriteError(); + } + for(size_t count=quantity; count; count--) + *dest++ = *data++; + i2c->txBufferLength += quantity; + return quantity; + } + setWriteError(); + return 0; +} + + +// ------------------------------------------------------------------------------------------------------ +// Read - returns next data byte (signed int) from Rx buffer +// return: data, -1 if buffer empty +// +int i2c_t3::read_(struct i2cStruct* i2c) +{ + if(i2c->rxBufferIndex >= i2c->rxBufferLength) return -1; + return i2c->rxBuffer[i2c->rxBufferIndex++]; +} + + +// ------------------------------------------------------------------------------------------------------ +// Peek - returns next data byte (signed int) from Rx buffer without removing it from Rx buffer +// return: data, -1 if buffer empty +// +int i2c_t3::peek_(struct i2cStruct* i2c) +{ + if(i2c->rxBufferIndex >= i2c->rxBufferLength) return -1; + return i2c->rxBuffer[i2c->rxBufferIndex]; +} + + +// ------------------------------------------------------------------------------------------------------ +// Read Byte - returns next data byte (uint8_t) from Rx buffer +// return: data, 0 if buffer empty +// +uint8_t i2c_t3::readByte_(struct i2cStruct* i2c) +{ + if(i2c->rxBufferIndex >= i2c->rxBufferLength) return 0; + return i2c->rxBuffer[i2c->rxBufferIndex++]; +} + + +// ------------------------------------------------------------------------------------------------------ +// Peek Byte - returns next data byte (uint8_t) from Rx buffer without removing it from Rx buffer +// return: data, 0 if buffer empty +// +uint8_t i2c_t3::peekByte_(struct i2cStruct* i2c) +{ + if(i2c->rxBufferIndex >= i2c->rxBufferLength) return 0; + return i2c->rxBuffer[i2c->rxBufferIndex]; +} + + +// ====================================================================================================== +// ------------------------------------------------------------------------------------------------------ +// I2C Interrupt Service Routine +// ------------------------------------------------------------------------------------------------------ +// ====================================================================================================== + + +void i2c0_isr(void) // I2C0 ISR +{ + I2C0_INTR_FLAG_ON; + i2c_isr_handler(&(i2c_t3::i2cData[0]),0); + I2C0_INTR_FLAG_OFF; +} +#if I2C_BUS_NUM >= 2 + void i2c1_isr(void) // I2C1 ISR + { + I2C1_INTR_FLAG_ON; + i2c_isr_handler(&(i2c_t3::i2cData[1]),1); + I2C1_INTR_FLAG_OFF; + } +#endif +#if I2C_BUS_NUM >= 3 + void i2c2_isr(void) // I2C2 ISR + { + I2C2_INTR_FLAG_ON; + i2c_isr_handler(&(i2c_t3::i2cData[2]),2); + I2C2_INTR_FLAG_OFF; + } +#endif +#if I2C_BUS_NUM >= 4 + void i2c3_isr(void) // I2C3 ISR + { + I2C3_INTR_FLAG_ON; + i2c_isr_handler(&(i2c_t3::i2cData[3]),3); + I2C3_INTR_FLAG_OFF; + } +#endif + +// +// I2C ISR base handler +// +void i2c_isr_handler(struct i2cStruct* i2c, uint8_t bus) +{ + uint8_t status, c1, data; + + status = *(i2c->S); + c1 = *(i2c->C1); + #if defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.5/3.6 + uint8_t flt = *(i2c->FLT); // store flags + #endif + + if(c1 & I2C_C1_MST) + { + // + // Master Mode + // + if(c1 & I2C_C1_TX) + { + if(i2c->activeDMA == I2C_DMA_BULK || i2c->activeDMA == I2C_DMA_LAST) + { + if(i2c->DMA->complete() && i2c->activeDMA == I2C_DMA_BULK) + { + // clear DMA interrupt, final byte should trigger another ISR + i2c->DMA->clearInterrupt(); + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TX; // intr en, Tx mode, DMA disabled + // DMA says complete at the beginning of its last byte, need to + // wait until end of its last byte to re-engage ISR + i2c->activeDMA = I2C_DMA_LAST; + } + else if(i2c->activeDMA == I2C_DMA_LAST) + { + // wait for TCF + while(!(*(i2c->S) & I2C_S_TCF)); + // clear DMA, only do this after TCF + i2c->DMA->clearComplete(); + // re-engage ISR for last byte + i2c->activeDMA = I2C_DMA_OFF; + i2c->txBufferIndex = i2c->txBufferLength-1; + *(i2c->D) = i2c->txBuffer[i2c->txBufferIndex]; + } + else if(i2c->DMA->error()) + { + i2c->DMA->clearInterrupt(); + i2c->DMA->clearError(); + i2c->activeDMA = I2C_DMA_OFF; + // check arbitration + if(status & I2C_S_ARBL) + { + // Arbitration Lost + i2c->currentStatus = I2C_ARB_LOST; + *(i2c->S) = I2C_S_ARBL | I2C_S_IICIF; // clear arbl flag and intr + *(i2c->C1) = I2C_C1_IICEN; // change to Rx mode, intr disabled (does this send STOP if ARBL flagged?), DMA disabled + i2c->txBufferIndex = 0; // reset Tx buffer index to prepare for resend + return; // TODO does this need to check IAAS and drop to Slave Rx? if so set Rx + dummy read. not sure if this would work for DMA + } + } + *(i2c->S) = I2C_S_IICIF; // clear intr + return; + } // end DMA Tx + else + { + // Continue Master Transmit + // check if Master Tx or Rx + if(i2c->currentStatus == I2C_SENDING) + { + // check arbitration + if(status & I2C_S_ARBL) + { + // Arbitration Lost + i2c->activeDMA = I2C_DMA_OFF; // clear pending DMA (if happens on address byte) + i2c->currentStatus = I2C_ARB_LOST; + *(i2c->S) = I2C_S_ARBL | I2C_S_IICIF; // clear arbl flag and intr + *(i2c->C1) = I2C_C1_IICEN; // change to Rx mode, intr disabled (does this send STOP if ARBL flagged?) + i2c->txBufferIndex = 0; // reset Tx buffer index to prepare for resend + return; // does this need to check IAAS and drop to Slave Rx? if so set Rx + dummy read. + } + // check if slave ACK'd + else if(status & I2C_S_RXAK) + { + i2c->activeDMA = I2C_DMA_OFF; // clear pending DMA (if happens on address byte) + if(i2c->txBufferIndex == 0) + i2c->currentStatus = I2C_ADDR_NAK; // NAK on Addr + else + i2c->currentStatus = I2C_DATA_NAK; // NAK on Data + // send STOP, change to Rx mode, intr disabled + // note: Slave NAK is an error, so send STOP regardless of setting + *(i2c->C1) = I2C_C1_IICEN; + } + else + { + // check if last byte transmitted + if(++i2c->txBufferIndex >= i2c->txBufferLength) + { + // Tx complete, change to waiting state + i2c->currentStatus = I2C_WAITING; + // send STOP if configured + if(i2c->currentStop == I2C_STOP) + *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled + else + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX; // no STOP, stay in Tx mode, intr disabled + } + else if(i2c->activeDMA == I2C_DMA_ADDR) + { + // Start DMA + i2c->activeDMA = I2C_DMA_BULK; + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TX | I2C_C1_DMAEN; // intr en, Tx mode, DMA en + i2c->DMA->enable(); + *(i2c->D) = i2c->txBuffer[1]; // DMA will start on next request + } + else + { + // ISR transmit next byte + *(i2c->D) = i2c->txBuffer[i2c->txBufferIndex]; + } + } + *(i2c->S) = I2C_S_IICIF; // clear intr + return; + } + else if(i2c->currentStatus == I2C_SEND_ADDR) + { + // Master Receive, addr sent + if(status & I2C_S_ARBL) + { + // Arbitration Lost + i2c->currentStatus = I2C_ARB_LOST; + *(i2c->S) = I2C_S_ARBL | I2C_S_IICIF; // clear arbl flag and intr + *(i2c->C1) = I2C_C1_IICEN; // change to Rx mode, intr disabled (does this send STOP if ARBL flagged?) + return; // TODO does this need to check IAAS and drop to Slave Rx? if so set Rx + dummy read. not sure if this would work for DMA + } + else if(status & I2C_S_RXAK) + { + // Slave addr NAK + i2c->currentStatus = I2C_ADDR_NAK; // NAK on Addr + // send STOP, change to Rx mode, intr disabled + *(i2c->C1) = I2C_C1_IICEN; + } + else if(i2c->activeDMA == I2C_DMA_ADDR) + { + // Start DMA + i2c->activeDMA = I2C_DMA_BULK; + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_DMAEN; // intr en, no STOP, change to Rx, DMA en + i2c->DMA->enable(); + data = *(i2c->D); // dummy read + } + else + { + // Slave addr ACK, change to Rx mode + i2c->currentStatus = I2C_RECEIVING; + if(i2c->reqCount == 1) + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TXAK; // no STOP, Rx, NAK on recv + else + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST; // no STOP, change to Rx + data = *(i2c->D); // dummy read + } + *(i2c->S) = I2C_S_IICIF; // clear intr + return; + } + else if(i2c->currentStatus == I2C_TIMEOUT) + { + // send STOP if configured + if(i2c->currentStop == I2C_STOP) + { + // send STOP, change to Rx mode, intr disabled + *(i2c->C1) = I2C_C1_IICEN; + } + else + { + // no STOP, stay in Tx mode, intr disabled + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX; + } + *(i2c->S) = I2C_S_IICIF; // clear intr + return; + } + else + { + // Should not be in Tx mode if not sending + // send STOP, change to Rx mode, intr disabled + *(i2c->C1) = I2C_C1_IICEN; + *(i2c->S) = I2C_S_IICIF; // clear intr + return; + } + } // end ISR Tx + } + else + { + // Continue Master Receive + // + if(i2c->activeDMA == I2C_DMA_BULK || i2c->activeDMA == I2C_DMA_LAST) + { + if(i2c->DMA->complete() && i2c->activeDMA == I2C_DMA_BULK) // 2nd to last byte + { + // clear DMA interrupt, final byte should trigger another ISR + i2c->DMA->clearInterrupt(); + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TXAK; // intr en, Rx mode, DMA disabled, NAK on recv + i2c->activeDMA = I2C_DMA_LAST; + } + else if(i2c->activeDMA == I2C_DMA_LAST) // last byte + { + // clear DMA + i2c->DMA->clearComplete(); + i2c->activeDMA = I2C_DMA_OFF; + if(i2c->currentStatus != I2C_TIMEOUT) + i2c->currentStatus = I2C_WAITING; // Rx complete, change to waiting state + // change to Tx mode + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX; + // grab last data + i2c->rxBufferLength = i2c->reqCount-1; + i2c->rxBuffer[i2c->rxBufferLength++] = *(i2c->D); + if(i2c->currentStop == I2C_STOP) // NAK then STOP + { + delayMicroseconds(1); // empirical patch, lets things settle before issuing STOP + *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled + } + // else NAK no STOP + } + else if(i2c->DMA->error()) // not sure what would cause this... + { + i2c->DMA->clearError(); + i2c->DMA->clearInterrupt(); + i2c->activeDMA = I2C_DMA_OFF; + i2c->currentStatus = I2C_WAITING; + *(i2c->C1) = I2C_C1_IICEN; // change to Rx mode, intr disabled, DMA disabled + } + *(i2c->S) = I2C_S_IICIF; // clear intr + return; + } + else + { + // check if 2nd to last byte or timeout + if((i2c->rxBufferLength+2) == i2c->reqCount || (i2c->currentStatus == I2C_TIMEOUT && !i2c->timeoutRxNAK)) + { + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TXAK; // no STOP, Rx, NAK on recv + } + // if last byte or timeout send STOP + if((i2c->rxBufferLength+1) >= i2c->reqCount || (i2c->currentStatus == I2C_TIMEOUT && i2c->timeoutRxNAK)) + { + i2c->timeoutRxNAK = 0; // clear flag + if(i2c->currentStatus != I2C_TIMEOUT) + i2c->currentStatus = I2C_WAITING; // Rx complete, change to waiting state + // change to Tx mode + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX; + // grab last data + i2c->rxBuffer[i2c->rxBufferLength++] = *(i2c->D); + if(i2c->currentStop == I2C_STOP) // NAK then STOP + { + delayMicroseconds(1); // empirical patch, lets things settle before issuing STOP + *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled + } + // else NAK no STOP + } + else + { + // grab next data, not last byte, will ACK + i2c->rxBuffer[i2c->rxBufferLength++] = *(i2c->D); + } + if(i2c->currentStatus == I2C_TIMEOUT && !i2c->timeoutRxNAK) + i2c->timeoutRxNAK = 1; // set flag to indicate NAK sent + *(i2c->S) = I2C_S_IICIF; // clear intr + return; + } + } + } + else + { + // + // Slave Mode + // + if(status & I2C_S_ARBL) + { + // Arbitration Lost + *(i2c->S) = I2C_S_ARBL; // clear arbl flag + if(!(status & I2C_S_IAAS)) + { + #if defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.5/3.6 + *(i2c->FLT) = flt; // clear STOP/START intr + #endif + *(i2c->S) = I2C_S_IICIF; // clear intr + return; + } + } + if(status & I2C_S_IAAS) + { + // If in Slave Rx already, then RepSTART occured, run callback + if(i2c->currentStatus == I2C_SLAVE_RX && i2c->user_onReceive != nullptr) + { + i2c->rxBufferIndex = 0; + i2c->user_onReceive(i2c->rxBufferLength); + } + // Is Addressed As Slave + if(status & I2C_S_SRW) + { + // Addressed Slave Transmit + // + i2c->currentStatus = I2C_SLAVE_TX; + i2c->txBufferLength = 0; + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_TX; + i2c->rxAddr = (*(i2c->D) >> 1); // read to get target addr + if(i2c->user_onRequest != nullptr) + i2c->user_onRequest(); // load Tx buffer with data + if(i2c->txBufferLength == 0) + i2c->txBuffer[0] = 0; // send 0's if buffer empty + *(i2c->D) = i2c->txBuffer[0]; // send first data + i2c->txBufferIndex = 1; + } + else + { + // Addressed Slave Receive + // + // setup SDA-rising ISR - required for STOP detection in Slave Rx mode for 3.0/3.1/3.2 + #if defined(__MK20DX128__) || defined(__MK20DX256__) // 3.0/3.1/3.2 + i2c->irqCount = 0; + if(i2c->currentPins == I2C_PINS_18_19) + attachInterrupt(18, i2c_t3::sda0_rising_isr, RISING); + else if(i2c->currentPins == I2C_PINS_16_17) + attachInterrupt(17, i2c_t3::sda0_rising_isr, RISING); + #if I2C_BUS_NUM >= 2 + else if(i2c->currentPins == I2C_PINS_29_30) + attachInterrupt(30, i2c_t3::sda1_rising_isr, RISING); + else if(i2c->currentPins == I2C_PINS_26_31) + attachInterrupt(31, i2c_t3::sda1_rising_isr, RISING); + #endif + #elif defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) + *(i2c->FLT) |= I2C_FLT_SSIE; // enable START/STOP intr for LC/3.5/3.6 + #endif + i2c->currentStatus = I2C_SLAVE_RX; + i2c->rxBufferLength = 0; + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE; + i2c->rxAddr = (*(i2c->D) >> 1); // read to get target addr + } + #if defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.5/3.6 + *(i2c->FLT) = flt; // clear STOP/START intr + #endif + *(i2c->S) = I2C_S_IICIF; // clear intr + return; + } + if(c1 & I2C_C1_TX) + { + // Continue Slave Transmit + if((status & I2C_S_RXAK) == 0) + { + // Master ACK'd previous byte + if(i2c->txBufferIndex < i2c->txBufferLength) + data = i2c->txBuffer[i2c->txBufferIndex++]; + else + data = 0; // send 0's if buffer empty + *(i2c->D) = data; + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_TX; + } + else + { + // Master did not ACK previous byte + *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE; // switch to Rx mode + data = *(i2c->D); // dummy read + i2c->currentStatus = I2C_WAITING; + } + } + else if(i2c->currentStatus == I2C_SLAVE_RX) + { + #if defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.5/3.6 + if(flt & I2C_FLT_STOPF) // STOP detected, run callback + { + // LC (MKL26) appears to have the same I2C_FLT reg definition as 3.6 (K66) + // There is both STOPF and STARTF and they are both enabled via SSIE, and they must both + // be cleared in order to work + i2c->currentStatus = I2C_WAITING; + if(i2c->user_onReceive != nullptr) + { + i2c->rxBufferIndex = 0; + i2c->user_onReceive(i2c->rxBufferLength); + } + *(i2c->FLT) = flt; // clear STOP/START intr + *(i2c->S) = I2C_S_IICIF; // clear intr + return; + } + #endif + // Continue Slave Receive + // + // setup SDA-rising ISR - required for STOP detection in Slave Rx mode for 3.0/3.1/3.2 + #if defined(__MK20DX128__) || defined(__MK20DX256__) // 3.0/3.1 + i2c->irqCount = 0; + if(i2c->currentPins == I2C_PINS_18_19) + attachInterrupt(18, i2c_t3::sda0_rising_isr, RISING); + else if(i2c->currentPins == I2C_PINS_16_17) + attachInterrupt(17, i2c_t3::sda0_rising_isr, RISING); + #if I2C_BUS_NUM >= 2 + else if(i2c->currentPins == I2C_PINS_29_30) + attachInterrupt(30, i2c_t3::sda1_rising_isr, RISING); + else if(i2c->currentPins == I2C_PINS_26_31) + attachInterrupt(31, i2c_t3::sda1_rising_isr, RISING); + #endif + #endif + data = *(i2c->D); + if(i2c->rxBufferLength < I2C_RX_BUFFER_LENGTH) + i2c->rxBuffer[i2c->rxBufferLength++] = data; + } + #if defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.5/3.6 + *(i2c->FLT) = flt; // clear STOP/START intr + #endif + *(i2c->S) = I2C_S_IICIF; // clear intr + } +} + +#if defined(__MK20DX128__) || defined(__MK20DX256__) // 3.0/3.1/3.2 +// ------------------------------------------------------------------------------------------------------ +// SDA-Rising Interrupt Service Routine - 3.0/3.1/3.2 only +// +// Detects the stop condition that terminates a slave receive transfer. +// + +// I2C0 SDA ISR +void i2c_t3::sda0_rising_isr(void) +{ + i2c_t3::sda_rising_isr_handler(&(i2c_t3::i2cData[0]),0); +} + +#if I2C_BUS_NUM >= 2 + // I2C1 SDA ISR + void i2c_t3::sda1_rising_isr(void) + { + i2c_t3::sda_rising_isr_handler(&(i2c_t3::i2cData[1]),1); + } +#endif + +// +// SDA ISR base handler +// +void i2c_t3::sda_rising_isr_handler(struct i2cStruct* i2c, uint8_t bus) +{ + uint8_t status = *(i2c->S); // capture status first, can change if ISR is too slow + if(!(status & I2C_S_BUSY)) + { + i2c->currentStatus = I2C_WAITING; + if(i2c->currentPins == I2C_PINS_18_19) + detachInterrupt(18); + else if(i2c->currentPins == I2C_PINS_16_17) + detachInterrupt(17); + #if I2C_BUS_NUM >= 2 + else if(i2c->currentPins == I2C_PINS_29_30) + detachInterrupt(30); + else if(i2c->currentPins == I2C_PINS_26_31) + detachInterrupt(31); + #endif + if(i2c->user_onReceive != nullptr) + { + i2c->rxBufferIndex = 0; + i2c->user_onReceive(i2c->rxBufferLength); + } + } + else + { + if(++(i2c->irqCount) >= 2 || !(i2c->currentMode == I2C_SLAVE)) + { + if(i2c->currentPins == I2C_PINS_18_19) + detachInterrupt(18); + else if(i2c->currentPins == I2C_PINS_16_17) + detachInterrupt(17); + #if I2C_BUS_NUM >= 2 + else if(i2c->currentPins == I2C_PINS_29_30) + detachInterrupt(30); + else if(i2c->currentPins == I2C_PINS_26_31) + detachInterrupt(31); + #endif + } + } +} +#endif // sda_rising_isr + +// ------------------------------------------------------------------------------------------------------ +// Instantiate +// +i2c_t3 Wire = i2c_t3(0); // I2C0 +#if I2C_BUS_NUM >= 2 + i2c_t3 Wire1 = i2c_t3(1); // I2C1 +#endif +#if I2C_BUS_NUM >= 3 + i2c_t3 Wire2 = i2c_t3(2); // I2C2 +#endif +#if I2C_BUS_NUM >= 4 + i2c_t3 Wire3 = i2c_t3(3); // I2C3 +#endif + +#endif // i2c_t3 diff --git a/Firmware_V2/src/libraries/I2C/i2c_t3.h b/Firmware_V2/src/libraries/I2C/i2c_t3.h new file mode 100644 index 0000000..d325c49 --- /dev/null +++ b/Firmware_V2/src/libraries/I2C/i2c_t3.h @@ -0,0 +1,1005 @@ +/* + ------------------------------------------------------------------------------------------------------ + i2c_t3 - I2C library for Teensy 3.x & LC + + - (v9.2) Modified 29Dec16 by Brian (nox771 at gmail.com) + - improved resetBus() function to reset C1 state (thanks hw999) + + - (v9.1) Modified 16Oct16 by Brian (nox771 at gmail.com) + - applied two fixes due to bug reports: + - removed I2C_F_DIV120 setting (120 divide-ratio) for I2C clock + - disabled I2C_AUTO_RETRY by default (setting remains but must be manually enabled) + + - (v9) Modified 01Jul16 by Brian (nox771 at gmail.com) + - Added support for Teensy 3.5/3.6: + - fully supported (Master/Slave modes, IMM/ISR/DMA operation) + - supports all available pin/bus options on Wire/Wire1/Wire2/Wire3 + - Fixed LC slave bug, whereby it was incorrectly detecting STOPs directed to other slaves + - I2C rate is now set using a much more flexible method than previously used (this is partially + motivated by increasing device count and frequencies). As a result, the fixed set of rate + enums are no longer needed (however they are currently still supported), and desired I2C + frequency can be directly specified, eg. for 400kHz, I2C_RATE_400 can be replaced by 400000. + Some setRate() functions are deprecated due to these changes. + + - (v8) Modified 02Apr15 by Brian (nox771 at gmail.com) + - added support for Teensy LC: + - fully supported (Master/Slave modes, IMM/ISR/DMA operation) + - Wire: pins 16/17 or 18/19, rate limited to I2C_RATE_1200 + - Wire1: pins 22/23, rate limited to I2C_RATE_2400 + - added timeout on acquiring bus (prevents lockup when bus cannot be acquired) + - added setDefaultTimeout() function for setting the default timeout to apply to all commands + - added resetBus() function for toggling SCL to release stuck Slave devices + - added setRate(rate) function, similar to setClock(freq), but using rate specifiers (does not + require specifying busFreq) + - added I2C_AUTO_RETRY user define + + - (v7) Modified 09Jan15 by Brian (nox771 at gmail.com) + - added support for F_BUS frequencies: 60MHz, 56MHz, 48MHz, 36MHz, 24MHz, 16MHz, 8MHz, 4MHz, 2MHz + - added new rates: I2C_RATE_1800, I2C_RATE_2800, I2C_RATE_3000 + - added new priority escalation - in cases where I2C ISR is blocked by having a lower priority than + calling function, the I2C will either adjust I2C ISR to a higher priority, + or switch to Immediate mode as needed. + - added new operating mode control - I2C can be set to operate in ISR mode, DMA mode (Master only), + or Immediate Mode (Master only) + - added new begin() functions to allow setting the initial operating mode: + - begin(i2c_mode mode, uint8_t address, i2c_pins pins, i2c_pullup pullup, i2c_rate rate, i2c_op_mode opMode) + - begin(i2c_mode mode, uint8_t address1, uint8_t address2, i2c_pins pins, i2c_pullup pullup, i2c_rate rate, i2c_op_mode opMode) + - added new functions: + - uint8_t setOpMode(i2c_op_mode opMode) - used to change operating mode on the fly (only when bus is idle) + - void sendTransmission() - non-blocking Tx with implicit I2C_STOP, added for symmetry with endTransmission() + - uint8_t setRate(uint32_t busFreq, i2c_rate rate) - used to set I2C clock dividers to get desired rate, i2c_rate argument + - uint8_t setRate(uint32_t busFreq, uint32_t i2cFreq) - used to set I2C clock dividers to get desired SCL freq, uint32_t argument + (quantized to nearest i2c_rate) + - added new Wire compatibility functions: + - void setClock(uint32_t i2cFreq) - (note: degenerate form of setRate() with busFreq == F_BUS) + - uint8_t endTransmission(uint8_t sendStop) + - uint8_t requestFrom(uint8_t addr, uint8_t len) + - uint8_t requestFrom(uint8_t addr, uint8_t len, uint8_t sendStop) + - fixed bug in Slave Range code whereby onRequest() callback occurred prior to updating rxAddr instead of after + - fixed bug in arbitration, was missing from Master Tx mode + - removed I2C1 defines (now included in kinetis.h) + - removed all debug code (eliminates rbuf dependency) + + - (v6) Modified 16Jan14 by Brian (nox771 at gmail.com) + - all new structure using dereferenced pointers instead of hardcoding. This allows functions + (including ISRs) to be reused across multiple I2C buses. Most functions moved to static, + which in turn are called by inline user functions. Added new struct (i2cData) for holding all + bus information. + - added support for Teensy 3.1 and I2C1 interface on pins 29/30 and 26/31. + - added header define (I2C_BUS_ENABLE n) to control number of enabled buses (eg. both I2C0 & I2C1 + or just I2C0). When using only I2C0 the code and ram usage will be lower. + - added interrupt flag (toggles pin high during ISR) with independent defines for I2C0 and + I2C1 (refer to header file), useful for logic analyzer trigger + + - (v5) Modified 09Jun13 by Brian (nox771 at gmail.com) + - fixed bug in ISR timeout code in which timeout condition could fail to reset in certain cases + - fixed bug in Slave mode in sda_rising_isr attach, whereby it was not getting attached on the addr byte + - moved debug routines so they are entirely defined internal to the library (no end user code req'd) + - debug routines now use IntervalTimer library + - added support for range of Slave addresses + - added getRxAddr() for Slave using addr range to determine its called address + - removed virtual keyword from all functions (is not a base class) + + - (v1-v4) Modified 26Feb13 by Brian (nox771 at gmail.com) + - Reworked begin function: + - added option for pins to use (SCL:SDA on 19:18 or 16:17 - note pin order difference) + - added option for internal pullup - as mentioned in previous code pullup is very strong, + approx 190 ohms, but is possibly useful for high speed I2C + - added option for rates - 100kHz, 200kHz, 300kHz, 400kHz, 600kHz, 800kHz, 1MHz, 1.2MHz, <-- 24/48MHz bus + 1.5MHz, 2.0MHz, 2.4MHz <-- 48MHz bus only + - Removed string.h dependency (memcpy) + - Changed Master modes to interrupt driven + - Added non-blocking Tx/Rx routines, and status/done/finish routines: + - sendTransmission() - non-blocking transmit + - sendRequest() - non-blocking receive + - status() - reports current status + - done() - indicates Tx/Rx complete (for main loop polling if I2C is running in background) + - finish() - loops until Tx/Rx complete or bus error + - Added readByte()/peekByte() for uint8_t return values (note: returns 0 instead of -1 if buf empty) + - Added fixes for Slave Rx mode - in short Slave Rx on this part is fubar + (as proof, notice the difference in the I2Cx_FLT register in the KL25 Sub-Family parts) + - the SDA-rising ISR hack can work but only detects STOP conditons. + A slave Rx followed by RepSTART won't be detected since bus remains busy. + To fix this if IAAS occurs while already in Slave Rx mode then it will + assume RepSTART occurred and trigger onReceive callback. + - Separated Tx/Rx buffer sizes for asymmetric devices (adjustable in i2c_t3.h) + - Changed Tx/Rx buffer indicies to size_t to allow for large (>256 byte) buffers + - Left debug routines in place (controlled via header defines - default is OFF). If debug is + enabled, note that it can easily overrun the Debug queue on large I2C transfers, yielding + garbage output. Adjust ringbuf size (in rbuf.h) and possibly PIT interrupt rate to adjust + data flow to Serial (note also the buffer in Serial can overflow if written too quickly). + - Added getError() function to return Wire error code + - Added pinConfigure() function for changing pins on the fly (only when bus not busy) + - Added timeouts to endTransmission(), requestFrom(), and finish() + ------------------------------------------------------------------------------------------------------ + Some code segments derived from: + TwoWire.cpp - TWI/I2C library for Wiring & Arduino + Copyright (c) 2006 Nicholas Zambetti. All right reserved. + Modified 2012 by Todd Krein (todd@krein.org) to implement repeated starts + ------------------------------------------------------------------------------------------------------ + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(I2C_T3_H) && (defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__) || \ + defined(__MK64FX512__) || defined(__MK66FX1M0__)) // 3.0/3.1-3.2/LC/3.5/3.6 +#define I2C_T3_H + +#include +#include // for size_t +#include "Arduino.h" +#include + +// TODO missing kinetis.h defs +#ifndef I2C_F_DIV52 + #define I2C_F_DIV52 ((uint8_t)0x43) + #define I2C_F_DIV60 ((uint8_t)0x45) + #define I2C_F_DIV136 ((uint8_t)0x4F) + #define I2C_F_DIV176 ((uint8_t)0x55) + #define I2C_F_DIV352 ((uint8_t)0x95) + #define I2C_FLT_SSIE ((uint8_t)0x20) // Start/Stop Interrupt Enable + #define I2C_FLT_STARTF ((uint8_t)0x10) // Start Detect Flag +#endif + + +// ====================================================================================================== +// == Start User Define Section ========================================================================= +// ====================================================================================================== + +// ------------------------------------------------------------------------------------------------------ +// I2C Bus Enable control - change to enable buses as follows. The number of buses will be limited to the +// lower of this setting or what is available on the device. +// +// Teensy Max #Buses +// ------ ---------- +// LC 2 +// 3.0 1 +// 3.1 2 +// 3.2 2 +// 3.5 3 +// 3.6 4 +// +// I2C_BUS_ENABLE 1 (enable Wire only) +// I2C_BUS_ENABLE 2 (enable Wire & Wire1) +// I2C_BUS_ENABLE 3 (enable Wire & Wire1 & Wire2) +// I2C_BUS_ENABLE 4 (enable Wire & Wire1 & Wire2 & Wire3) +// +#define I2C_BUS_ENABLE 4 + +// ------------------------------------------------------------------------------------------------------ +// Tx/Rx buffer sizes - modify these as needed. Buffers should be large enough to hold: +// Target Addr + Data payload. Default is: 1byte Addr + 258byte Data +// (this can be substantially reduced if working with sensors or small data packets) +// +#define I2C_TX_BUFFER_LENGTH 259 +#define I2C_RX_BUFFER_LENGTH 259 + +// ------------------------------------------------------------------------------------------------------ +// Interrupt flag - uncomment and set below to make the specified pin high whenever the +// I2C interrupt occurs (modify pin number as needed). This is useful as a +// trigger signal when using a logic analyzer. +// +//#define I2C0_INTR_FLAG_PIN 5 +//#define I2C1_INTR_FLAG_PIN 6 +//#define I2C2_INTR_FLAG_PIN 9 +//#define I2C3_INTR_FLAG_PIN 10 + +// ------------------------------------------------------------------------------------------------------ +// Auto retry - uncomment to make the library automatically call resetBus() if it has a timeout while +// trying to send a START (occurs at the beginning of any endTransmission() or requestFrom() +// call). This will toggle SCL to try and get a hung Slave device to release the SDA line. +// If successful then it will try again to send a START, if not then it will return a timeout +// error (same as if auto retry was not defined). +// +// Note: this is incompatible with multi-master buses, only use in single-master configurations +// +//#define I2C_AUTO_RETRY + +// ====================================================================================================== +// == End User Define Section =========================================================================== +// ====================================================================================================== + + +// ------------------------------------------------------------------------------------------------------ +// Set number of enabled buses +// +#if defined(__MK20DX128__) // 3.0 + #define I2C_BUS_NUM 1 +#elif (defined(__MK20DX256__) || defined(__MKL26Z64__)) && (I2C_BUS_ENABLE >= 2) // 3.1/3.2/LC + #define I2C_BUS_NUM 2 +#elif defined(__MK64FX512__) && (I2C_BUS_ENABLE >= 3) // 3.5 + #define I2C_BUS_NUM 3 +#elif defined(__MK66FX1M0__) && (I2C_BUS_ENABLE >= 4) // 3.6 + #define I2C_BUS_NUM 4 +#else + #define I2C_BUS_NUM I2C_BUS_ENABLE +#endif + + +// ------------------------------------------------------------------------------------------------------ +// Interrupt flag setup +// +#if defined(I2C0_INTR_FLAG_PIN) + #define I2C0_INTR_FLAG_INIT do \ + { \ + pinMode(I2C0_INTR_FLAG_PIN, OUTPUT); \ + digitalWrite(I2C0_INTR_FLAG_PIN, LOW); \ + } while(0) + + #define I2C0_INTR_FLAG_ON do {digitalWrite(I2C0_INTR_FLAG_PIN, HIGH);} while(0) + #define I2C0_INTR_FLAG_OFF do {digitalWrite(I2C0_INTR_FLAG_PIN, LOW);} while(0) +#else + #define I2C0_INTR_FLAG_INIT do{}while(0) + #define I2C0_INTR_FLAG_ON do{}while(0) + #define I2C0_INTR_FLAG_OFF do{}while(0) +#endif + +#if defined(I2C1_INTR_FLAG_PIN) + #define I2C1_INTR_FLAG_INIT do \ + { \ + pinMode(I2C1_INTR_FLAG_PIN, OUTPUT); \ + digitalWrite(I2C1_INTR_FLAG_PIN, LOW); \ + } while(0) + + #define I2C1_INTR_FLAG_ON do {digitalWrite(I2C1_INTR_FLAG_PIN, HIGH);} while(0) + #define I2C1_INTR_FLAG_OFF do {digitalWrite(I2C1_INTR_FLAG_PIN, LOW);} while(0) +#else + #define I2C1_INTR_FLAG_INIT do{}while(0) + #define I2C1_INTR_FLAG_ON do{}while(0) + #define I2C1_INTR_FLAG_OFF do{}while(0) +#endif + +#if defined(I2C2_INTR_FLAG_PIN) + #define I2C2_INTR_FLAG_INIT do \ + { \ + pinMode(I2C2_INTR_FLAG_PIN, OUTPUT); \ + digitalWrite(I2C2_INTR_FLAG_PIN, LOW); \ + } while(0) + + #define I2C2_INTR_FLAG_ON do {digitalWrite(I2C2_INTR_FLAG_PIN, HIGH);} while(0) + #define I2C2_INTR_FLAG_OFF do {digitalWrite(I2C2_INTR_FLAG_PIN, LOW);} while(0) +#else + #define I2C2_INTR_FLAG_INIT do{}while(0) + #define I2C2_INTR_FLAG_ON do{}while(0) + #define I2C2_INTR_FLAG_OFF do{}while(0) +#endif + +#if defined(I2C3_INTR_FLAG_PIN) + #define I2C3_INTR_FLAG_INIT do \ + { \ + pinMode(I2C3_INTR_FLAG_PIN, OUTPUT); \ + digitalWrite(I2C3_INTR_FLAG_PIN, LOW); \ + } while(0) + + #define I2C3_INTR_FLAG_ON do {digitalWrite(I2C3_INTR_FLAG_PIN, HIGH);} while(0) + #define I2C3_INTR_FLAG_OFF do {digitalWrite(I2C3_INTR_FLAG_PIN, LOW);} while(0) +#else + #define I2C3_INTR_FLAG_INIT do{}while(0) + #define I2C3_INTR_FLAG_ON do{}while(0) + #define I2C3_INTR_FLAG_OFF do{}while(0) +#endif + + +// ------------------------------------------------------------------------------------------------------ +// Function argument enums +// +enum i2c_op_mode {I2C_OP_MODE_IMM, I2C_OP_MODE_ISR, I2C_OP_MODE_DMA}; +enum i2c_mode {I2C_MASTER, I2C_SLAVE}; +enum i2c_pullup {I2C_PULLUP_EXT, I2C_PULLUP_INT}; +enum i2c_rate {I2C_RATE_100 = 100000, + I2C_RATE_200 = 200000, + I2C_RATE_300 = 300000, + I2C_RATE_400 = 400000, + I2C_RATE_600 = 600000, + I2C_RATE_800 = 800000, + I2C_RATE_1000 = 1000000, + I2C_RATE_1200 = 1200000, + I2C_RATE_1500 = 1500000, + I2C_RATE_1800 = 1800000, + I2C_RATE_2000 = 2000000, + I2C_RATE_2400 = 2400000, + I2C_RATE_2800 = 2800000, + I2C_RATE_3000 = 3000000}; +enum i2c_stop {I2C_NOSTOP, I2C_STOP}; +enum i2c_status {I2C_WAITING, + I2C_SENDING, + I2C_SEND_ADDR, + I2C_RECEIVING, + I2C_TIMEOUT, + I2C_ADDR_NAK, + I2C_DATA_NAK, + I2C_ARB_LOST, + I2C_BUF_OVF, + I2C_SLAVE_TX, + I2C_SLAVE_RX}; +enum i2c_dma_state {I2C_DMA_OFF, + I2C_DMA_ADDR, + I2C_DMA_BULK, + I2C_DMA_LAST}; +#if defined(__MKL26Z64__) // LC + enum i2c_pins {I2C_PINS_16_17, // 16 SCL0 17 SDA0 + I2C_PINS_18_19, // 19 SCL0 18 SDA0 + I2C_PINS_22_23}; // 22 SCL1 23 SDA1 +#elif defined(__MK20DX128__) // 3.0 + enum i2c_pins {I2C_PINS_16_17, // 16 SCL0 17 SDA0 + I2C_PINS_18_19}; // 19 SCL0 18 SDA0 +#elif defined(__MK20DX256__) // 3.1/3.2 + enum i2c_pins {I2C_PINS_16_17, // 16 SCL0 17 SDA0 + I2C_PINS_18_19, // 19 SCL0 18 SDA0 + I2C_PINS_29_30, // 29 SCL1 30 SDA1 + I2C_PINS_26_31}; // 26 SCL1 31 SDA1 +#elif defined(__MK64FX512__) // 3.5 + enum i2c_pins {I2C_PINS_3_4, // 3 SCL2 4 SDA2 + I2C_PINS_7_8, // 7 SCL0 8 SDA0 + I2C_PINS_16_17, // 16 SCL0 17 SDA0 + I2C_PINS_18_19, // 19 SCL0 18 SDA0 + I2C_PINS_33_34, // 33 SCL0 34 SDA0 + I2C_PINS_37_38, // 37 SCL1 38 SDA1 + I2C_PINS_47_48}; // 47 SCL0 48 SDA0 +#elif defined(__MK66FX1M0__) // 3.6 + enum i2c_pins {I2C_PINS_3_4, // 3 SCL2 4 SDA2 + I2C_PINS_7_8, // 7 SCL0 8 SDA0 + I2C_PINS_16_17, // 16 SCL0 17 SDA0 + I2C_PINS_18_19, // 19 SCL0 18 SDA0 + I2C_PINS_33_34, // 33 SCL0 34 SDA0 + I2C_PINS_37_38, // 37 SCL1 38 SDA1 + I2C_PINS_47_48, // 47 SCL0 48 SDA0 + I2C_PINS_56_57}; // 57 SCL3 56 SDA3 +#endif + + +// ------------------------------------------------------------------------------------------------------ +// Divide ratio tables +// +const int32_t i2c_div_num[] = + {20,22,24,26,28,30,32,34,36,40,44,48,52,56,60,64,68,72, + 80,88,96,104,112,128,136,144,160,176,192,224,240,256,288, + 320,352,384,448,480,512,576,640,768,896,960,1024,1152, + 1280,1536,1920,1792,2048,2304,2560,3072,3840}; + +const uint8_t i2c_div_ratio[] = + {I2C_F_DIV20,I2C_F_DIV22,I2C_F_DIV24,I2C_F_DIV26, + I2C_F_DIV28,I2C_F_DIV30,I2C_F_DIV32,I2C_F_DIV34, + I2C_F_DIV36,I2C_F_DIV40,I2C_F_DIV44,I2C_F_DIV48, + I2C_F_DIV52,I2C_F_DIV56,I2C_F_DIV60,I2C_F_DIV64, + I2C_F_DIV68,I2C_F_DIV72,I2C_F_DIV80,I2C_F_DIV88, + I2C_F_DIV96,I2C_F_DIV104,I2C_F_DIV112,I2C_F_DIV128, + I2C_F_DIV136,I2C_F_DIV144,I2C_F_DIV160,I2C_F_DIV176, + I2C_F_DIV192,I2C_F_DIV224,I2C_F_DIV240,I2C_F_DIV256, + I2C_F_DIV288,I2C_F_DIV320,I2C_F_DIV352,I2C_F_DIV384, + I2C_F_DIV448,I2C_F_DIV480,I2C_F_DIV512,I2C_F_DIV576, + I2C_F_DIV640,I2C_F_DIV768,I2C_F_DIV896,I2C_F_DIV960, + I2C_F_DIV1024,I2C_F_DIV1152,I2C_F_DIV1280,I2C_F_DIV1536, + I2C_F_DIV1920,I2C_F_DIV1792,I2C_F_DIV2048,I2C_F_DIV2304, + I2C_F_DIV2560,I2C_F_DIV3072,I2C_F_DIV3840}; + + +// ------------------------------------------------------------------------------------------------------ +// Main I2C data structure +// +struct i2cStruct +{ + volatile uint8_t* A1; // Address Register 1 (User&ISR) + volatile uint8_t* F; // Frequency Divider Register (User&ISR) + volatile uint8_t* C1; // Control Register 1 (User&ISR) + volatile uint8_t* S; // Status Register (User&ISR) + volatile uint8_t* D; // Data I/O Register (User&ISR) + volatile uint8_t* C2; // Control Register 2 (User&ISR) + volatile uint8_t* FLT; // Programmable Input Glitch Filter (User&ISR) + volatile uint8_t* RA; // Range Address Register (User&ISR) + volatile uint8_t* SMB; // SMBus Control and Status Register (User&ISR) + volatile uint8_t* A2; // Address Register 2 (User&ISR) + volatile uint8_t* SLTH; // SCL Low Timeout Register High (User&ISR) + volatile uint8_t* SLTL; // SCL Low Timeout Register Low (User&ISR) + uint8_t rxBuffer[I2C_RX_BUFFER_LENGTH]; // Rx Buffer (ISR) + volatile size_t rxBufferIndex; // Rx Index (User&ISR) + volatile size_t rxBufferLength; // Rx Length (ISR) + uint8_t txBuffer[I2C_TX_BUFFER_LENGTH]; // Tx Buffer (User) + volatile size_t txBufferIndex; // Tx Index (User&ISR) + volatile size_t txBufferLength; // Tx Length (User&ISR) + i2c_op_mode opMode; // Operating Mode (User) + i2c_mode currentMode; // Current Mode (User) + i2c_pins currentPins; // Current Pins (User) + i2c_pullup currentPullup; // Current Pullup (User) + uint32_t currentRate; // Current Rate (User) + i2c_stop currentStop; // Current Stop (User) + volatile i2c_status currentStatus; // Current Status (User&ISR) + uint8_t rxAddr; // Rx Address (ISR) + size_t reqCount; // Byte Request Count (User) + uint8_t irqCount; // IRQ Count, used by SDA-rising ISR (ISR) + uint8_t timeoutRxNAK; // Rx Timeout NAK flag (ISR) + volatile i2c_dma_state activeDMA; // Active DMA flag (User&ISR) + void (*user_onReceive)(size_t len); // Slave Rx Callback Function (User) + void (*user_onRequest)(void); // Slave Tx Callback Function (User) + DMAChannel* DMA; // DMA Channel object (User&ISR) + uint32_t defTimeout; // Default Timeout (User) +}; + + +// ------------------------------------------------------------------------------------------------------ +// I2C Class +// +extern "C" void i2c0_isr(void); +#if I2C_BUS_NUM >= 2 + extern "C" void i2c1_isr(void); +#endif +#if I2C_BUS_NUM >= 3 + extern "C" void i2c2_isr(void); +#endif +#if I2C_BUS_NUM >= 4 + extern "C" void i2c3_isr(void); +#endif +extern "C" void i2c_isr_handler(struct i2cStruct* i2c, uint8_t bus); + +class i2c_t3 : public Stream +{ +private: + // + // I2C data structures - these need to be static so "C" ISRs can use them + // + static struct i2cStruct i2cData[I2C_BUS_NUM]; + // + // Base handler - ISRs are non-class global functions, friend of class req'd for data access + // + friend void i2c_isr_handler(struct i2cStruct* i2c, uint8_t bus); + // + // Bus ISRs + // + friend void i2c0_isr(void); // I2C0 ISR + #if (defined(__MK20DX128__) || defined(__MK20DX256__)) + static void sda_rising_isr_handler(struct i2cStruct* i2c, uint8_t bus); // Slave STOP base handler - 3.0/3.1/3.2 only + static void sda0_rising_isr(void); // Slave STOP detection (I2C0) - 3.0/3.1/3.2 only + #endif + #if I2C_BUS_NUM >= 2 + friend void i2c1_isr(void); // I2C1 ISR + #if defined(__MK20DX256__) + static void sda1_rising_isr(void); // Slave STOP detection (I2C1) - 3.1/3.2 only + #endif + #endif + #if I2C_BUS_NUM >= 3 + friend void i2c2_isr(void); // I2C2 ISR + #endif + #if I2C_BUS_NUM >= 4 + friend void i2c3_isr(void); // I2C3 ISR + #endif + +public: + // + // I2C bus number - this is a local, passed as an argument to base functions + // since static functions cannot see it. + uint8_t bus; + // + // I2C structure pointer - this is a local, passed as an argument to base functions + // since static functions cannot see it. + struct i2cStruct* i2c; + + // ------------------------------------------------------------------------------------------------------ + // Constructor + // + i2c_t3(uint8_t i2c_bus); + ~i2c_t3(); + + // ------------------------------------------------------------------------------------------------------ + // Initialize I2C (base routine) + // + static void begin_(struct i2cStruct* i2c, uint8_t bus, i2c_mode mode, uint8_t address1, uint8_t address2, + i2c_pins pins, i2c_pullup pullup, uint32_t rate, i2c_op_mode opMode); + // + // Return default pins + // + static i2c_pins getDefaultPins_(uint8_t bus); + // + // Initialize I2C (Master) - initializes I2C as Master mode, external pullups, 100kHz rate, + // and default pin setting + // default pin setting: + // Wire: I2C_PINS_18_19 + // Wire1: I2C_PINS_29_30 (3.1/3.2), I2C_PINS_22_23 (LC), I2C_PINS_37_38 (3.5/3.6) + // Wire2: I2C_PINS_3_4 (3.5/3.6) + // Wire3: I2C_PINS_56_57 (3.6) + // return: none + // parameters: none + // + inline void begin(void) + { begin_(i2c, bus, I2C_MASTER, 0, 0, getDefaultPins_(bus), I2C_PULLUP_EXT, 100000, I2C_OP_MODE_ISR); } + // + // Initialize I2C (Slave) - initializes I2C as Slave mode using address, external pullups, 100kHz rate, + // and default pin setting + // default pin setting: + // Wire: I2C_PINS_18_19 + // Wire1: I2C_PINS_29_30 (3.1/3.2), I2C_PINS_22_23 (LC), I2C_PINS_37_38 (3.5/3.6) + // Wire2: I2C_PINS_3_4 (3.5/3.6) + // Wire3: I2C_PINS_56_57 (3.6) + // return: none + // parameters: + // address = 7bit slave address of device + // + inline void begin(int address) + { begin_(i2c, bus, I2C_SLAVE, (uint8_t)address, 0, getDefaultPins_(bus), I2C_PULLUP_EXT, 100000, I2C_OP_MODE_ISR); } + inline void begin(uint8_t address) + { begin_(i2c, bus, I2C_SLAVE, address, 0, getDefaultPins_(bus), I2C_PULLUP_EXT, 100000, I2C_OP_MODE_ISR); } + // + // Initialize I2C - initializes I2C as Master or single address Slave + // return: none + // parameters: + // mode = I2C_MASTER, I2C_SLAVE + // address = 7bit slave address when configured as Slave (ignored for Master mode) + // pins = pins to use, options are: + // Interface Devices Pin Name SCL SDA + // --------- ------- -------------- ----- ----- (note: in almost all cases SCL is the + // Wire All I2C_PINS_16_17 16 17 lower pin #, except cases marked *) + // Wire All I2C_PINS_18_19 19 18 * + // Wire 3.5/3.6 I2C_PINS_7_8 7 8 + // Wire 3.5/3.6 I2C_PINS_33_34 33 34 + // Wire 3.5/3.6 I2C_PINS_47_48 47 48 + // Wire1 LC I2C_PINS_22_23 22 23 + // Wire1 3.1/3.2 I2C_PINS_26_31 26 31 + // Wire1 3.1/3.2 I2C_PINS_29_30 29 30 + // Wire1 3.5/3.6 I2C_PINS_37_38 37 38 + // Wire2 3.5/3.6 I2C_PINS_3_4 3 4 + // Wire3 3.6 I2C_PINS_56_57 57 56 * + // pullup = I2C_PULLUP_EXT, I2C_PULLUP_INT + // rate = I2C frequency to use, can be specified directly in Hz, eg. 400000 for 400kHz, or using one of the + // following enum values (deprecated): + // I2C_RATE_100, I2C_RATE_200, I2C_RATE_300, I2C_RATE_400, + // I2C_RATE_600, I2C_RATE_800, I2C_RATE_1000, I2C_RATE_1200, + // I2C_RATE_1500, I2C_RATE_1800, I2C_RATE_2000, I2C_RATE_2400, + // I2C_RATE_2800, I2C_RATE_3000 + // opMode = I2C_OP_MODE_IMM, I2C_OP_MODE_ISR, I2C_OP_MODE_DMA (ignored for Slave mode, defaults to ISR) + // + inline void begin(i2c_mode mode, uint8_t address, i2c_pins pins, i2c_pullup pullup, uint32_t rate, i2c_op_mode opMode=I2C_OP_MODE_ISR) + { begin_(i2c, bus, mode, address, 0, pins, pullup, rate, opMode); } + // + // Initialize I2C - initializes I2C as Master or address range Slave + // return: none + // parameters: + // mode = I2C_MASTER, I2C_SLAVE + // address1 = 1st 7bit address for specifying Slave address range (ignored for Master mode) + // address2 = 2nd 7bit address for specifying Slave address range (ignored for Master mode) + // pins = pins to use, options are: + // Interface Devices Pin Name SCL SDA + // --------- ------- -------------- ----- ----- (note: in almost all cases SCL is the + // Wire All I2C_PINS_16_17 16 17 lower pin #, except cases marked *) + // Wire All I2C_PINS_18_19 19 18 * + // Wire 3.5/3.6 I2C_PINS_7_8 7 8 + // Wire 3.5/3.6 I2C_PINS_33_34 33 34 + // Wire 3.5/3.6 I2C_PINS_47_48 47 48 + // Wire1 LC I2C_PINS_22_23 22 23 + // Wire1 3.1/3.2 I2C_PINS_26_31 26 31 + // Wire1 3.1/3.2 I2C_PINS_29_30 29 30 + // Wire1 3.5/3.6 I2C_PINS_37_38 37 38 + // Wire2 3.5/3.6 I2C_PINS_3_4 3 4 + // Wire3 3.6 I2C_PINS_56_57 57 56 * + // pullup = I2C_PULLUP_EXT, I2C_PULLUP_INT + // rate = I2C frequency to use, can be specified directly in Hz, eg. 400000 for 400kHz, or using one of the + // following enum values (deprecated): + // I2C_RATE_100, I2C_RATE_200, I2C_RATE_300, I2C_RATE_400, + // I2C_RATE_600, I2C_RATE_800, I2C_RATE_1000, I2C_RATE_1200, + // I2C_RATE_1500, I2C_RATE_1800, I2C_RATE_2000, I2C_RATE_2400, + // I2C_RATE_2800, I2C_RATE_3000 + // opMode = I2C_OP_MODE_IMM, I2C_OP_MODE_ISR, I2C_OP_MODE_DMA (ignored for Slave mode, defaults to ISR) + // + inline void begin(i2c_mode mode, uint8_t address1, uint8_t address2, i2c_pins pins, i2c_pullup pullup, uint32_t rate, i2c_op_mode opMode=I2C_OP_MODE_ISR) + { begin_(i2c, bus, mode, address1, address2, pins, pullup, rate, opMode); } + + // ------------------------------------------------------------------------------------------------------ + // Set Operating Mode (base routine) + // + static uint8_t setOpMode_(struct i2cStruct* i2c, uint8_t bus, i2c_op_mode opMode); + // + // Set Operating Mode - this configures operating mode of the I2C as either Immediate, ISR, or DMA. + // By default Arduino-style begin() calls will initialize to ISR mode. This can + // only be called when the bus is idle (no changing mode in the middle of Tx/Rx). + // Note that Slave mode can only use ISR operation. + // return: 1=success, 0=fail (bus busy) + // parameters: + // opMode = I2C_OP_MODE_ISR, I2C_OP_MODE_DMA, I2C_OP_MODE_IMM + // + inline uint8_t setOpMode(i2c_op_mode opMode) { return setOpMode_(i2c, bus, opMode); } + + // ------------------------------------------------------------------------------------------------------ + // Set I2C rate (base routine) + // + static void setRate_(struct i2cStruct* i2c, uint32_t busFreq, uint32_t i2cFreq); + // + // Set I2C rate - reconfigures I2C frequency divider based on supplied bus freq and desired I2C freq. + // This will be done assuming an idealized I2C rate, even though at high I2C rates + // the actual throughput is much lower than theoretical value. + // + // Since the division ratios are quantized with non-uniform spacing, the selected rate + // will be the one using the nearest available divider. + // return: none + // parameters: + // busFreq = bus frequency, typically F_BUS unless reconfigured + // freq = desired I2C frequency (will be quantized to nearest rate), or can be I2C_RATE_XXX enum (deprecated), + // such as I2C_RATE_100, I2C_RATE_400, etc... + // + // Max I2C rate is 1/20th F_BUS. Some examples: + // + // F_CPU F_BUS Max I2C + // (MHz) (MHz) Rate + // ------------- ----- ---------- + // 240/120 120 6.0M bus overclock + // 216 108 5.4M bus overclock + // 192/96 96 4.8M bus overclock + // 180 90 4.5M bus overclock + // 240 80 4.0M bus overclock + // 216/144/72 72 3.6M bus overclock + // 192 64 3.2M bus overclock + // 240/180/120 60 3.0M + // 168 56 2.8M + // 216 54 2.7M + // 192/144/96/48 48 2.4M + // 72 36 1.8M + // 24 24 1.2M + // 16 16 800k + // 8 8 400k + // 4 4 200k + // 2 2 100k + // + inline void setRate(uint32_t busFreq, uint32_t i2cFreq) { setRate_(i2c, busFreq, i2cFreq); } + // return value and i2c_rate enum no longer needed (deprecated form, always returns 1) + inline uint8_t setRate(uint32_t busFreq, i2c_rate rate) { setRate_(i2c, busFreq, (uint32_t)rate); return 1; } + // Wire compatibility + inline void setClock(uint32_t i2cFreq) + { + #if defined(__MKL26Z64__) // LC + if(i2c->currentPins == I2C_PINS_22_23) + setRate_(i2c, (uint32_t)F_CPU, i2cFreq); // LC Wire1 bus uses system clock (F_CPU) instead of bus clock (F_BUS) + else + setRate_(i2c, (uint32_t)F_BUS, i2cFreq); + #else + setRate_(i2c, (uint32_t)F_BUS, i2cFreq); + #endif + } + // i2c_t3 version of setClock (deprecated) + inline uint8_t setRate(i2c_rate rate) { setClock((uint32_t)rate); return 1; } + + // ------------------------------------------------------------------------------------------------------ + // Get I2C clock - return current clock setting + // return: uint32_t = bus frequency (Hz) + // parameters: none + inline uint32_t getClock(void) { return i2c->currentRate; } + + // ------------------------------------------------------------------------------------------------------ + // Configure I2C pins (base routine) + // + static uint8_t pinConfigure_(struct i2cStruct* i2c, uint8_t bus, i2c_pins pins, i2c_pullup pullup, uint8_t reconfig=1); + // + // ------------------------------------------------------------------------------------------------------ + // Configure I2C pins - reconfigures active I2C pins on-the-fly (only works when bus is idle). If reconfig + // set then inactive pins will switch to input mode using same pullup configuration. + // return: 1=success, 0=fail (bus busy or incompatible pins) + // parameters: + // pins = pins to use, options are: + // Interface Devices Pin Name SCL SDA + // --------- ------- -------------- ----- ----- (note: in almost all cases SCL is the + // Wire All I2C_PINS_16_17 16 17 lower pin #, except cases marked *) + // Wire All I2C_PINS_18_19 19 18 * + // Wire 3.5/3.6 I2C_PINS_7_8 7 8 + // Wire 3.5/3.6 I2C_PINS_33_34 33 34 + // Wire 3.5/3.6 I2C_PINS_47_48 47 48 + // Wire1 LC I2C_PINS_22_23 22 23 + // Wire1 3.1/3.2 I2C_PINS_26_31 26 31 + // Wire1 3.1/3.2 I2C_PINS_29_30 29 30 + // Wire1 3.5/3.6 I2C_PINS_37_38 37 38 + // Wire2 3.5/3.6 I2C_PINS_3_4 3 4 + // Wire3 3.6 I2C_PINS_56_57 57 56 * + // pullup = I2C_PULLUP_EXT, I2C_PULLUP_INT + // reconfig = 1=reconfigure old pins, 0=do not reconfigure old pins (base routine only) + // + inline uint8_t pinConfigure(i2c_pins pins, i2c_pullup pullup) { return pinConfigure_(i2c, bus, pins, pullup, 1); } + + // ------------------------------------------------------------------------------------------------------ + // Set Default Timeout - sets the default timeout to be applied to all functions called with a timeout of + // zero (the default in cases where timeout is not specified). The default is + // initially zero (infinite wait). + // return: none + // parameters: + // timeout = timeout in microseconds + inline void setDefaultTimeout(uint32_t timeout) { i2c->defTimeout = timeout; } + + // ------------------------------------------------------------------------------------------------------ + // Acquire Bus - acquires bus in Master mode and escalates priority as needed, intended + // for internal use only + // return: 1=success, 0=fail (cannot acquire bus) + // parameters: + // timeout = timeout in microseconds + // forceImm = flag to indicate if immediate mode is required + // + static uint8_t acquireBus_(struct i2cStruct* i2c, uint8_t bus, uint32_t timeout, uint8_t& forceImm); + + // ------------------------------------------------------------------------------------------------------ + // Reset Bus - toggles SCL until SDA line is released (9 clocks max). This is used to correct + // a hung bus in which a Slave device missed some clocks and remains stuck outputting + // a low signal on SDA (thereby preventing START/STOP signaling). + // return: none + // + static void resetBus_(struct i2cStruct* i2c, uint8_t bus); + inline void resetBus(void) { resetBus_(i2c, bus); } + + // ------------------------------------------------------------------------------------------------------ + // Setup Master Transmit - initialize Tx buffer for transmit to slave at address + // return: none + // parameters: + // address = target 7bit slave address + // + void beginTransmission(uint8_t address); + inline void beginTransmission(int address) { beginTransmission((uint8_t)address); } + + // ------------------------------------------------------------------------------------------------------ + // Master Transmit (base routine) - cannot be static due to call to getError() and in turn getWriteError() + // + uint8_t endTransmission(struct i2cStruct* i2c, uint8_t bus, i2c_stop sendStop, uint32_t timeout); + // + // Master Transmit - blocking routine with timeout, transmits Tx buffer to slave. i2c_stop parameter can be used + // to indicate if command should end with a STOP(I2C_STOP) or not (I2C_NOSTOP). + // return: 0=success, 1=data too long, 2=recv addr NACK, 3=recv data NACK, 4=other error + // parameters: + // i2c_stop = I2C_NOSTOP, I2C_STOP + // timeout = timeout in microseconds + // + inline uint8_t endTransmission(i2c_stop sendStop, uint32_t timeout) { return endTransmission(i2c, bus, sendStop, timeout); } + // + // Master Transmit - blocking routine, transmits Tx buffer to slave + // return: 0=success, 1=data too long, 2=recv addr NACK, 3=recv data NACK, 4=other error + // + inline uint8_t endTransmission(void) { return endTransmission(i2c, bus, I2C_STOP, 0); } + // + // Master Transmit - blocking routine, transmits Tx buffer to slave. i2c_stop parameter can be used to indicate + // if command should end with a STOP (I2C_STOP) or not (I2C_NOSTOP). + // return: 0=success, 1=data too long, 2=recv addr NACK, 3=recv data NACK, 4=other error + // parameters: + // i2c_stop = I2C_NOSTOP, I2C_STOP + // + inline uint8_t endTransmission(i2c_stop sendStop) { return endTransmission(i2c, bus, sendStop, 0); } + inline uint8_t endTransmission(uint8_t sendStop) { return endTransmission(i2c, bus, (i2c_stop)sendStop, 0); } // Wire compatibility + + // ------------------------------------------------------------------------------------------------------ + // Send Master Transmit (base routine) + // + static void sendTransmission_(struct i2cStruct* i2c, uint8_t bus, i2c_stop sendStop, uint32_t timeout); + // + // Send Master Transmit - non-blocking routine, starts transmit of Tx buffer to slave. Defaults to I2C_STOP. + // Use done() or finish() to determine completion and status() to determine success/fail. + // return: none + // + inline void sendTransmission(void) { sendTransmission_(i2c, bus, I2C_STOP, 0); } + // + // Send Master Transmit - non-blocking routine, starts transmit of Tx buffer to slave. i2c_stop parameter can be + // used to indicate if command should end with a STOP (I2C_STOP) or not (I2C_NOSTOP). Use + // done() or finish() to determine completion and status() to determine success/fail. + // return: none + // parameters: + // i2c_stop = I2C_NOSTOP, I2C_STOP + // + inline void sendTransmission(i2c_stop sendStop) { sendTransmission_(i2c, bus, sendStop, 0); } + + // ------------------------------------------------------------------------------------------------------ + // Master Receive (base routine) + // + static size_t requestFrom_(struct i2cStruct* i2c, uint8_t bus, uint8_t addr, size_t len, i2c_stop sendStop, uint32_t timeout); + // + // Master Receive - blocking routine, requests length bytes from slave at address. Receive data will be placed + // in the Rx buffer. + // return: #bytes received = success, 0=fail + // parameters: + // address = target 7bit slave address + // length = number of bytes requested + // + inline size_t requestFrom(uint8_t addr, size_t len) + { return requestFrom_(i2c, bus, addr, len, I2C_STOP, 0); } + inline size_t requestFrom(int addr, int len) + { return requestFrom_(i2c, bus, (uint8_t)addr, (size_t)len, I2C_STOP, 0); } + inline uint8_t requestFrom(uint8_t addr, uint8_t len) + { return (uint8_t)requestFrom_(i2c, bus, addr, (size_t)len, I2C_STOP, 0); } // Wire compatibility + // + // Master Receive - blocking routine, requests length bytes from slave at address. Receive data will be placed + // in the Rx buffer. i2c_stop parameter can be used to indicate if command should end with a + // STOP (I2C_STOP) or not (I2C_NOSTOP). + // return: #bytes received = success, 0=fail + // parameters: + // address = target 7bit slave address + // length = number of bytes requested + // i2c_stop = I2C_NOSTOP, I2C_STOP + // + inline size_t requestFrom(uint8_t addr, size_t len, i2c_stop sendStop) + { return requestFrom_(i2c, bus, addr, len, sendStop, 0); } + inline uint8_t requestFrom(uint8_t addr, uint8_t len, uint8_t sendStop) + { return (uint8_t)requestFrom_(i2c, bus, addr, (size_t)len, (i2c_stop)sendStop, 0); } // Wire compatibility + // + // Master Receive - blocking routine with timeout, requests length bytes from slave at address. Receive data will + // be placed in the Rx buffer. i2c_stop parameter can be used to indicate if command should end + // with a STOP (I2C_STOP) or not (I2C_NOSTOP). + // return: #bytes received = success, 0=fail (0 length request, NAK, timeout, or bus error) + // parameters: + // address = target 7bit slave address + // length = number of bytes requested + // i2c_stop = I2C_NOSTOP, I2C_STOP + // timeout = timeout in microseconds + // + inline size_t requestFrom(uint8_t addr, size_t len, i2c_stop sendStop, uint32_t timeout) + { return requestFrom_(i2c, bus, addr, len, sendStop, timeout); } + + // ------------------------------------------------------------------------------------------------------ + // Start Master Receive (base routine) + // + static void sendRequest_(struct i2cStruct* i2c, uint8_t bus, uint8_t addr, size_t len, i2c_stop sendStop, uint32_t timeout); + // + // Start Master Receive - non-blocking routine, starts request for length bytes from slave at address. Receive + // data will be placed in the Rx buffer. i2c_stop parameter can be used to indicate if + // command should end with a STOP (I2C_STOP) or not (I2C_NOSTOP). Use done() or finish() + // to determine completion and status() to determine success/fail. + // return: none + // parameters: + // address = target 7bit slave address + // length = number of bytes requested + // i2c_stop = I2C_NOSTOP, I2C_STOP + // + inline void sendRequest(uint8_t addr, size_t len, i2c_stop sendStop) { sendRequest_(i2c, bus, addr, len, sendStop, 0); } + + // ------------------------------------------------------------------------------------------------------ + // Get Wire Error - returns "Wire" error code from a failed Tx/Rx command + // return: 0=success, 1=data too long, 2=recv addr NACK, 3=recv data NACK, 4=other error + // + uint8_t getError(void); + + // ------------------------------------------------------------------------------------------------------ + // Return Status - returns current status of I2C (enum return value) + // return: I2C_WAITING, I2C_SENDING, I2C_SEND_ADDR, I2C_RECEIVING, + // I2C_TIMEOUT, I2C_ADDR_NAK, I2C_DATA_NAK, I2C_ARB_LOST, + // I2C_SLAVE_TX, I2C_SLAVE_RX + // + inline i2c_status status(void) { return i2c->currentStatus; } + + // ------------------------------------------------------------------------------------------------------ + // Return Status (base routine) + // + static uint8_t done_(struct i2cStruct* i2c); + // + // Done Check - returns simple complete/not-complete value to indicate I2C status + // return: 1=Tx/Rx complete (with or without errors), 0=still running + // + inline uint8_t done(void) { return done_(i2c); } + + // ------------------------------------------------------------------------------------------------------ + // Return Status (base routine) + // + static uint8_t finish_(struct i2cStruct* i2c, uint8_t bus, uint32_t timeout); + // + // Finish - blocking routine, loops until Tx/Rx is complete + // return: 1=success (Tx or Rx completed, no error), 0=fail (NAK, timeout or Arb Lost) + // + inline uint8_t finish(void) { return finish_(i2c, bus, 0); } + // + // Finish - blocking routine with timeout, loops until Tx/Rx is complete or timeout occurs + // return: 1=success (Tx or Rx completed, no error), 0=fail (NAK, timeout or Arb Lost) + // parameters: + // timeout = timeout in microseconds + // + inline uint8_t finish(uint32_t timeout) { return finish_(i2c, bus, timeout); } + + // ------------------------------------------------------------------------------------------------------ + // Write - write data byte to Tx buffer + // return: #bytes written = success, 0=fail + // parameters: + // data = data byte + // + size_t write(uint8_t data); + inline size_t write(unsigned long n) { return write((uint8_t)n); } + inline size_t write(long n) { return write((uint8_t)n); } + inline size_t write(unsigned int n) { return write((uint8_t)n); } + inline size_t write(int n) { return write((uint8_t)n); } + + // ------------------------------------------------------------------------------------------------------ + // Write Array - write length number of bytes from data array to Tx buffer + // return: #bytes written = success, 0=fail + // parameters: + // data = pointer to uint8_t (or char) array of data + // length = number of bytes to write + // + size_t write(const uint8_t* data, size_t quantity); + inline size_t write(const char* str) { return write((const uint8_t*)str, strlen(str)); } + + // ------------------------------------------------------------------------------------------------------ + // Available - returns number of remaining available bytes in Rx buffer + // return: #bytes available + // + inline int available(void) { return i2c->rxBufferLength - i2c->rxBufferIndex; } + + // ------------------------------------------------------------------------------------------------------ + // Read (base routine) + // + static int read_(struct i2cStruct* i2c); + // + // Read - returns next data byte (signed int) from Rx buffer + // return: data, -1 if buffer empty + // + inline int read(void) { return read_(i2c); } + + // ------------------------------------------------------------------------------------------------------ + // Peek (base routine) + // + static int peek_(struct i2cStruct* i2c); + // + // Peek - returns next data byte (signed int) from Rx buffer without removing it from Rx buffer + // return: data, -1 if buffer empty + // + inline int peek(void) { return peek_(i2c); } + + // ------------------------------------------------------------------------------------------------------ + // Read Byte (base routine) + // + static uint8_t readByte_(struct i2cStruct* i2c); + // + // Read Byte - returns next data byte (uint8_t) from Rx buffer + // return: data, 0 if buffer empty + // + inline uint8_t readByte(void) { return readByte_(i2c); } + + // ------------------------------------------------------------------------------------------------------ + // Peek Byte (base routine) + // + static uint8_t peekByte_(struct i2cStruct* i2c); + // + // Peek Byte - returns next data byte (uint8_t) from Rx buffer without removing it from Rx buffer + // return: data, 0 if buffer empty + // + inline uint8_t peekByte(void) { return peekByte_(i2c); } + + // ------------------------------------------------------------------------------------------------------ + // Flush (not implemented) + // + inline void flush(void) {} + + // ------------------------------------------------------------------------------------------------------ + // Get Rx Address - returns target address of incoming I2C command. Used for Slaves operating over an address range. + // return: rxAddr of last received command + // + inline uint8_t getRxAddr(void) { return i2c->rxAddr; } + + // ------------------------------------------------------------------------------------------------------ + // Set callback function for Slave Rx + // + inline void onReceive(void (*function)(size_t len)) { i2c->user_onReceive = function; } + + // ------------------------------------------------------------------------------------------------------ + // Set callback function for Slave Tx + // + inline void onRequest(void (*function)(void)) { i2c->user_onRequest = function; } + + // ------------------------------------------------------------------------------------------------------ + // For compatibility with pre-1.0 sketches and libraries + inline void send(uint8_t b) { write(b); } + inline void send(uint8_t* s, uint8_t n) { write(s, n); } + inline void send(int n) { write((uint8_t)n); } + inline void send(char* s) { write(s); } + inline uint8_t receive(void) { int c = read(); return (c<0) ? 0 : c; } + + // ------------------------------------------------------------------------------------------------------ + // Immediate operation + static void i2c_wait_(struct i2cStruct* i2c) { while(!(*(i2c->S) & I2C_S_IICIF)){} *(i2c->S) = I2C_S_IICIF; } +}; + +extern i2c_t3 Wire; +#if I2C_BUS_NUM >= 2 + extern i2c_t3 Wire1; +#endif +#if I2C_BUS_NUM >= 3 + extern i2c_t3 Wire2; +#endif +#if I2C_BUS_NUM >= 4 + extern i2c_t3 Wire3; +#endif + +#endif // I2C_T3_H diff --git a/Firmware_V2/src/libraries/Metro/Metro.cpp b/Firmware_V2/src/libraries/Metro/Metro.cpp new file mode 100644 index 0000000..f8bd4a6 --- /dev/null +++ b/Firmware_V2/src/libraries/Metro/Metro.cpp @@ -0,0 +1,73 @@ + +#if defined(ARDUINO) && ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif +#include "Metro.h" + + +void Metro::begin(unsigned long interval_millis) +{ + this->autoreset = 0; + interval(interval_millis); + reset(); +} + +// New creator so I can use either the original check behavior or benjamin.soelberg's +// suggested one (see below). +// autoreset = 0 is benjamin.soelberg's check behavior +// autoreset != 0 is the original behavior + +void Metro::begin(unsigned long interval_millis, uint8_t autoreset) +{ + this->autoreset = autoreset; // Fix by Paul Bouchier + interval(interval_millis); + reset(); +} + +void Metro::interval(unsigned long interval_millis) +{ + this->interval_millis = interval_millis; +} + +// Benjamin.soelberg's check behavior: +// When a check is true, add the interval to the internal counter. +// This should guarantee a better overall stability. + +// Original check behavior: +// When a check is true, add the interval to the current millis() counter. +// This method can add a certain offset over time. + +char Metro::check() +{ + if (millis() - this->previous_millis >= this->interval_millis) { + // As suggested by benjamin.soelberg@gmail.com, the following line + // this->previous_millis = millis(); + // was changed to + // this->previous_millis += this->interval_millis; + + // If the interval is set to 0 we revert to the original behavior + if (this->interval_millis <= 0 || this->autoreset ) { + this->previous_millis = millis(); + } else { + this->previous_millis += this->interval_millis; + } + + return 1; + } + + + + return 0; + +} + +void Metro::reset() +{ + + this->previous_millis = millis(); + +} + + diff --git a/Firmware_V2/src/libraries/Metro/Metro.h b/Firmware_V2/src/libraries/Metro/Metro.h new file mode 100644 index 0000000..f8117a2 --- /dev/null +++ b/Firmware_V2/src/libraries/Metro/Metro.h @@ -0,0 +1,26 @@ + + +#ifndef Metro_h +#define Metro_h + +#include + +class Metro +{ + +public: + void begin(unsigned long interval_millis); + void begin(unsigned long interval_millis, uint8_t autoreset); + void interval(unsigned long interval_millis); + char check(); + void reset(); + +private: + uint8_t autoreset; + unsigned long previous_millis, interval_millis; + +}; + +#endif + + diff --git a/Firmware_V2/src/libraries/SPI/SPI.cpp b/Firmware_V2/src/libraries/SPI/SPI.cpp new file mode 100644 index 0000000..cd1f35a --- /dev/null +++ b/Firmware_V2/src/libraries/SPI/SPI.cpp @@ -0,0 +1,1260 @@ +/* + * Copyright (c) 2010 by Cristian Maglie + * SPI Master library for arduino. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#include "SPI.h" +#include "pins_arduino.h" + + + +/**********************************************************/ +/* 8 bit AVR-based boards */ +/**********************************************************/ + +#if defined(__AVR__) + +SPIClass SPI; + +uint8_t SPIClass::interruptMode = 0; +uint8_t SPIClass::interruptMask = 0; +uint8_t SPIClass::interruptSave = 0; +#ifdef SPI_TRANSACTION_MISMATCH_LED +uint8_t SPIClass::inTransactionFlag = 0; +#endif +uint8_t SPIClass::_transferWriteFill = 0; + + +void SPIClass::begin() +{ + // Set SS to high so a connected chip will be "deselected" by default + digitalWrite(SS, HIGH); + + // When the SS pin is set as OUTPUT, it can be used as + // a general purpose output port (it doesn't influence + // SPI operations). + pinMode(SS, OUTPUT); + + // Warning: if the SS pin ever becomes a LOW INPUT then SPI + // automatically switches to Slave, so the data direction of + // the SS pin MUST be kept as OUTPUT. + SPCR |= _BV(MSTR); + SPCR |= _BV(SPE); + + // Set direction register for SCK and MOSI pin. + // MISO pin automatically overrides to INPUT. + // By doing this AFTER enabling SPI, we avoid accidentally + // clocking in a single bit since the lines go directly + // from "input" to SPI control. + // http://code.google.com/p/arduino/issues/detail?id=888 + pinMode(SCK, OUTPUT); + pinMode(MOSI, OUTPUT); +} + +void SPIClass::end() { + SPCR &= ~_BV(SPE); +} + +// mapping of interrupt numbers to bits within SPI_AVR_EIMSK +#if defined(__AVR_ATmega32U4__) + #define SPI_INT0_MASK (1< 1) return; + + stmp = SREG; + noInterrupts(); + switch (interruptNumber) { + #ifdef SPI_INT0_MASK + case 0: mask = SPI_INT0_MASK; break; + #endif + #ifdef SPI_INT1_MASK + case 1: mask = SPI_INT1_MASK; break; + #endif + #ifdef SPI_INT2_MASK + case 2: mask = SPI_INT2_MASK; break; + #endif + #ifdef SPI_INT3_MASK + case 3: mask = SPI_INT3_MASK; break; + #endif + #ifdef SPI_INT4_MASK + case 4: mask = SPI_INT4_MASK; break; + #endif + #ifdef SPI_INT5_MASK + case 5: mask = SPI_INT5_MASK; break; + #endif + #ifdef SPI_INT6_MASK + case 6: mask = SPI_INT6_MASK; break; + #endif + #ifdef SPI_INT7_MASK + case 7: mask = SPI_INT7_MASK; break; + #endif + default: + interruptMode = 2; + SREG = stmp; + return; + } + interruptMode = 1; + interruptMask |= mask; + SREG = stmp; +} + +void SPIClass::transfer(const void * buf, void * retbuf, uint32_t count) { + if (count == 0) return; + + const uint8_t *p = (const uint8_t *)buf; + uint8_t *pret = (uint8_t *)retbuf; + uint8_t in; + + uint8_t out = p ? *p++ : _transferWriteFill; + SPDR = out; + while (--count > 0) { + if (p) { + out = *p++; + } + while (!(SPSR & _BV(SPIF))) ; + in = SPDR; + SPDR = out; + if (pret)*pret++ = in; + } + while (!(SPSR & _BV(SPIF))) ; + in = SPDR; + if (pret)*pret = in; +} + + +/**********************************************************/ +/* 32 bit Teensy 3.x */ +/**********************************************************/ + +#elif defined(__arm__) && defined(TEENSYDUINO) && defined(KINETISK) +#if defined(KINETISK) && defined( SPI_HAS_TRANSFER_ASYNC) + +#ifndef TRANSFER_COUNT_FIXED +inline void DMAChanneltransferCount(DMAChannel * dmac, unsigned int len) { + // note does no validation of length... + DMABaseClass::TCD_t *tcd = dmac->TCD; + if (!(tcd->BITER & DMA_TCD_BITER_ELINK)) { + tcd->BITER = len & 0x7fff; + } else { + tcd->BITER = (tcd->BITER & 0xFE00) | (len & 0x1ff); + } + tcd->CITER = tcd->BITER; +} +#else +inline void DMAChanneltransferCount(DMAChannel * dmac, unsigned int len) { + dmac->transferCount(len); +} +#endif +#endif + + +#if defined(__MK20DX128__) || defined(__MK20DX256__) +#ifdef SPI_HAS_TRANSFER_ASYNC +void _spi_dma_rxISR0(void) {SPI.dma_rxisr();} +#else +void _spi_dma_rxISR0(void) {;} +#endif + +const SPIClass::SPI_Hardware_t SPIClass::spi0_hardware = { + SIM_SCGC6, SIM_SCGC6_SPI0, 4, IRQ_SPI0, + 32767, DMAMUX_SOURCE_SPI0_TX, DMAMUX_SOURCE_SPI0_RX, + _spi_dma_rxISR0, + 12, 8, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), + 11, 7, + PORT_PCR_DSE | PORT_PCR_MUX(2), PORT_PCR_MUX(2), + 13, 14, + PORT_PCR_DSE | PORT_PCR_MUX(2), PORT_PCR_MUX(2), + 10, 2, 9, 6, 20, 23, 21, 22, 15, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), + 0x1, 0x1, 0x2, 0x2, 0x4, 0x4, 0x8, 0x8, 0x10 +}; +SPIClass SPI((uintptr_t)&KINETISK_SPI0, (uintptr_t)&SPIClass::spi0_hardware); + +#elif defined(__MK64FX512__) || defined(__MK66FX1M0__) +#ifdef SPI_HAS_TRANSFER_ASYNC +void _spi_dma_rxISR0(void) {SPI.dma_rxisr();} +void _spi_dma_rxISR1(void) {SPI1.dma_rxisr();} +void _spi_dma_rxISR2(void) {SPI2.dma_rxisr();} +#else +void _spi_dma_rxISR0(void) {;} +void _spi_dma_rxISR1(void) {;} +void _spi_dma_rxISR2(void) {;} +#endif +const SPIClass::SPI_Hardware_t SPIClass::spi0_hardware = { + SIM_SCGC6, SIM_SCGC6_SPI0, 4, IRQ_SPI0, + 32767, DMAMUX_SOURCE_SPI0_TX, DMAMUX_SOURCE_SPI0_RX, + _spi_dma_rxISR0, + 12, 8, 39, 255, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), 0, + 11, 7, 28, 255, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), 0, + 13, 14, 27, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), + 10, 2, 9, 6, 20, 23, 21, 22, 15, 26, 45, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(3), + 0x1, 0x1, 0x2, 0x2, 0x4, 0x4, 0x8, 0x8, 0x10, 0x1, 0x20 +}; +const SPIClass::SPI_Hardware_t SPIClass::spi1_hardware = { + SIM_SCGC6, SIM_SCGC6_SPI1, 1, IRQ_SPI1, + #if defined(__MK66FX1M0__) + 32767, DMAMUX_SOURCE_SPI1_TX, DMAMUX_SOURCE_SPI1_RX, + #else + // T3.5 does not have good DMA support on 1 and 2 + 511, 0, DMAMUX_SOURCE_SPI1, + #endif + _spi_dma_rxISR1, + 1, 5, 61, 59, + PORT_PCR_MUX(2), PORT_PCR_MUX(7), PORT_PCR_MUX(2), PORT_PCR_MUX(7), + 0, 21, 61, 59, + PORT_PCR_MUX(2), PORT_PCR_MUX(7), PORT_PCR_MUX(7), PORT_PCR_MUX(2), + 32, 20, 60, + PORT_PCR_MUX(2), PORT_PCR_MUX(7), PORT_PCR_MUX(2), + 6, 31, 58, 62, 63, 255, 255, 255, 255, 255, 255, + PORT_PCR_MUX(7), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), 0, 0, 0, 0, 0, 0, + 0x1, 0x1, 0x2, 0x1, 0x4, 0, 0, 0, 0, 0, 0 +}; +const SPIClass::SPI_Hardware_t SPIClass::spi2_hardware = { + SIM_SCGC3, SIM_SCGC3_SPI2, 1, IRQ_SPI2, + #if defined(__MK66FX1M0__) + 32767, DMAMUX_SOURCE_SPI2_TX, DMAMUX_SOURCE_SPI2_RX, + #else + // T3.5 does not have good DMA support on 1 and 2 + 511, 0, DMAMUX_SOURCE_SPI2, + #endif + _spi_dma_rxISR2, + 45, 51, 255, 255, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), 0, 0, + 44, 52, 255, 255, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), 0, 0, + 46, 53, 255, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), 0, + 43, 54, 55, 255, 255, 255, 255, 255, 255, 255, 255, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), 0, 0, 0, 0, 0, 0, 0, 0, + 0x1, 0x2, 0x1, 0, 0, 0, 0, 0, 0, 0, 0 +}; +SPIClass SPI((uintptr_t)&KINETISK_SPI0, (uintptr_t)&SPIClass::spi0_hardware); +SPIClass SPI1((uintptr_t)&KINETISK_SPI1, (uintptr_t)&SPIClass::spi1_hardware); +SPIClass SPI2((uintptr_t)&KINETISK_SPI2, (uintptr_t)&SPIClass::spi2_hardware); +#endif + + +void SPIClass::begin() +{ + volatile uint32_t *reg; + + hardware().clock_gate_register |= hardware().clock_gate_mask; + port().MCR = SPI_MCR_MDIS | SPI_MCR_HALT | SPI_MCR_PCSIS(0x1F); + port().CTAR0 = SPI_CTAR_FMSZ(7) | SPI_CTAR_PBR(0) | SPI_CTAR_BR(1) | SPI_CTAR_CSSCK(1); + port().CTAR1 = SPI_CTAR_FMSZ(15) | SPI_CTAR_PBR(0) | SPI_CTAR_BR(1) | SPI_CTAR_CSSCK(1); + port().MCR = SPI_MCR_MSTR | SPI_MCR_PCSIS(0x1F); + reg = portConfigRegister(hardware().mosi_pin[mosi_pin_index]); + *reg = hardware().mosi_mux[mosi_pin_index]; + reg = portConfigRegister(hardware().miso_pin[miso_pin_index]); + *reg= hardware().miso_mux[miso_pin_index]; + reg = portConfigRegister(hardware().sck_pin[sck_pin_index]); + *reg = hardware().sck_mux[sck_pin_index]; +} + +void SPIClass::end() +{ + volatile uint32_t *reg; + + reg = portConfigRegister(hardware().mosi_pin[mosi_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware().miso_pin[miso_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware().sck_pin[sck_pin_index]); + *reg = 0; + port().MCR = SPI_MCR_MDIS | SPI_MCR_HALT | SPI_MCR_PCSIS(0x1F); +} + +void SPIClass::usingInterrupt(IRQ_NUMBER_t interruptName) +{ + uint32_t n = (uint32_t)interruptName; + + if (n >= NVIC_NUM_INTERRUPTS) return; + + //Serial.print("usingInterrupt "); + //Serial.println(n); + interruptMasksUsed |= (1 << (n >> 5)); + interruptMask[n >> 5] |= (1 << (n & 0x1F)); + //Serial.printf("interruptMasksUsed = %d\n", interruptMasksUsed); + //Serial.printf("interruptMask[0] = %08X\n", interruptMask[0]); + //Serial.printf("interruptMask[1] = %08X\n", interruptMask[1]); + //Serial.printf("interruptMask[2] = %08X\n", interruptMask[2]); +} + +void SPIClass::notUsingInterrupt(IRQ_NUMBER_t interruptName) +{ + uint32_t n = (uint32_t)interruptName; + if (n >= NVIC_NUM_INTERRUPTS) return; + interruptMask[n >> 5] &= ~(1 << (n & 0x1F)); + if (interruptMask[n >> 5] == 0) { + interruptMasksUsed &= ~(1 << (n >> 5)); + } +} + +const uint16_t SPISettings::ctar_div_table[23] = { + 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, 32, 40, + 56, 64, 96, 128, 192, 256, 384, 512, 640, 768 +}; +const uint32_t SPISettings::ctar_clock_table[23] = { + SPI_CTAR_PBR(0) | SPI_CTAR_BR(0) | SPI_CTAR_DBR | SPI_CTAR_CSSCK(0), + SPI_CTAR_PBR(1) | SPI_CTAR_BR(0) | SPI_CTAR_DBR | SPI_CTAR_CSSCK(0), + SPI_CTAR_PBR(0) | SPI_CTAR_BR(0) | SPI_CTAR_CSSCK(0), + SPI_CTAR_PBR(2) | SPI_CTAR_BR(0) | SPI_CTAR_DBR | SPI_CTAR_CSSCK(0), + SPI_CTAR_PBR(1) | SPI_CTAR_BR(0) | SPI_CTAR_CSSCK(0), + SPI_CTAR_PBR(0) | SPI_CTAR_BR(1) | SPI_CTAR_CSSCK(1), + SPI_CTAR_PBR(2) | SPI_CTAR_BR(0) | SPI_CTAR_CSSCK(0), + SPI_CTAR_PBR(1) | SPI_CTAR_BR(1) | SPI_CTAR_CSSCK(1), + SPI_CTAR_PBR(0) | SPI_CTAR_BR(3) | SPI_CTAR_CSSCK(2), + SPI_CTAR_PBR(2) | SPI_CTAR_BR(1) | SPI_CTAR_CSSCK(0), + SPI_CTAR_PBR(1) | SPI_CTAR_BR(3) | SPI_CTAR_CSSCK(2), + SPI_CTAR_PBR(0) | SPI_CTAR_BR(4) | SPI_CTAR_CSSCK(3), + SPI_CTAR_PBR(2) | SPI_CTAR_BR(3) | SPI_CTAR_CSSCK(2), + SPI_CTAR_PBR(3) | SPI_CTAR_BR(3) | SPI_CTAR_CSSCK(2), + SPI_CTAR_PBR(0) | SPI_CTAR_BR(5) | SPI_CTAR_CSSCK(4), + SPI_CTAR_PBR(1) | SPI_CTAR_BR(5) | SPI_CTAR_CSSCK(4), + SPI_CTAR_PBR(0) | SPI_CTAR_BR(6) | SPI_CTAR_CSSCK(5), + SPI_CTAR_PBR(1) | SPI_CTAR_BR(6) | SPI_CTAR_CSSCK(5), + SPI_CTAR_PBR(0) | SPI_CTAR_BR(7) | SPI_CTAR_CSSCK(6), + SPI_CTAR_PBR(1) | SPI_CTAR_BR(7) | SPI_CTAR_CSSCK(6), + SPI_CTAR_PBR(0) | SPI_CTAR_BR(8) | SPI_CTAR_CSSCK(7), + SPI_CTAR_PBR(2) | SPI_CTAR_BR(7) | SPI_CTAR_CSSCK(6), + SPI_CTAR_PBR(1) | SPI_CTAR_BR(8) | SPI_CTAR_CSSCK(7) +}; + +void SPIClass::updateCTAR(uint32_t ctar) +{ + if (port().CTAR0 != ctar) { + uint32_t mcr = port().MCR; + if (mcr & SPI_MCR_MDIS) { + port().CTAR0 = ctar; + port().CTAR1 = ctar | SPI_CTAR_FMSZ(8); + } else { + port().MCR = SPI_MCR_MDIS | SPI_MCR_HALT | SPI_MCR_PCSIS(0x1F); + port().CTAR0 = ctar; + port().CTAR1 = ctar | SPI_CTAR_FMSZ(8); + port().MCR = mcr; + } + } +} + +void SPIClass::setBitOrder(uint8_t bitOrder) +{ + hardware().clock_gate_register |= hardware().clock_gate_mask; + uint32_t ctar = port().CTAR0; + if (bitOrder == LSBFIRST) { + ctar |= SPI_CTAR_LSBFE; + } else { + ctar &= ~SPI_CTAR_LSBFE; + } + updateCTAR(ctar); +} + +void SPIClass::setDataMode(uint8_t dataMode) +{ + hardware().clock_gate_register |= hardware().clock_gate_mask; + //uint32_t ctar = port().CTAR0; + + // TODO: implement with native code + //SPCR = (SPCR & ~SPI_MODE_MASK) | dataMode; +} + +void SPIClass::setClockDivider_noInline(uint32_t clk) +{ + hardware().clock_gate_register |= hardware().clock_gate_mask; + uint32_t ctar = port().CTAR0; + ctar &= (SPI_CTAR_CPOL | SPI_CTAR_CPHA | SPI_CTAR_LSBFE); + if (ctar & SPI_CTAR_CPHA) { + clk = (clk & 0xFFFF0FFF) | ((clk & 0xF000) >> 4); + } + ctar |= clk; + updateCTAR(ctar); +} + +uint8_t SPIClass::pinIsChipSelect(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().cs_pin); i++) { + if (pin == hardware().cs_pin[i]) return hardware().cs_mask[i]; + } + return 0; +} + +bool SPIClass::pinIsChipSelect(uint8_t pin1, uint8_t pin2) +{ + uint8_t pin1_mask, pin2_mask; + if ((pin1_mask = (uint8_t)pinIsChipSelect(pin1)) == 0) return false; + if ((pin2_mask = (uint8_t)pinIsChipSelect(pin2)) == 0) return false; + //Serial.printf("pinIsChipSelect %d %d %x %x\n\r", pin1, pin2, pin1_mask, pin2_mask); + if ((pin1_mask & pin2_mask) != 0) return false; + return true; +} + +bool SPIClass::pinIsMOSI(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().mosi_pin); i++) { + if (pin == hardware().mosi_pin[i]) return true; + } + return false; +} + +bool SPIClass::pinIsMISO(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().miso_pin); i++) { + if (pin == hardware().miso_pin[i]) return true; + } + return false; +} + +bool SPIClass::pinIsSCK(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().sck_pin); i++) { + if (pin == hardware().sck_pin[i]) return true; + } + return false; +} + +// setCS() is not intended for use from normal Arduino programs/sketches. +uint8_t SPIClass::setCS(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().cs_pin); i++) { + if (pin == hardware().cs_pin[i]) { + volatile uint32_t *reg = portConfigRegister(pin); + *reg = hardware().cs_mux[i]; + return hardware().cs_mask[i]; + } + } + return 0; +} + +void SPIClass::setMOSI(uint8_t pin) +{ + if (hardware_addr == (uintptr_t)&spi0_hardware) { + SPCR.setMOSI_soft(pin); + } + if (pin != hardware().mosi_pin[mosi_pin_index]) { + for (unsigned int i = 0; i < sizeof(hardware().mosi_pin); i++) { + if (pin == hardware().mosi_pin[i]) { + if (hardware().clock_gate_register & hardware().clock_gate_mask) { + volatile uint32_t *reg; + reg = portConfigRegister(hardware().mosi_pin[mosi_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware().mosi_pin[i]); + *reg = hardware().mosi_mux[i]; + } + mosi_pin_index = i; + return; + } + } + } +} + +void SPIClass::setMISO(uint8_t pin) +{ + if (hardware_addr == (uintptr_t)&spi0_hardware) { + SPCR.setMISO_soft(pin); + } + if (pin != hardware().miso_pin[miso_pin_index]) { + for (unsigned int i = 0; i < sizeof(hardware().miso_pin); i++) { + if (pin == hardware().miso_pin[i]) { + if (hardware().clock_gate_register & hardware().clock_gate_mask) { + volatile uint32_t *reg; + reg = portConfigRegister(hardware().miso_pin[miso_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware().miso_pin[i]); + *reg = hardware().miso_mux[i]; + } + miso_pin_index = i; + return; + } + } + } +} + +void SPIClass::setSCK(uint8_t pin) +{ + if (hardware_addr == (uintptr_t)&spi0_hardware) { + SPCR.setSCK_soft(pin); + } + if (pin != hardware().sck_pin[sck_pin_index]) { + for (unsigned int i = 0; i < sizeof(hardware().sck_pin); i++) { + if (pin == hardware().sck_pin[i]) { + if (hardware().clock_gate_register & hardware().clock_gate_mask) { + volatile uint32_t *reg; + reg = portConfigRegister(hardware().sck_pin[sck_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware().sck_pin[i]); + *reg = hardware().sck_mux[i]; + } + sck_pin_index = i; + return; + } + } + } +} + +void SPIClass::transfer(const void * buf, void * retbuf, size_t count) +{ + + if (count == 0) return; + if (!(port().CTAR0 & SPI_CTAR_LSBFE)) { + // We are doing the standard MSB order + const uint8_t *p_write = (const uint8_t *)buf; + uint8_t *p_read = (uint8_t *)retbuf; + size_t count_read = count; + + // Lets clear the reader queue + port().MCR = SPI_MCR_MSTR | SPI_MCR_CLR_RXF | SPI_MCR_PCSIS(0x1F); + + uint32_t sr; + + // Now lets loop while we still have data to output + if (count & 1) { + if (p_write) { + if (count > 1) + port().PUSHR = *p_write++ | SPI_PUSHR_CONT | SPI_PUSHR_CTAS(0); + else + port().PUSHR = *p_write++ | SPI_PUSHR_CTAS(0); + } else { + if (count > 1) + port().PUSHR = _transferWriteFill | SPI_PUSHR_CONT | SPI_PUSHR_CTAS(0); + else + port().PUSHR = _transferWriteFill | SPI_PUSHR_CTAS(0); + } + count--; + } + + uint16_t w = (uint16_t)(_transferWriteFill << 8) | _transferWriteFill; + + while (count > 0) { + // Push out the next byte; + if (p_write) { + w = (*p_write++) << 8; + w |= *p_write++; + } + uint16_t queue_full_status_mask = (hardware().queue_size-1) << 12; + if (count == 2) + port().PUSHR = w | SPI_PUSHR_CTAS(1); + else + port().PUSHR = w | SPI_PUSHR_CONT | SPI_PUSHR_CTAS(1); + count -= 2; // how many bytes to output. + // Make sure queue is not full before pushing next byte out + do { + sr = port().SR; + if (sr & 0xF0) { + uint16_t w = port().POPR; // Read any pending RX bytes in + if (count_read & 1) { + if (p_read) { + *p_read++ = w; // Read any pending RX bytes in + } + count_read--; + } else { + if (p_read) { + *p_read++ = w >> 8; + *p_read++ = (w & 0xff); + } + count_read -= 2; + } + } + } while ((sr & (15 << 12)) > queue_full_status_mask); + + } + + // now lets wait for all of the read bytes to be returned... + while (count_read) { + sr = port().SR; + if (sr & 0xF0) { + uint16_t w = port().POPR; // Read any pending RX bytes in + if (count_read & 1) { + if (p_read) + *p_read++ = w; // Read any pending RX bytes in + count_read--; + } else { + if (p_read) { + *p_read++ = w >> 8; + *p_read++ = (w & 0xff); + } + count_read -= 2; + } + } + } + } else { + // We are doing the less ofen LSB mode + const uint8_t *p_write = (const uint8_t *)buf; + uint8_t *p_read = (uint8_t *)retbuf; + size_t count_read = count; + + // Lets clear the reader queue + port().MCR = SPI_MCR_MSTR | SPI_MCR_CLR_RXF | SPI_MCR_PCSIS(0x1F); + + uint32_t sr; + + // Now lets loop while we still have data to output + if (count & 1) { + if (p_write) { + if (count > 1) + port().PUSHR = *p_write++ | SPI_PUSHR_CONT | SPI_PUSHR_CTAS(0); + else + port().PUSHR = *p_write++ | SPI_PUSHR_CTAS(0); + } else { + if (count > 1) + port().PUSHR = _transferWriteFill | SPI_PUSHR_CONT | SPI_PUSHR_CTAS(0); + else + port().PUSHR = _transferWriteFill | SPI_PUSHR_CTAS(0); + } + count--; + } + + uint16_t w = _transferWriteFill; + + while (count > 0) { + // Push out the next byte; + if (p_write) { + w = *p_write++; + w |= ((*p_write++) << 8); + } + uint16_t queue_full_status_mask = (hardware().queue_size-1) << 12; + if (count == 2) + port().PUSHR = w | SPI_PUSHR_CTAS(1); + else + port().PUSHR = w | SPI_PUSHR_CONT | SPI_PUSHR_CTAS(1); + count -= 2; // how many bytes to output. + // Make sure queue is not full before pushing next byte out + do { + sr = port().SR; + if (sr & 0xF0) { + uint16_t w = port().POPR; // Read any pending RX bytes in + if (count_read & 1) { + if (p_read) { + *p_read++ = w; // Read any pending RX bytes in + } + count_read--; + } else { + if (p_read) { + *p_read++ = (w & 0xff); + *p_read++ = w >> 8; + } + count_read -= 2; + } + } + } while ((sr & (15 << 12)) > queue_full_status_mask); + + } + + // now lets wait for all of the read bytes to be returned... + while (count_read) { + sr = port().SR; + if (sr & 0xF0) { + uint16_t w = port().POPR; // Read any pending RX bytes in + if (count_read & 1) { + if (p_read) + *p_read++ = w; // Read any pending RX bytes in + count_read--; + } else { + if (p_read) { + *p_read++ = (w & 0xff); + *p_read++ = w >> 8; + } + count_read -= 2; + } + } + } + } +} +//============================================================================= +// ASYNCH Support +//============================================================================= +//========================================================================= +// Try Transfer using DMA. +//========================================================================= +#ifdef SPI_HAS_TRANSFER_ASYNC +static uint8_t bit_bucket; +#define dontInterruptAtCompletion(dmac) (dmac)->TCD->CSR &= ~DMA_TCD_CSR_INTMAJOR + +//========================================================================= +// Init the DMA channels +//========================================================================= +bool SPIClass::initDMAChannels() { + // Allocate our channels. + _dmaTX = new DMAChannel(); + if (_dmaTX == nullptr) { + return false; + } + + _dmaRX = new DMAChannel(); + if (_dmaRX == nullptr) { + delete _dmaTX; // release it + _dmaTX = nullptr; + return false; + } + + // Let's setup the RX chain + _dmaRX->disable(); + _dmaRX->source((volatile uint8_t&)port().POPR); + _dmaRX->disableOnCompletion(); + _dmaRX->triggerAtHardwareEvent(hardware().rx_dma_channel); + _dmaRX->attachInterrupt(hardware().dma_rxisr); + _dmaRX->interruptAtCompletion(); + + // We may be using settings chain here so lets set it up. + // Now lets setup TX chain. Note if trigger TX is not set + // we need to have the RX do it for us. + _dmaTX->disable(); + _dmaTX->destination((volatile uint8_t&)port().PUSHR); + _dmaTX->disableOnCompletion(); + + if (hardware().tx_dma_channel) { + _dmaTX->triggerAtHardwareEvent(hardware().tx_dma_channel); + } else { +// Serial.printf("SPI InitDMA tx triger by RX: %x\n", (uint32_t)_dmaRX); + _dmaTX->triggerAtTransfersOf(*_dmaRX); + } + + + _dma_state = DMAState::idle; // Should be first thing set! + return true; +} + +//========================================================================= +// Main Async Transfer function +//========================================================================= + +bool SPIClass::transfer(const void *buf, void *retbuf, size_t count, EventResponderRef event_responder) { + uint8_t dma_first_byte; + if (_dma_state == DMAState::notAllocated) { + if (!initDMAChannels()) + return false; + } + + if (_dma_state == DMAState::active) + return false; // already active + + event_responder.clearEvent(); // Make sure it is not set yet + if (count < 2) { + // Use non-async version to simplify cases... + transfer(buf, retbuf, count); + event_responder.triggerEvent(); + return true; + } + + // Now handle the cases where the count > then how many we can output in one DMA request + if (count > hardware().max_dma_count) { + _dma_count_remaining = count - hardware().max_dma_count; + count = hardware().max_dma_count; + } else { + _dma_count_remaining = 0; + } + + // Now See if caller passed in a source buffer. + _dmaTX->TCD->ATTR_DST = 0; // Make sure set for 8 bit mode + uint8_t *write_data = (uint8_t*) buf; + if (buf) { + dma_first_byte = *write_data; + _dmaTX->sourceBuffer((uint8_t*)write_data+1, count-1); + _dmaTX->TCD->SLAST = 0; // Finish with it pointing to next location + } else { + dma_first_byte = _transferWriteFill; + _dmaTX->source((uint8_t&)_transferWriteFill); // maybe have setable value + DMAChanneltransferCount(_dmaTX, count-1); + } + if (retbuf) { + // On T3.5 must handle SPI1/2 differently as only one DMA channel + _dmaRX->TCD->ATTR_SRC = 0; //Make sure set for 8 bit mode... + _dmaRX->destinationBuffer((uint8_t*)retbuf, count); + _dmaRX->TCD->DLASTSGA = 0; // At end point after our bufffer + } else { + // Write only mode + _dmaRX->TCD->ATTR_SRC = 0; //Make sure set for 8 bit mode... + _dmaRX->destination((uint8_t&)bit_bucket); + DMAChanneltransferCount(_dmaRX, count); + } + + _dma_event_responder = &event_responder; + // Now try to start it? + // Setup DMA main object + yield(); + port().MCR = SPI_MCR_MSTR | SPI_MCR_CLR_RXF | SPI_MCR_CLR_TXF | SPI_MCR_PCSIS(0x1F); + + port().SR = 0xFF0F0000; + + // Lets try to output the first byte to make sure that we are in 8 bit mode... + port().PUSHR = dma_first_byte | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT; + + if (hardware().tx_dma_channel) { + port().RSER = SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS | SPI_RSER_TFFF_RE | SPI_RSER_TFFF_DIRS; + _dmaRX->enable(); + // Get the initial settings. + _dmaTX->enable(); + } else { + //T3.5 SP1 and SPI2 - TX is not triggered by SPI but by RX... + port().RSER = SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS ; + _dmaTX->triggerAtTransfersOf(*_dmaRX); + _dmaTX->enable(); + _dmaRX->enable(); + } + + _dma_state = DMAState::active; + return true; +} + + +//------------------------------------------------------------------------- +// DMA RX ISR +//------------------------------------------------------------------------- +void SPIClass::dma_rxisr(void) { + _dmaRX->clearInterrupt(); + _dmaTX->clearComplete(); + _dmaRX->clearComplete(); + + uint8_t should_reenable_tx = true; // should we re-enable TX maybe not if count will be 0... + if (_dma_count_remaining) { + // What do I need to do to start it back up again... + // We will use the BITR/CITR from RX as TX may have prefed some stuff + if (_dma_count_remaining > hardware().max_dma_count) { + _dma_count_remaining -= hardware().max_dma_count; + } else { + DMAChanneltransferCount(_dmaTX, _dma_count_remaining-1); + DMAChanneltransferCount(_dmaRX, _dma_count_remaining); + if (_dma_count_remaining == 1) should_reenable_tx = false; + + _dma_count_remaining = 0; + } + // In some cases we need to again start the TX manually to get it to work... + if (_dmaTX->TCD->SADDR == &_transferWriteFill) { + if (port().CTAR0 & SPI_CTAR_FMSZ(8)) { + port().PUSHR = (_transferWriteFill | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT); + } else { + port().PUSHR = (_transferWriteFill | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT); + } + } else { + if (port().CTAR0 & SPI_CTAR_FMSZ(8)) { + // 16 bit mode + uint16_t w = *((uint16_t*)_dmaTX->TCD->SADDR); + _dmaTX->TCD->SADDR = (volatile uint8_t*)(_dmaTX->TCD->SADDR) + 2; + port().PUSHR = (w | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT); + } else { + uint8_t w = *((uint8_t*)_dmaTX->TCD->SADDR); + _dmaTX->TCD->SADDR = (volatile uint8_t*)(_dmaTX->TCD->SADDR) + 1; + port().PUSHR = (w | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT); + } + } + _dmaRX->enable(); + if (should_reenable_tx) + _dmaTX->enable(); + } else { + + port().RSER = 0; + //port().MCR = SPI_MCR_MSTR | SPI_MCR_CLR_RXF | SPI_MCR_PCSIS(0x1F); // clear out the queue + port().SR = 0xFF0F0000; + port().CTAR0 &= ~(SPI_CTAR_FMSZ(8)); // Hack restore back to 8 bits + + _dma_state = DMAState::completed; // set back to 1 in case our call wants to start up dma again + _dma_event_responder->triggerEvent(); + + } +} +#endif // SPI_HAS_TRANSFER_ASYNC + + +/**********************************************************/ +/* 32 bit Teensy-LC */ +/**********************************************************/ + +#elif defined(__arm__) && defined(TEENSYDUINO) && defined(KINETISL) + +#ifdef SPI_HAS_TRANSFER_ASYNC +void _spi_dma_rxISR0(void) {SPI.dma_isr();} +void _spi_dma_rxISR1(void) {SPI1.dma_isr();} +#else +void _spi_dma_rxISR0(void) {;} +void _spi_dma_rxISR1(void) {;} +#endif + +const SPIClass::SPI_Hardware_t SPIClass::spi0_hardware = { + SIM_SCGC4, SIM_SCGC4_SPI0, + 0, // BR index 0 + DMAMUX_SOURCE_SPI0_TX, DMAMUX_SOURCE_SPI0_RX, _spi_dma_rxISR0, + 12, 8, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), + 11, 7, + PORT_PCR_DSE | PORT_PCR_MUX(2), PORT_PCR_MUX(2), + 13, 14, + PORT_PCR_DSE | PORT_PCR_MUX(2), PORT_PCR_MUX(2), + 10, 2, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), + 0x1, 0x1 +}; +SPIClass SPI((uintptr_t)&KINETISL_SPI0, (uintptr_t)&SPIClass::spi0_hardware); + +const SPIClass::SPI_Hardware_t SPIClass::spi1_hardware = { + SIM_SCGC4, SIM_SCGC4_SPI1, + 1, // BR index 1 in SPI Settings + DMAMUX_SOURCE_SPI1_TX, DMAMUX_SOURCE_SPI1_RX, _spi_dma_rxISR1, + 1, 5, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), + 0, 21, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), + 20, 255, + PORT_PCR_MUX(2), 0, + 6, 255, + PORT_PCR_MUX(2), 0, + 0x1, 0 +}; +SPIClass SPI1((uintptr_t)&KINETISL_SPI1, (uintptr_t)&SPIClass::spi1_hardware); + + +void SPIClass::begin() +{ + volatile uint32_t *reg; + + hardware().clock_gate_register |= hardware().clock_gate_mask; + port().C1 = SPI_C1_SPE | SPI_C1_MSTR; + port().C2 = 0; + uint8_t tmp __attribute__((unused)) = port().S; + reg = portConfigRegister(hardware().mosi_pin[mosi_pin_index]); + *reg = hardware().mosi_mux[mosi_pin_index]; + reg = portConfigRegister(hardware().miso_pin[miso_pin_index]); + *reg = hardware().miso_mux[miso_pin_index]; + reg = portConfigRegister(hardware().sck_pin[sck_pin_index]); + *reg = hardware().sck_mux[sck_pin_index]; +} + +void SPIClass::end() { + volatile uint32_t *reg; + + reg = portConfigRegister(hardware().mosi_pin[mosi_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware().miso_pin[miso_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware().sck_pin[sck_pin_index]); + *reg = 0; + port().C1 = 0; +} + +const uint16_t SPISettings::br_div_table[30] = { + 2, 4, 6, 8, 10, 12, 14, 16, 20, 24, + 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, + 160, 192, 224, 256, 320, 384, 448, 512, 640, 768, +}; + +const uint8_t SPISettings::br_clock_table[30] = { + SPI_BR_SPPR(0) | SPI_BR_SPR(0), + SPI_BR_SPPR(1) | SPI_BR_SPR(0), + SPI_BR_SPPR(2) | SPI_BR_SPR(0), + SPI_BR_SPPR(3) | SPI_BR_SPR(0), + SPI_BR_SPPR(4) | SPI_BR_SPR(0), + SPI_BR_SPPR(5) | SPI_BR_SPR(0), + SPI_BR_SPPR(6) | SPI_BR_SPR(0), + SPI_BR_SPPR(7) | SPI_BR_SPR(0), + SPI_BR_SPPR(4) | SPI_BR_SPR(1), + SPI_BR_SPPR(5) | SPI_BR_SPR(1), + SPI_BR_SPPR(6) | SPI_BR_SPR(1), + SPI_BR_SPPR(7) | SPI_BR_SPR(1), + SPI_BR_SPPR(4) | SPI_BR_SPR(2), + SPI_BR_SPPR(5) | SPI_BR_SPR(2), + SPI_BR_SPPR(6) | SPI_BR_SPR(2), + SPI_BR_SPPR(7) | SPI_BR_SPR(2), + SPI_BR_SPPR(4) | SPI_BR_SPR(3), + SPI_BR_SPPR(5) | SPI_BR_SPR(3), + SPI_BR_SPPR(6) | SPI_BR_SPR(3), + SPI_BR_SPPR(7) | SPI_BR_SPR(3), + SPI_BR_SPPR(4) | SPI_BR_SPR(4), + SPI_BR_SPPR(5) | SPI_BR_SPR(4), + SPI_BR_SPPR(6) | SPI_BR_SPR(4), + SPI_BR_SPPR(7) | SPI_BR_SPR(4), + SPI_BR_SPPR(4) | SPI_BR_SPR(5), + SPI_BR_SPPR(5) | SPI_BR_SPR(5), + SPI_BR_SPPR(6) | SPI_BR_SPR(5), + SPI_BR_SPPR(7) | SPI_BR_SPR(5), + SPI_BR_SPPR(4) | SPI_BR_SPR(6), + SPI_BR_SPPR(5) | SPI_BR_SPR(6) +}; + +void SPIClass::setMOSI(uint8_t pin) +{ + if (pin != hardware().mosi_pin[mosi_pin_index]) { + for (unsigned int i = 0; i < sizeof(hardware().mosi_pin); i++) { + if (pin == hardware().mosi_pin[i] ) { + if (hardware().clock_gate_register & hardware().clock_gate_mask) { + volatile uint32_t *reg; + reg = portConfigRegister(hardware().mosi_pin[mosi_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware().mosi_pin[i]); + *reg = hardware().mosi_mux[i]; + } + mosi_pin_index = i; + return; + } + } + } +} + +void SPIClass::setMISO(uint8_t pin) +{ + if (pin != hardware().miso_pin[miso_pin_index]) { + for (unsigned int i = 0; i < sizeof(hardware().miso_pin); i++) { + if (pin == hardware().miso_pin[i] ) { + if (hardware().clock_gate_register & hardware().clock_gate_mask) { + volatile uint32_t *reg; + reg = portConfigRegister(hardware().miso_pin[miso_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware().miso_pin[i]); + *reg = hardware().miso_mux[i]; + } + miso_pin_index = i; + return; + } + } + } +} + +void SPIClass::setSCK(uint8_t pin) +{ + if (pin != hardware().sck_pin[sck_pin_index]) { + for (unsigned int i = 0; i < sizeof(hardware().sck_pin); i++) { + if (pin == hardware().sck_pin[i] ) { + if (hardware().clock_gate_register & hardware().clock_gate_mask) { + volatile uint32_t *reg; + reg = portConfigRegister(hardware().sck_pin[sck_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware().sck_pin[i]); + *reg = hardware().sck_mux[i]; + } + sck_pin_index = i; + return; + } + } + } +} + +bool SPIClass::pinIsChipSelect(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().cs_pin); i++) { + if (pin == hardware().cs_pin[i]) return hardware().cs_mask[i]; + } + return 0; +} + +bool SPIClass::pinIsMOSI(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().mosi_pin); i++) { + if (pin == hardware().mosi_pin[i]) return true; + } + return false; +} + +bool SPIClass::pinIsMISO(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().miso_pin); i++) { + if (pin == hardware().miso_pin[i]) return true; + } + return false; +} + +bool SPIClass::pinIsSCK(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().sck_pin); i++) { + if (pin == hardware().sck_pin[i]) return true; + } + return false; +} + +// setCS() is not intended for use from normal Arduino programs/sketches. +uint8_t SPIClass::setCS(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().cs_pin); i++) { + if (pin == hardware().cs_pin[i]) { + volatile uint32_t *reg = portConfigRegister(pin); + *reg = hardware().cs_mux[i]; + return hardware().cs_mask[i]; + } + } + return 0; +} + +void SPIClass::transfer(const void * buf, void * retbuf, size_t count) { + if (count == 0) return; + const uint8_t *p = (const uint8_t *)buf; + uint8_t *pret = (uint8_t *)retbuf; + uint8_t in; + + while (!(port().S & SPI_S_SPTEF)) ; // wait + uint8_t out = p ? *p++ : _transferWriteFill; + port().DL = out; + while (--count > 0) { + if (p) { + out = *p++; + } + while (!(port().S & SPI_S_SPTEF)) ; // wait + __disable_irq(); + port().DL = out; + while (!(port().S & SPI_S_SPRF)) ; // wait + in = port().DL; + __enable_irq(); + if (pret)*pret++ = in; + } + while (!(port().S & SPI_S_SPRF)) ; // wait + in = port().DL; + if (pret)*pret = in; +} +//============================================================================= +// ASYNCH Support +//============================================================================= +//========================================================================= +// Try Transfer using DMA. +//========================================================================= +#ifdef SPI_HAS_TRANSFER_ASYNC +static uint8_t _dma_dummy_rx; + +void SPIClass::dma_isr(void) { + // Serial.println("_spi_dma_rxISR"); + _dmaRX->clearInterrupt(); + port().C2 = 0; + uint8_t tmp __attribute__((unused)) = port().S; + _dmaTX->clearComplete(); + _dmaRX->clearComplete(); + + _dma_state = DMAState::completed; // set back to 1 in case our call wants to start up dma again + _dma_event_responder->triggerEvent(); +} + +bool SPIClass::initDMAChannels() { + //Serial.println("First dma call"); Serial.flush(); + _dmaTX = new DMAChannel(); + if (_dmaTX == nullptr) { + return false; + } + + _dmaTX->disable(); + _dmaTX->destination((volatile uint8_t&)port().DL); + _dmaTX->disableOnCompletion(); + _dmaTX->triggerAtHardwareEvent(hardware().tx_dma_channel); + + + _dmaRX = new DMAChannel(); + if (_dmaRX == NULL) { + delete _dmaTX; + _dmaRX = nullptr; + return false; + } + _dmaRX->disable(); + _dmaRX->source((volatile uint8_t&)port().DL); + _dmaRX->disableOnCompletion(); + _dmaRX->triggerAtHardwareEvent(hardware().rx_dma_channel); + _dmaRX->attachInterrupt(hardware().dma_isr); + _dmaRX->interruptAtCompletion(); + + _dma_state = DMAState::idle; // Should be first thing set! + //Serial.println("end First dma call"); + return true; +} + +//========================================================================= +// Main Async Transfer function +//========================================================================= +bool SPIClass::transfer(const void *buf, void *retbuf, size_t count, EventResponderRef event_responder) { + if (_dma_state == DMAState::notAllocated) { + if (!initDMAChannels()) { + return false; + } + } + + if (_dma_state == DMAState::active) + return false; // already active + + event_responder.clearEvent(); // Make sure it is not set yet + + if (count < 2) { + // Use non-async version to simplify cases... + transfer(buf, retbuf, count); + event_responder.triggerEvent(); + return true; + } + //_dmaTX->destination((volatile uint8_t&)port().DL); + //_dmaRX->source((volatile uint8_t&)port().DL); + _dmaTX->CFG->DCR = (_dmaTX->CFG->DCR & ~DMA_DCR_DSIZE(3)) | DMA_DCR_DSIZE(1); + _dmaRX->CFG->DCR = (_dmaRX->CFG->DCR & ~DMA_DCR_SSIZE(3)) | DMA_DCR_SSIZE(1); // 8 bit transfer + + // Now see if the user passed in TX buffer to send. + uint8_t first_char; + if (buf) { + uint8_t *data_out = (uint8_t*)buf; + first_char = *data_out++; + _dmaTX->sourceBuffer(data_out, count-1); + } else { + first_char = (_transferWriteFill & 0xff); + _dmaTX->source((uint8_t&)_transferWriteFill); // maybe have setable value + _dmaTX->transferCount(count-1); + } + + if (retbuf) { + _dmaRX->destinationBuffer((uint8_t*)retbuf, count); + } else { + _dmaRX->destination(_dma_dummy_rx); // NULL ? + _dmaRX->transferCount(count); + } + + _dma_event_responder = &event_responder; + + //Serial.println("Before DMA C2"); + // Try pushing the first character + while (!(port().S & SPI_S_SPTEF)); + port().DL = first_char; + + port().C2 |= SPI_C2_TXDMAE | SPI_C2_RXDMAE; + + // Now make sure SPI is enabled. + port().C1 |= SPI_C1_SPE; + + _dmaRX->enable(); + _dmaTX->enable(); + _dma_state = DMAState::active; + return true; +} +#endif //SPI_HAS_TRANSFER_ASYNC + +#endif diff --git a/Firmware_V2/src/libraries/SPI/SPI.h b/Firmware_V2/src/libraries/SPI/SPI.h new file mode 100644 index 0000000..0b571a2 --- /dev/null +++ b/Firmware_V2/src/libraries/SPI/SPI.h @@ -0,0 +1,1036 @@ +/* + * Copyright (c) 2010 by Cristian Maglie + * Copyright (c) 2014 by Paul Stoffregen (Transaction API) + * Copyright (c) 2014 by Matthijs Kooijman (SPISettings AVR) + * SPI Master library for arduino. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#ifndef _SPI_H_INCLUDED +#define _SPI_H_INCLUDED + +#include + +#if defined(__arm__) && defined(TEENSYDUINO) +#if defined(__has_include) && __has_include() +// SPI_HAS_TRANSFER_ASYNC - Defined to say that the SPI supports an ASYNC version +// of the SPI_HAS_TRANSFER_BUF +#define SPI_HAS_TRANSFER_ASYNC 1 +#include +#include +#endif +#endif + +// SPI_HAS_TRANSACTION means SPI has beginTransaction(), endTransaction(), +// usingInterrupt(), and SPISetting(clock, bitOrder, dataMode) +#define SPI_HAS_TRANSACTION 1 + +// Uncomment this line to add detection of mismatched begin/end transactions. +// A mismatch occurs if other libraries fail to use SPI.endTransaction() for +// each SPI.beginTransaction(). Connect a LED to this pin. The LED will turn +// on if any mismatch is ever detected. +//#define SPI_TRANSACTION_MISMATCH_LED 5 + +// SPI_HAS_TRANSFER_BUF - is defined to signify that this library supports +// a version of transfer which allows you to pass in both TX and RX buffer +// pointers, either of which could be NULL +#define SPI_HAS_TRANSFER_BUF 1 + + +#ifndef LSBFIRST +#define LSBFIRST 0 +#endif +#ifndef MSBFIRST +#define MSBFIRST 1 +#endif + +#define SPI_MODE0 0x00 +#define SPI_MODE1 0x04 +#define SPI_MODE2 0x08 +#define SPI_MODE3 0x0C + +#define SPI_CLOCK_DIV4 0x00 +#define SPI_CLOCK_DIV16 0x01 +#define SPI_CLOCK_DIV64 0x02 +#define SPI_CLOCK_DIV128 0x03 +#define SPI_CLOCK_DIV2 0x04 +#define SPI_CLOCK_DIV8 0x05 +#define SPI_CLOCK_DIV32 0x06 + +#define SPI_MODE_MASK 0x0C // CPOL = bit 3, CPHA = bit 2 on SPCR +#define SPI_CLOCK_MASK 0x03 // SPR1 = bit 1, SPR0 = bit 0 on SPCR +#define SPI_2XCLOCK_MASK 0x01 // SPI2X = bit 0 on SPSR + + +/**********************************************************/ +/* 8 bit AVR-based boards */ +/**********************************************************/ + +#if defined(__AVR__) + +// define SPI_AVR_EIMSK for AVR boards with external interrupt pins +#if defined(EIMSK) + #define SPI_AVR_EIMSK EIMSK +#elif defined(GICR) + #define SPI_AVR_EIMSK GICR +#elif defined(GIMSK) + #define SPI_AVR_EIMSK GIMSK +#endif + +class SPISettings { +public: + SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) { + if (__builtin_constant_p(clock)) { + init_AlwaysInline(clock, bitOrder, dataMode); + } else { + init_MightInline(clock, bitOrder, dataMode); + } + } + SPISettings() { + init_AlwaysInline(4000000, MSBFIRST, SPI_MODE0); + } +private: + void init_MightInline(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) { + init_AlwaysInline(clock, bitOrder, dataMode); + } + void init_AlwaysInline(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) + __attribute__((__always_inline__)) { + // Clock settings are defined as follows. Note that this shows SPI2X + // inverted, so the bits form increasing numbers. Also note that + // fosc/64 appears twice + // SPR1 SPR0 ~SPI2X Freq + // 0 0 0 fosc/2 + // 0 0 1 fosc/4 + // 0 1 0 fosc/8 + // 0 1 1 fosc/16 + // 1 0 0 fosc/32 + // 1 0 1 fosc/64 + // 1 1 0 fosc/64 + // 1 1 1 fosc/128 + + // We find the fastest clock that is less than or equal to the + // given clock rate. The clock divider that results in clock_setting + // is 2 ^^ (clock_div + 1). If nothing is slow enough, we'll use the + // slowest (128 == 2 ^^ 7, so clock_div = 6). + uint8_t clockDiv; + + // When the clock is known at compiletime, use this if-then-else + // cascade, which the compiler knows how to completely optimize + // away. When clock is not known, use a loop instead, which generates + // shorter code. + if (__builtin_constant_p(clock)) { + if (clock >= F_CPU / 2) { + clockDiv = 0; + } else if (clock >= F_CPU / 4) { + clockDiv = 1; + } else if (clock >= F_CPU / 8) { + clockDiv = 2; + } else if (clock >= F_CPU / 16) { + clockDiv = 3; + } else if (clock >= F_CPU / 32) { + clockDiv = 4; + } else if (clock >= F_CPU / 64) { + clockDiv = 5; + } else { + clockDiv = 6; + } + } else { + uint32_t clockSetting = F_CPU / 2; + clockDiv = 0; + while (clockDiv < 6 && clock < clockSetting) { + clockSetting /= 2; + clockDiv++; + } + } + + // Compensate for the duplicate fosc/64 + if (clockDiv == 6) + clockDiv = 7; + + // Invert the SPI2X bit + clockDiv ^= 0x1; + + // Pack into the SPISettings class + spcr = _BV(SPE) | _BV(MSTR) | ((bitOrder == LSBFIRST) ? _BV(DORD) : 0) | + (dataMode & SPI_MODE_MASK) | ((clockDiv >> 1) & SPI_CLOCK_MASK); + spsr = clockDiv & SPI_2XCLOCK_MASK; + } + uint8_t spcr; + uint8_t spsr; + friend class SPIClass; +}; + + + +class SPIClass { // AVR +public: + // Initialize the SPI library + static void begin(); + + // If SPI is used from within an interrupt, this function registers + // that interrupt with the SPI library, so beginTransaction() can + // prevent conflicts. The input interruptNumber is the number used + // with attachInterrupt. If SPI is used from a different interrupt + // (eg, a timer), interruptNumber should be 255. + static void usingInterrupt(uint8_t interruptNumber); + + // Before using SPI.transfer() or asserting chip select pins, + // this function is used to gain exclusive access to the SPI bus + // and configure the correct settings. + inline static void beginTransaction(SPISettings settings) { + if (interruptMode > 0) { + #ifdef SPI_AVR_EIMSK + if (interruptMode == 1) { + interruptSave = SPI_AVR_EIMSK; + SPI_AVR_EIMSK &= ~interruptMask; + } else + #endif + { + uint8_t tmp = SREG; + cli(); + interruptSave = tmp; + } + } + #ifdef SPI_TRANSACTION_MISMATCH_LED + if (inTransactionFlag) { + pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT); + digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH); + } + inTransactionFlag = 1; + #endif + SPCR = settings.spcr; + SPSR = settings.spsr; + } + + // Write to the SPI bus (MOSI pin) and also receive (MISO pin) + inline static uint8_t transfer(uint8_t data) { + SPDR = data; + asm volatile("nop"); + while (!(SPSR & _BV(SPIF))) ; // wait + return SPDR; + } + inline static uint16_t transfer16(uint16_t data) { + union { uint16_t val; struct { uint8_t lsb; uint8_t msb; }; } in, out; + in.val = data; + if ((SPCR & _BV(DORD))) { + SPDR = in.lsb; + asm volatile("nop"); + while (!(SPSR & _BV(SPIF))) ; + out.lsb = SPDR; + SPDR = in.msb; + asm volatile("nop"); + while (!(SPSR & _BV(SPIF))) ; + out.msb = SPDR; + } else { + SPDR = in.msb; + asm volatile("nop"); + while (!(SPSR & _BV(SPIF))) ; + out.msb = SPDR; + SPDR = in.lsb; + asm volatile("nop"); + while (!(SPSR & _BV(SPIF))) ; + out.lsb = SPDR; + } + return out.val; + } + inline static void transfer(void *buf, size_t count) { + if (count == 0) return; + uint8_t *p = (uint8_t *)buf; + SPDR = *p; + while (--count > 0) { + uint8_t out = *(p + 1); + while (!(SPSR & _BV(SPIF))) ; + uint8_t in = SPDR; + SPDR = out; + *p++ = in; + } + while (!(SPSR & _BV(SPIF))) ; + *p = SPDR; + } + static void setTransferWriteFill(uint8_t ch ) {_transferWriteFill = ch;} + static void transfer(const void * buf, void * retbuf, uint32_t count); + + // After performing a group of transfers and releasing the chip select + // signal, this function allows others to access the SPI bus + inline static void endTransaction(void) { + #ifdef SPI_TRANSACTION_MISMATCH_LED + if (!inTransactionFlag) { + pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT); + digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH); + } + inTransactionFlag = 0; + #endif + if (interruptMode > 0) { + #ifdef SPI_AVR_EIMSK + if (interruptMode == 1) { + SPI_AVR_EIMSK = interruptSave; + } else + #endif + { + SREG = interruptSave; + } + } + } + + // Disable the SPI bus + static void end(); + + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + inline static void setBitOrder(uint8_t bitOrder) { + if (bitOrder == LSBFIRST) SPCR |= _BV(DORD); + else SPCR &= ~(_BV(DORD)); + } + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + inline static void setDataMode(uint8_t dataMode) { + SPCR = (SPCR & ~SPI_MODE_MASK) | dataMode; + } + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + inline static void setClockDivider(uint8_t clockDiv) { + SPCR = (SPCR & ~SPI_CLOCK_MASK) | (clockDiv & SPI_CLOCK_MASK); + SPSR = (SPSR & ~SPI_2XCLOCK_MASK) | ((clockDiv >> 2) & SPI_2XCLOCK_MASK); + } + // These undocumented functions should not be used. SPI.transfer() + // polls the hardware flag which is automatically cleared as the + // AVR responds to SPI's interrupt + inline static void attachInterrupt() { SPCR |= _BV(SPIE); } + inline static void detachInterrupt() { SPCR &= ~_BV(SPIE); } + +private: + static uint8_t interruptMode; // 0=none, 1=mask, 2=global + static uint8_t interruptMask; // which interrupts to mask + static uint8_t interruptSave; // temp storage, to restore state + #ifdef SPI_TRANSACTION_MISMATCH_LED + static uint8_t inTransactionFlag; + #endif + static uint8_t _transferWriteFill; +}; + + + +/**********************************************************/ +/* 32 bit Teensy 3.x */ +/**********************************************************/ + +#elif defined(__arm__) && defined(TEENSYDUINO) && defined(KINETISK) + +#define SPI_HAS_NOTUSINGINTERRUPT 1 + +class SPISettings { +public: + SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) { + if (__builtin_constant_p(clock)) { + init_AlwaysInline(clock, bitOrder, dataMode); + } else { + init_MightInline(clock, bitOrder, dataMode); + } + } + SPISettings() { + init_AlwaysInline(4000000, MSBFIRST, SPI_MODE0); + } +private: + void init_MightInline(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) { + init_AlwaysInline(clock, bitOrder, dataMode); + } + void init_AlwaysInline(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) + __attribute__((__always_inline__)) { + uint32_t t, c = SPI_CTAR_FMSZ(7); + if (bitOrder == LSBFIRST) c |= SPI_CTAR_LSBFE; + if (__builtin_constant_p(clock)) { + if (clock >= F_BUS / 2) { + t = SPI_CTAR_PBR(0) | SPI_CTAR_BR(0) | SPI_CTAR_DBR + | SPI_CTAR_CSSCK(0); + } else if (clock >= F_BUS / 3) { + t = SPI_CTAR_PBR(1) | SPI_CTAR_BR(0) | SPI_CTAR_DBR + | SPI_CTAR_CSSCK(0); + } else if (clock >= F_BUS / 4) { + t = SPI_CTAR_PBR(0) | SPI_CTAR_BR(0) | SPI_CTAR_CSSCK(0); + } else if (clock >= F_BUS / 5) { + t = SPI_CTAR_PBR(2) | SPI_CTAR_BR(0) | SPI_CTAR_DBR + | SPI_CTAR_CSSCK(0); + } else if (clock >= F_BUS / 6) { + t = SPI_CTAR_PBR(1) | SPI_CTAR_BR(0) | SPI_CTAR_CSSCK(0); + } else if (clock >= F_BUS / 8) { + t = SPI_CTAR_PBR(0) | SPI_CTAR_BR(1) | SPI_CTAR_CSSCK(1); + } else if (clock >= F_BUS / 10) { + t = SPI_CTAR_PBR(2) | SPI_CTAR_BR(0) | SPI_CTAR_CSSCK(0); + } else if (clock >= F_BUS / 12) { + t = SPI_CTAR_PBR(1) | SPI_CTAR_BR(1) | SPI_CTAR_CSSCK(1); + } else if (clock >= F_BUS / 16) { + t = SPI_CTAR_PBR(0) | SPI_CTAR_BR(3) | SPI_CTAR_CSSCK(2); + } else if (clock >= F_BUS / 20) { + t = SPI_CTAR_PBR(2) | SPI_CTAR_BR(1) | SPI_CTAR_CSSCK(0); + } else if (clock >= F_BUS / 24) { + t = SPI_CTAR_PBR(1) | SPI_CTAR_BR(3) | SPI_CTAR_CSSCK(2); + } else if (clock >= F_BUS / 32) { + t = SPI_CTAR_PBR(0) | SPI_CTAR_BR(4) | SPI_CTAR_CSSCK(3); + } else if (clock >= F_BUS / 40) { + t = SPI_CTAR_PBR(2) | SPI_CTAR_BR(3) | SPI_CTAR_CSSCK(2); + } else if (clock >= F_BUS / 56) { + t = SPI_CTAR_PBR(3) | SPI_CTAR_BR(3) | SPI_CTAR_CSSCK(2); + } else if (clock >= F_BUS / 64) { + t = SPI_CTAR_PBR(0) | SPI_CTAR_BR(5) | SPI_CTAR_CSSCK(4); + } else if (clock >= F_BUS / 96) { + t = SPI_CTAR_PBR(1) | SPI_CTAR_BR(5) | SPI_CTAR_CSSCK(4); + } else if (clock >= F_BUS / 128) { + t = SPI_CTAR_PBR(0) | SPI_CTAR_BR(6) | SPI_CTAR_CSSCK(5); + } else if (clock >= F_BUS / 192) { + t = SPI_CTAR_PBR(1) | SPI_CTAR_BR(6) | SPI_CTAR_CSSCK(5); + } else if (clock >= F_BUS / 256) { + t = SPI_CTAR_PBR(0) | SPI_CTAR_BR(7) | SPI_CTAR_CSSCK(6); + } else if (clock >= F_BUS / 384) { + t = SPI_CTAR_PBR(1) | SPI_CTAR_BR(7) | SPI_CTAR_CSSCK(6); + } else if (clock >= F_BUS / 512) { + t = SPI_CTAR_PBR(0) | SPI_CTAR_BR(8) | SPI_CTAR_CSSCK(7); + } else if (clock >= F_BUS / 640) { + t = SPI_CTAR_PBR(2) | SPI_CTAR_BR(7) | SPI_CTAR_CSSCK(6); + } else { /* F_BUS / 768 */ + t = SPI_CTAR_PBR(1) | SPI_CTAR_BR(8) | SPI_CTAR_CSSCK(7); + } + } else { + for (uint32_t i=0; i<23; i++) { + t = ctar_clock_table[i]; + if (clock >= F_BUS / ctar_div_table[i]) break; + } + } + if (dataMode & 0x08) { + c |= SPI_CTAR_CPOL; + } + if (dataMode & 0x04) { + c |= SPI_CTAR_CPHA; + t = (t & 0xFFFF0FFF) | ((t & 0xF000) >> 4); + } + ctar = c | t; + } + static const uint16_t ctar_div_table[23]; + static const uint32_t ctar_clock_table[23]; + uint32_t ctar; + friend class SPIClass; +}; + + + +class SPIClass { // Teensy 3.x +public: +#if defined(__MK20DX128__) || defined(__MK20DX256__) + static const uint8_t CNT_MISO_PINS = 2; + static const uint8_t CNT_MOSI_PINS = 2; + static const uint8_t CNT_SCK_PINS = 2; + static const uint8_t CNT_CS_PINS = 9; +#elif defined(__MK64FX512__) || defined(__MK66FX1M0__) + static const uint8_t CNT_MISO_PINS = 4; + static const uint8_t CNT_MOSI_PINS = 4; + static const uint8_t CNT_SCK_PINS = 3; + static const uint8_t CNT_CS_PINS = 11; +#endif + typedef struct { + volatile uint32_t &clock_gate_register; + uint32_t clock_gate_mask; + uint8_t queue_size; + uint8_t spi_irq; + uint32_t max_dma_count; + uint8_t tx_dma_channel; + uint8_t rx_dma_channel; + void (*dma_rxisr)(); + uint8_t miso_pin[CNT_MISO_PINS]; + uint32_t miso_mux[CNT_MISO_PINS]; + uint8_t mosi_pin[CNT_MOSI_PINS]; + uint32_t mosi_mux[CNT_MOSI_PINS]; + uint8_t sck_pin[CNT_SCK_PINS]; + uint32_t sck_mux[CNT_SCK_PINS]; + uint8_t cs_pin[CNT_CS_PINS]; + uint32_t cs_mux[CNT_CS_PINS]; + uint8_t cs_mask[CNT_CS_PINS]; + } SPI_Hardware_t; + static const SPI_Hardware_t spi0_hardware; + static const SPI_Hardware_t spi1_hardware; + static const SPI_Hardware_t spi2_hardware; + + enum DMAState { notAllocated, idle, active, completed}; +public: + constexpr SPIClass(uintptr_t myport, uintptr_t myhardware) + : port_addr(myport), hardware_addr(myhardware) { + } + // Initialize the SPI library + void begin(); + + // If SPI is to used from within an interrupt, this function registers + // that interrupt with the SPI library, so beginTransaction() can + // prevent conflicts. The input interruptNumber is the number used + // with attachInterrupt. If SPI is used from a different interrupt + // (eg, a timer), interruptNumber should be 255. + void usingInterrupt(uint8_t n) { + if (n == 3 || n == 4 || n == 24 || n == 33) { + usingInterrupt(IRQ_PORTA); + } else if (n == 0 || n == 1 || (n >= 16 && n <= 19) || n == 25 || n == 32) { + usingInterrupt(IRQ_PORTB); + } else if ((n >= 9 && n <= 13) || n == 15 || n == 22 || n == 23 + || (n >= 27 && n <= 30)) { + usingInterrupt(IRQ_PORTC); + } else if (n == 2 || (n >= 5 && n <= 8) || n == 14 || n == 20 || n == 21) { + usingInterrupt(IRQ_PORTD); + } else if (n == 26 || n == 31) { + usingInterrupt(IRQ_PORTE); + } + } + void usingInterrupt(IRQ_NUMBER_t interruptName); + void notUsingInterrupt(IRQ_NUMBER_t interruptName); + + // Before using SPI.transfer() or asserting chip select pins, + // this function is used to gain exclusive access to the SPI bus + // and configure the correct settings. + void beginTransaction(SPISettings settings) { + if (interruptMasksUsed) { + __disable_irq(); + if (interruptMasksUsed & 0x01) { + interruptSave[0] = NVIC_ICER0 & interruptMask[0]; + NVIC_ICER0 = interruptSave[0]; + } + #if NVIC_NUM_INTERRUPTS > 32 + if (interruptMasksUsed & 0x02) { + interruptSave[1] = NVIC_ICER1 & interruptMask[1]; + NVIC_ICER1 = interruptSave[1]; + } + #endif + #if NVIC_NUM_INTERRUPTS > 64 && defined(NVIC_ISER2) + if (interruptMasksUsed & 0x04) { + interruptSave[2] = NVIC_ICER2 & interruptMask[2]; + NVIC_ICER2 = interruptSave[2]; + } + #endif + #if NVIC_NUM_INTERRUPTS > 96 && defined(NVIC_ISER3) + if (interruptMasksUsed & 0x08) { + interruptSave[3] = NVIC_ICER3 & interruptMask[3]; + NVIC_ICER3 = interruptSave[3]; + } + #endif + __enable_irq(); + } + #ifdef SPI_TRANSACTION_MISMATCH_LED + if (inTransactionFlag) { + pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT); + digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH); + } + inTransactionFlag = 1; + #endif + if (port().CTAR0 != settings.ctar) { + port().MCR = SPI_MCR_MDIS | SPI_MCR_HALT | SPI_MCR_PCSIS(0x3F); + port().CTAR0 = settings.ctar; + port().CTAR1 = settings.ctar| SPI_CTAR_FMSZ(8); + port().MCR = SPI_MCR_MSTR | SPI_MCR_PCSIS(0x3F); + } + } + + // Write to the SPI bus (MOSI pin) and also receive (MISO pin) + uint8_t transfer(uint8_t data) { + port().SR = SPI_SR_TCF; + port().PUSHR = data; + while (!(port().SR & SPI_SR_TCF)) ; // wait + return port().POPR; + } + uint16_t transfer16(uint16_t data) { + port().SR = SPI_SR_TCF; + port().PUSHR = data | SPI_PUSHR_CTAS(1); + while (!(port().SR & SPI_SR_TCF)) ; // wait + return port().POPR; + } + + void inline transfer(void *buf, size_t count) {transfer(buf, buf, count);} + void setTransferWriteFill(uint8_t ch ) {_transferWriteFill = ch;} + void transfer(const void * buf, void * retbuf, size_t count); + + // Asynch support (DMA ) +#ifdef SPI_HAS_TRANSFER_ASYNC + bool transfer(const void *txBuffer, void *rxBuffer, size_t count, EventResponderRef event_responder); + + friend void _spi_dma_rxISR0(void); + friend void _spi_dma_rxISR1(void); + friend void _spi_dma_rxISR2(void); + + inline void dma_rxisr(void); +#endif + + + // After performing a group of transfers and releasing the chip select + // signal, this function allows others to access the SPI bus + void endTransaction(void) { + #ifdef SPI_TRANSACTION_MISMATCH_LED + if (!inTransactionFlag) { + pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT); + digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH); + } + inTransactionFlag = 0; + #endif + if (interruptMasksUsed) { + if (interruptMasksUsed & 0x01) { + NVIC_ISER0 = interruptSave[0]; + } + #if NVIC_NUM_INTERRUPTS > 32 + if (interruptMasksUsed & 0x02) { + NVIC_ISER1 = interruptSave[1]; + } + #endif + #if NVIC_NUM_INTERRUPTS > 64 && defined(NVIC_ISER2) + if (interruptMasksUsed & 0x04) { + NVIC_ISER2 = interruptSave[2]; + } + #endif + #if NVIC_NUM_INTERRUPTS > 96 && defined(NVIC_ISER3) + if (interruptMasksUsed & 0x08) { + NVIC_ISER3 = interruptSave[3]; + } + #endif + } + } + + // Disable the SPI bus + void end(); + + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + void setBitOrder(uint8_t bitOrder); + + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + void setDataMode(uint8_t dataMode); + + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + void setClockDivider(uint8_t clockDiv) { + if (clockDiv == SPI_CLOCK_DIV2) { + setClockDivider_noInline(SPISettings(12000000, MSBFIRST, SPI_MODE0).ctar); + } else if (clockDiv == SPI_CLOCK_DIV4) { + setClockDivider_noInline(SPISettings(4000000, MSBFIRST, SPI_MODE0).ctar); + } else if (clockDiv == SPI_CLOCK_DIV8) { + setClockDivider_noInline(SPISettings(2000000, MSBFIRST, SPI_MODE0).ctar); + } else if (clockDiv == SPI_CLOCK_DIV16) { + setClockDivider_noInline(SPISettings(1000000, MSBFIRST, SPI_MODE0).ctar); + } else if (clockDiv == SPI_CLOCK_DIV32) { + setClockDivider_noInline(SPISettings(500000, MSBFIRST, SPI_MODE0).ctar); + } else if (clockDiv == SPI_CLOCK_DIV64) { + setClockDivider_noInline(SPISettings(250000, MSBFIRST, SPI_MODE0).ctar); + } else { /* clockDiv == SPI_CLOCK_DIV128 */ + setClockDivider_noInline(SPISettings(125000, MSBFIRST, SPI_MODE0).ctar); + } + } + void setClockDivider_noInline(uint32_t clk); + + // These undocumented functions should not be used. SPI.transfer() + // polls the hardware flag which is automatically cleared as the + // AVR responds to SPI's interrupt + void attachInterrupt() { } + void detachInterrupt() { } + + // Teensy 3.x can use alternate pins for these 3 SPI signals. + void setMOSI(uint8_t pin); + void setMISO(uint8_t pin); + void setSCK(uint8_t pin); + + // return true if "pin" has special chip select capability + uint8_t pinIsChipSelect(uint8_t pin); + bool pinIsMOSI(uint8_t pin); + bool pinIsMISO(uint8_t pin); + bool pinIsSCK(uint8_t pin); + // return true if both pin1 and pin2 have independent chip select capability + bool pinIsChipSelect(uint8_t pin1, uint8_t pin2); + // configure a pin for chip select and return its SPI_MCR_PCSIS bitmask + // setCS() is a special function, not intended for use from normal Arduino + // programs/sketches. See the ILI3941_t3 library for an example. + uint8_t setCS(uint8_t pin); + +private: + KINETISK_SPI_t & port() { return *(KINETISK_SPI_t *)port_addr; } + const SPI_Hardware_t & hardware() { return *(const SPI_Hardware_t *)hardware_addr; } + void updateCTAR(uint32_t ctar); + uintptr_t port_addr; + uintptr_t hardware_addr; + uint8_t miso_pin_index = 0; + uint8_t mosi_pin_index = 0; + uint8_t sck_pin_index = 0; + uint8_t interruptMasksUsed = 0; + uint32_t interruptMask[(NVIC_NUM_INTERRUPTS+31)/32] = {}; + uint32_t interruptSave[(NVIC_NUM_INTERRUPTS+31)/32] = {}; + #ifdef SPI_TRANSACTION_MISMATCH_LED + uint8_t inTransactionFlag = 0; + #endif + + uint8_t _transferWriteFill = 0; + + // DMA Support +#ifdef SPI_HAS_TRANSFER_ASYNC + bool initDMAChannels(); + DMAState _dma_state = DMAState::notAllocated; + uint32_t _dma_count_remaining = 0; // How many bytes left to output after current DMA completes + DMAChannel *_dmaTX = nullptr; + DMAChannel *_dmaRX = nullptr; + EventResponder *_dma_event_responder = nullptr; +#endif +}; + + + +/**********************************************************/ +/* 32 bit Teensy-LC */ +/**********************************************************/ + +#elif defined(__arm__) && defined(TEENSYDUINO) && defined(KINETISL) + +class SPISettings { +public: + SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) { + if (__builtin_constant_p(clock)) { + init_AlwaysInline(clock, bitOrder, dataMode); + } else { + init_MightInline(clock, bitOrder, dataMode); + } + } + SPISettings() { + init_AlwaysInline(4000000, MSBFIRST, SPI_MODE0); + } +private: + void init_MightInline(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) { + init_AlwaysInline(clock, bitOrder, dataMode); + } + void init_AlwaysInline(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) + __attribute__((__always_inline__)) { + uint8_t c = SPI_C1_MSTR | SPI_C1_SPE; + if (dataMode & 0x04) c |= SPI_C1_CPHA; + if (dataMode & 0x08) c |= SPI_C1_CPOL; + if (bitOrder == LSBFIRST) c |= SPI_C1_LSBFE; + c1 = c; + if (__builtin_constant_p(clock)) { + if (clock >= F_BUS / 2) { c = SPI_BR_SPPR(0) | SPI_BR_SPR(0); + } else if (clock >= F_BUS / 4) { c = SPI_BR_SPPR(1) | SPI_BR_SPR(0); + } else if (clock >= F_BUS / 6) { c = SPI_BR_SPPR(2) | SPI_BR_SPR(0); + } else if (clock >= F_BUS / 8) { c = SPI_BR_SPPR(3) | SPI_BR_SPR(0); + } else if (clock >= F_BUS / 10) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(0); + } else if (clock >= F_BUS / 12) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(0); + } else if (clock >= F_BUS / 14) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(0); + } else if (clock >= F_BUS / 16) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(0); + } else if (clock >= F_BUS / 20) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(1); + } else if (clock >= F_BUS / 24) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(1); + } else if (clock >= F_BUS / 28) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(1); + } else if (clock >= F_BUS / 32) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(1); + } else if (clock >= F_BUS / 40) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(2); + } else if (clock >= F_BUS / 48) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(2); + } else if (clock >= F_BUS / 56) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(2); + } else if (clock >= F_BUS / 64) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(2); + } else if (clock >= F_BUS / 80) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(3); + } else if (clock >= F_BUS / 96) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(3); + } else if (clock >= F_BUS / 112) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(3); + } else if (clock >= F_BUS / 128) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(3); + } else if (clock >= F_BUS / 160) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(4); + } else if (clock >= F_BUS / 192) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(4); + } else if (clock >= F_BUS / 224) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(4); + } else if (clock >= F_BUS / 256) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(4); + } else if (clock >= F_BUS / 320) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(5); + } else if (clock >= F_BUS / 384) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(5); + } else if (clock >= F_BUS / 448) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(5); + } else if (clock >= F_BUS / 512) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(5); + } else if (clock >= F_BUS / 640) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(6); + } else /* F_BUS / 768 */ { c = SPI_BR_SPPR(5) | SPI_BR_SPR(6); + } + } else { + for (uint32_t i=0; i<30; i++) { + c = br_clock_table[i]; + if (clock >= F_BUS / br_div_table[i]) break; + } + } + br[0] = c; + if (__builtin_constant_p(clock)) { + if (clock >= (F_PLL/2) / 2) { c = SPI_BR_SPPR(0) | SPI_BR_SPR(0); + } else if (clock >= (F_PLL/2) / 4) { c = SPI_BR_SPPR(1) | SPI_BR_SPR(0); + } else if (clock >= (F_PLL/2) / 6) { c = SPI_BR_SPPR(2) | SPI_BR_SPR(0); + } else if (clock >= (F_PLL/2) / 8) { c = SPI_BR_SPPR(3) | SPI_BR_SPR(0); + } else if (clock >= (F_PLL/2) / 10) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(0); + } else if (clock >= (F_PLL/2) / 12) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(0); + } else if (clock >= (F_PLL/2) / 14) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(0); + } else if (clock >= (F_PLL/2) / 16) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(0); + } else if (clock >= (F_PLL/2) / 20) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(1); + } else if (clock >= (F_PLL/2) / 24) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(1); + } else if (clock >= (F_PLL/2) / 28) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(1); + } else if (clock >= (F_PLL/2) / 32) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(1); + } else if (clock >= (F_PLL/2) / 40) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(2); + } else if (clock >= (F_PLL/2) / 48) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(2); + } else if (clock >= (F_PLL/2) / 56) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(2); + } else if (clock >= (F_PLL/2) / 64) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(2); + } else if (clock >= (F_PLL/2) / 80) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(3); + } else if (clock >= (F_PLL/2) / 96) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(3); + } else if (clock >= (F_PLL/2) / 112) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(3); + } else if (clock >= (F_PLL/2) / 128) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(3); + } else if (clock >= (F_PLL/2) / 160) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(4); + } else if (clock >= (F_PLL/2) / 192) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(4); + } else if (clock >= (F_PLL/2) / 224) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(4); + } else if (clock >= (F_PLL/2) / 256) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(4); + } else if (clock >= (F_PLL/2) / 320) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(5); + } else if (clock >= (F_PLL/2) / 384) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(5); + } else if (clock >= (F_PLL/2) / 448) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(5); + } else if (clock >= (F_PLL/2) / 512) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(5); + } else if (clock >= (F_PLL/2) / 640) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(6); + } else /* (F_PLL/2) / 768 */ { c = SPI_BR_SPPR(5) | SPI_BR_SPR(6); + } + } else { + for (uint32_t i=0; i<30; i++) { + c = br_clock_table[i]; + if (clock >= (F_PLL/2) / br_div_table[i]) break; + } + } + br[1] = c; + } + static const uint8_t br_clock_table[30]; + static const uint16_t br_div_table[30]; + uint8_t c1, br[2]; + friend class SPIClass; +}; + + +class SPIClass { // Teensy-LC +public: + static const uint8_t CNT_MISO_PINS = 2; + static const uint8_t CNT_MMOSI_PINS = 2; + static const uint8_t CNT_SCK_PINS = 2; + static const uint8_t CNT_CS_PINS = 2; + typedef struct { + volatile uint32_t &clock_gate_register; + uint32_t clock_gate_mask; + uint8_t br_index; + uint8_t tx_dma_channel; + uint8_t rx_dma_channel; + void (*dma_isr)(); + uint8_t miso_pin[CNT_MISO_PINS]; + uint32_t miso_mux[CNT_MISO_PINS]; + uint8_t mosi_pin[CNT_MMOSI_PINS]; + uint32_t mosi_mux[CNT_MMOSI_PINS]; + uint8_t sck_pin[CNT_SCK_PINS]; + uint32_t sck_mux[CNT_SCK_PINS]; + uint8_t cs_pin[CNT_CS_PINS]; + uint32_t cs_mux[CNT_CS_PINS]; + uint8_t cs_mask[CNT_CS_PINS]; + } SPI_Hardware_t; + static const SPI_Hardware_t spi0_hardware; + static const SPI_Hardware_t spi1_hardware; + enum DMAState { notAllocated, idle, active, completed}; +public: + constexpr SPIClass(uintptr_t myport, uintptr_t myhardware) + : port_addr(myport), hardware_addr(myhardware) { + } + // Initialize the SPI library + void begin(); + + // If SPI is to used from within an interrupt, this function registers + // that interrupt with the SPI library, so beginTransaction() can + // prevent conflicts. The input interruptNumber is the number used + // with attachInterrupt. If SPI is used from a different interrupt + // (eg, a timer), interruptNumber should be 255. + void usingInterrupt(uint8_t n) { + if (n == 3 || n == 4) { + usingInterrupt(IRQ_PORTA); + } else if ((n >= 2 && n <= 15) || (n >= 20 && n <= 23)) { + usingInterrupt(IRQ_PORTCD); + } + } + void usingInterrupt(IRQ_NUMBER_t interruptName) { + uint32_t n = (uint32_t)interruptName; + if (n < NVIC_NUM_INTERRUPTS) interruptMask |= (1 << n); + } + void notUsingInterrupt(IRQ_NUMBER_t interruptName) { + uint32_t n = (uint32_t)interruptName; + if (n < NVIC_NUM_INTERRUPTS) interruptMask &= ~(1 << n); + } + + // Before using SPI.transfer() or asserting chip select pins, + // this function is used to gain exclusive access to the SPI bus + // and configure the correct settings. + void beginTransaction(SPISettings settings) { + if (interruptMask) { + __disable_irq(); + interruptSave = NVIC_ICER0 & interruptMask; + NVIC_ICER0 = interruptSave; + __enable_irq(); + } + #ifdef SPI_TRANSACTION_MISMATCH_LED + if (inTransactionFlag) { + pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT); + digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH); + } + inTransactionFlag = 1; + #endif + port().C1 = settings.c1; + port().BR = settings.br[hardware().br_index]; + } + + // Write to the SPI bus (MOSI pin) and also receive (MISO pin) + uint8_t transfer(uint8_t data) { + port().DL = data; + while (!(port().S & SPI_S_SPRF)) ; // wait + return port().DL; + } + uint16_t transfer16(uint16_t data) { + port().C2 = SPI_C2_SPIMODE; + port().S; + port().DL = data; + port().DH = data >> 8; + while (!(port().S & SPI_S_SPRF)) ; // wait + uint16_t r = port().DL | (port().DH << 8); + port().C2 = 0; + port().S; + return r; + } + void transfer(void *buf, size_t count) { + if (count == 0) return; + uint8_t *p = (uint8_t *)buf; + while (!(port().S & SPI_S_SPTEF)) ; // wait + port().DL = *p; + while (--count > 0) { + uint8_t out = *(p + 1); + while (!(port().S & SPI_S_SPTEF)) ; // wait + __disable_irq(); + port().DL = out; + while (!(port().S & SPI_S_SPRF)) ; // wait + uint8_t in = port().DL; + __enable_irq(); + *p++ = in; + } + while (!(port().S & SPI_S_SPRF)) ; // wait + *p = port().DL; + } + + void setTransferWriteFill(uint8_t ch ) {_transferWriteFill = ch;} + void transfer(const void * buf, void * retbuf, size_t count); + + // Asynch support (DMA ) +#ifdef SPI_HAS_TRANSFER_ASYNC + bool transfer(const void *txBuffer, void *rxBuffer, size_t count, EventResponderRef event_responder); + + friend void _spi_dma_rxISR0(void); + friend void _spi_dma_rxISR1(void); + inline void dma_isr(void); +#endif + + // After performing a group of transfers and releasing the chip select + // signal, this function allows others to access the SPI bus + void endTransaction(void) { + #ifdef SPI_TRANSACTION_MISMATCH_LED + if (!inTransactionFlag) { + pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT); + digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH); + } + inTransactionFlag = 0; + #endif + if (interruptMask) { + NVIC_ISER0 = interruptSave; + } + } + + // Disable the SPI bus + void end(); + + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + void setBitOrder(uint8_t bitOrder) { + uint8_t c = port().C1 | SPI_C1_SPE; + if (bitOrder == LSBFIRST) c |= SPI_C1_LSBFE; + else c &= ~SPI_C1_LSBFE; + port().C1 = c; + } + + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + void setDataMode(uint8_t dataMode) { + uint8_t c = port().C1 | SPI_C1_SPE; + if (dataMode & 0x04) c |= SPI_C1_CPHA; + else c &= ~SPI_C1_CPHA; + if (dataMode & 0x08) c |= SPI_C1_CPOL; + else c &= ~SPI_C1_CPOL; + port().C1 = c; + } + + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + void setClockDivider(uint8_t clockDiv) { + unsigned int i = hardware().br_index; + if (clockDiv == SPI_CLOCK_DIV2) { + port().BR = (SPISettings(12000000, MSBFIRST, SPI_MODE0).br[i]); + } else if (clockDiv == SPI_CLOCK_DIV4) { + port().BR = (SPISettings(4000000, MSBFIRST, SPI_MODE0).br[i]); + } else if (clockDiv == SPI_CLOCK_DIV8) { + port().BR = (SPISettings(2000000, MSBFIRST, SPI_MODE0).br[i]); + } else if (clockDiv == SPI_CLOCK_DIV16) { + port().BR = (SPISettings(1000000, MSBFIRST, SPI_MODE0).br[i]); + } else if (clockDiv == SPI_CLOCK_DIV32) { + port().BR = (SPISettings(500000, MSBFIRST, SPI_MODE0).br[i]); + } else if (clockDiv == SPI_CLOCK_DIV64) { + port().BR = (SPISettings(250000, MSBFIRST, SPI_MODE0).br[i]); + } else { /* clockDiv == SPI_CLOCK_DIV128 */ + port().BR = (SPISettings(125000, MSBFIRST, SPI_MODE0).br[i]); + } + } + + // These undocumented functions should not be used. SPI.transfer() + // polls the hardware flag which is automatically cleared as the + // AVR responds to SPI's interrupt + void attachInterrupt() { } + void detachInterrupt() { } + + // Teensy LC can use alternate pins for these 3 SPI signals. + void setMOSI(uint8_t pin); + void setMISO(uint8_t pin); + void setSCK(uint8_t pin); + // return true if "pin" has special chip select capability + bool pinIsChipSelect(uint8_t pin); + bool pinIsMOSI(uint8_t pin); + bool pinIsMISO(uint8_t pin); + bool pinIsSCK(uint8_t pin); + // return true if both pin1 and pin2 have independent chip select capability + bool pinIsChipSelect(uint8_t pin1, uint8_t pin2) { return false; } + // configure a pin for chip select and return its SPI_MCR_PCSIS bitmask + // setCS() is a special function, not intended for use from normal Arduino + // programs/sketches. See the ILI3941_t3 library for an example. + uint8_t setCS(uint8_t pin); + +private: + KINETISL_SPI_t & port() { return *(KINETISL_SPI_t *)port_addr; } + const SPI_Hardware_t & hardware() { return *(const SPI_Hardware_t *)hardware_addr; } + uintptr_t port_addr; + uintptr_t hardware_addr; + uint32_t interruptMask = 0; + uint32_t interruptSave = 0; + uint8_t mosi_pin_index = 0; + uint8_t miso_pin_index = 0; + uint8_t sck_pin_index = 0; + #ifdef SPI_TRANSACTION_MISMATCH_LED + uint8_t inTransactionFlag = 0; + #endif + uint8_t _transferWriteFill = 0; +#ifdef SPI_HAS_TRANSFER_ASYNC + // DMA Support + bool initDMAChannels(); + + DMAState _dma_state = DMAState::notAllocated; + uint32_t _dma_count_remaining = 0; // How many bytes left to output after current DMA completes + DMAChannel *_dmaTX = nullptr; + DMAChannel *_dmaRX = nullptr; + EventResponder *_dma_event_responder = nullptr; +#endif +}; + + +#endif + + + +extern SPIClass SPI; +#if defined(__MKL26Z64__) +extern SPIClass SPI1; +#endif +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) +extern SPIClass SPI1; +extern SPIClass SPI2; +#endif +#endif diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/ArduinoFiles.h b/Firmware_V2/src/libraries/SdFat/FatLib/ArduinoFiles.h new file mode 100644 index 0000000..da8e8cb --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/ArduinoFiles.h @@ -0,0 +1,248 @@ +/* Arduino SdFat Library + * Copyright (C) 2012 by William Greiman + * + * This file is part of the Arduino SdFat Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino SdFat Library. If not, see + * . + */ +/** + * \file + * \brief PrintFile class + */ +#ifndef ArduinoFiles_h +#define ArduinoFiles_h +#include "FatLibConfig.h" +#if ENABLE_ARDUINO_FEATURES +#include "FatFile.h" +#include +//------------------------------------------------------------------------------ +/** Arduino SD.h style flag for open for read. */ +#define FILE_READ O_READ +/** Arduino SD.h style flag for open at EOF for read/write with create. */ +#define FILE_WRITE (O_RDWR | O_CREAT | O_AT_END) +//============================================================================== +/** + * \class PrintFile + * \brief FatFile with Print. + */ +class PrintFile : public FatFile, public Print { + public: + PrintFile() {} + /** Create a file object and open it in the current working directory. + * + * \param[in] path A path for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a + * bitwise-inclusive OR of open flags. see + * FatFile::open(FatFile*, const char*, uint8_t). + */ + PrintFile(const char* path, uint8_t oflag) : FatFile(path, oflag) {} +#if DESTRUCTOR_CLOSES_FILE + ~PrintFile() {} +#endif // DESTRUCTOR_CLOSES_FILE + using FatFile::clearWriteError; + using FatFile::getWriteError; + using FatFile::read; + using FatFile::write; + /** \return number of bytes available from the current position to EOF + * or INT_MAX if more than INT_MAX bytes are available. + */ + int available() { + uint32_t n = FatFile::available(); + return n > INT_MAX ? INT_MAX : n; + } + /** Ensure that any bytes written to the file are saved to the SD card. */ + void flush() { + FatFile::sync(); + } + /** Return the next available byte without consuming it. + * + * \return The byte if no error and not at eof else -1; + */ + int peek() { + return FatFile::peek(); + } + /** Read the next byte from a file. + * + * \return For success return the next byte in the file as an int. + * If an error occurs or end of file is reached return -1. + */ +// int read() { +// return FatFile::read(); +// } + /** Write a byte to a file. Required by the Arduino Print class. + * \param[in] b the byte to be written. + * Use getWriteError to check for errors. + * \return 1 for success and 0 for failure. + */ + size_t write(uint8_t b) { + return FatFile::write(b); + } + /** Write data to an open file. Form required by Print. + * + * \note Data is moved to the cache but may not be written to the + * storage device until sync() is called. + * + * \param[in] buf Pointer to the location of the data to be written. + * + * \param[in] size Number of bytes to write. + * + * \return For success write() returns the number of bytes written, always + * \a nbyte. If an error occurs, write() returns -1. Possible errors + * include write() is called before a file has been opened, write is called + * for a read-only file, device is full, a corrupt file system or an + * I/O error. + */ + size_t write(const uint8_t *buf, size_t size) { + return FatFile::write(buf, size); + } +}; +//============================================================================== +/** + * \class File + * \brief Arduino SD.h style File API + */ +#if ARDUINO_FILE_USES_STREAM +class File : public FatFile, public Stream { +#else // ARDUINO_FILE_USES_STREAM +class File : public FatFile, public Print { +#endif // ARDUINO_FILE_USES_STREAM + public: + File() {} + /** Create a file object and open it in the current working directory. + * + * \param[in] path A path with a valid 8.3 DOS name for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a + * bitwise-inclusive OR of open flags. see + * FatFile::open(FatFile*, const char*, uint8_t). + */ + File(const char* path, uint8_t oflag) { + open(path, oflag); + } + using FatFile::clearWriteError; + using FatFile::getWriteError; + using FatFile::read; + using FatFile::write; + /** The parenthesis operator. + * + * \return true if a file is open. + */ + operator bool() { + return isOpen(); + } + /** \return number of bytes available from the current position to EOF + * or INT_MAX if more than INT_MAX bytes are available. + */ + int available() { + uint32_t n = FatFile::available(); + return n > INT_MAX ? INT_MAX : n; + } + /** Ensure that any bytes written to the file are saved to the SD card. */ + void flush() { + FatFile::sync(); + } + /** This function reports if the current file is a directory or not. + * \return true if the file is a directory. + */ + bool isDirectory() { + return isDir(); + } + /** No longer implemented due to Long File Names. + * + * Use getName(char* name, size_t size). + * \return a pointer to replacement suggestion. + */ + const char* name() const { + return "use getName()"; + } + /** Return the next available byte without consuming it. + * + * \return The byte if no error and not at eof else -1; + */ + int peek() { + return FatFile::peek(); + } + /** \return the current file position. */ + uint32_t position() { + return curPosition(); + } + /** Opens the next file or folder in a directory. + * + * \param[in] mode open mode flags. + * \return a File object. + */ + File openNextFile(uint8_t mode = O_READ) { + File tmpFile; + tmpFile.openNext(this, mode); + return tmpFile; + } + /** Read the next byte from a file. + * + * \return For success return the next byte in the file as an int. + * If an error occurs or end of file is reached return -1. + */ + int read() { + return FatFile::read(); + } + /** Rewind a file if it is a directory */ + void rewindDirectory() { + if (isDir()) { + rewind(); + } + } + /** + * Seek to a new position in the file, which must be between + * 0 and the size of the file (inclusive). + * + * \param[in] pos the new file position. + * \return true for success else false. + */ + bool seek(uint32_t pos) { + return seekSet(pos); + } + /** \return the file's size. */ + uint32_t size() { + return fileSize(); + } + /** Write a byte to a file. Required by the Arduino Print class. + * \param[in] b the byte to be written. + * Use getWriteError to check for errors. + * \return 1 for success and 0 for failure. + */ + size_t write(uint8_t b) { + return FatFile::write(b); + } + /** Write data to an open file. Form required by Print. + * + * \note Data is moved to the cache but may not be written to the + * storage device until sync() is called. + * + * \param[in] buf Pointer to the location of the data to be written. + * + * \param[in] size Number of bytes to write. + * + * \return For success write() returns the number of bytes written, always + * \a nbyte. If an error occurs, write() returns -1. Possible errors + * include write() is called before a file has been opened, write is called + * for a read-only file, device is full, a corrupt file system or an + * I/O error. + */ + size_t write(const uint8_t *buf, size_t size) { + return FatFile::write(buf, size); + } +}; +#endif // ENABLE_ARDUINO_FEATURES +#endif // ArduinoFiles_h diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/ArduinoStream.h b/Firmware_V2/src/libraries/SdFat/FatLib/ArduinoStream.h new file mode 100644 index 0000000..8fd62d7 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/ArduinoStream.h @@ -0,0 +1,149 @@ +/* FatLib Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#ifndef ArduinoStream_h +#define ArduinoStream_h +/** + * \file + * \brief ArduinoInStream and ArduinoOutStream classes + */ +#include "FatLibConfig.h" +#if ENABLE_ARDUINO_FEATURES +#include "SysCall.h" +#include "bufstream.h" +//============================================================================== +/** + * \class ArduinoInStream + * \brief Input stream for Arduino Stream objects + */ +class ArduinoInStream : public ibufstream { + public: + /** + * Constructor + * \param[in] hws hardware stream + * \param[in] buf buffer for input line + * \param[in] size size of input buffer + */ + ArduinoInStream(Stream &hws, char* buf, size_t size) { + m_hw = &hws; + m_line = buf; + m_size = size; + } + /** read a line. */ + void readline() { + size_t i = 0; + uint32_t t; + m_line[0] = '\0'; + while (!m_hw->available()) { + SysCall::yield(); + } + + while (1) { + t = millis(); + while (!m_hw->available()) { + if ((millis() - t) > 10) { + goto done; + } + } + if (i >= (m_size - 1)) { + setstate(failbit); + return; + } + m_line[i++] = m_hw->read(); + m_line[i] = '\0'; + } +done: + init(m_line); + } + + protected: + /** Internal - do not use. + * \param[in] off + * \param[in] way + * \return true/false. + */ + bool seekoff(off_type off, seekdir way) { + (void)off; + (void)way; + return false; + } + /** Internal - do not use. + * \param[in] pos + * \return true/false. + */ + bool seekpos(pos_type pos) { + (void)pos; + return false; + } + + private: + char *m_line; + size_t m_size; + Stream* m_hw; +}; +//============================================================================== +/** + * \class ArduinoOutStream + * \brief Output stream for Arduino Print objects + */ +class ArduinoOutStream : public ostream { + public: + /** constructor + * + * \param[in] pr Print object for this ArduinoOutStream. + */ + explicit ArduinoOutStream(Print& pr) : m_pr(&pr) {} + + protected: + /// @cond SHOW_PROTECTED + /** + * Internal do not use + * \param[in] c + */ + void putch(char c) { + if (c == '\n') { + m_pr->write('\r'); + } + m_pr->write(c); + } + void putstr(const char* str) { + m_pr->write(str); + } + bool seekoff(off_type off, seekdir way) { + (void)off; + (void)way; + return false; + } + bool seekpos(pos_type pos) { + (void)pos; + return false; + } + bool sync() { + return true; + } + pos_type tellpos() { + return 0; + } + /// @endcond + private: + ArduinoOutStream() {} + Print* m_pr; +}; +#endif // ENABLE_ARDUINO_FEATURES +#endif // ArduinoStream_h diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/FatApiConstants.h b/Firmware_V2/src/libraries/SdFat/FatLib/FatApiConstants.h new file mode 100644 index 0000000..9bc5912 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/FatApiConstants.h @@ -0,0 +1,67 @@ +/* FatLib Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#ifndef FatApiConstants_h +#define FatApiConstants_h +//------------------------------------------------------------------------------ +// use the gnu style oflag in open() +/** open() oflag for reading */ +uint8_t const O_READ = 0X01; +/** open() oflag - same as O_IN */ +uint8_t const O_RDONLY = O_READ; +/** open() oflag for write */ +uint8_t const O_WRITE = 0X02; +/** open() oflag - same as O_WRITE */ +uint8_t const O_WRONLY = O_WRITE; +/** open() oflag for reading and writing */ +uint8_t const O_RDWR = (O_READ | O_WRITE); +/** open() oflag mask for access modes */ +uint8_t const O_ACCMODE = (O_READ | O_WRITE); +/** The file offset shall be set to the end of the file prior to each write. */ +uint8_t const O_APPEND = 0X04; +/** synchronous writes - call sync() after each write */ +uint8_t const O_SYNC = 0X08; +/** truncate the file to zero length */ +uint8_t const O_TRUNC = 0X10; +/** set the initial position at the end of the file */ +uint8_t const O_AT_END = 0X20; +/** create the file if nonexistent */ +uint8_t const O_CREAT = 0X40; +/** If O_CREAT and O_EXCL are set, open() shall fail if the file exists */ +uint8_t const O_EXCL = 0X80; + +// FatFile class static and const definitions +// flags for ls() +/** ls() flag for list all files including hidden. */ +uint8_t const LS_A = 1; +/** ls() flag to print modify. date */ +uint8_t const LS_DATE = 2; +/** ls() flag to print file size. */ +uint8_t const LS_SIZE = 4; +/** ls() flag for recursive list of subdirectories */ +uint8_t const LS_R = 8; + +// flags for timestamp +/** set the file's last access date */ +uint8_t const T_ACCESS = 1; +/** set the file's creation date and time */ +uint8_t const T_CREATE = 2; +/** Set the file's write date and time */ +uint8_t const T_WRITE = 4; +#endif // FatApiConstants_h diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/FatFile.cpp b/Firmware_V2/src/libraries/SdFat/FatLib/FatFile.cpp new file mode 100644 index 0000000..5146a49 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/FatFile.cpp @@ -0,0 +1,1494 @@ +/* FatLib Library + * Copyright (C) 2012 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#include "FatFile.h" +#include "FatFileSystem.h" +//------------------------------------------------------------------------------ +// Pointer to cwd directory. +FatFile* FatFile::m_cwd = 0; +// Callback function for date/time. +void (*FatFile::m_dateTime)(uint16_t* date, uint16_t* time) = 0; +//------------------------------------------------------------------------------ +// Add a cluster to a file. +bool FatFile::addCluster() { + m_flags |= F_FILE_DIR_DIRTY; + return m_vol->allocateCluster(m_curCluster, &m_curCluster); +} +//------------------------------------------------------------------------------ +// Add a cluster to a directory file and zero the cluster. +// Return with first block of cluster in the cache. +bool FatFile::addDirCluster() { + uint32_t block; + cache_t* pc; + + if (isRootFixed()) { + DBG_FAIL_MACRO; + goto fail; + } + // max folder size + if (m_curPosition >= 512UL*4095) { + DBG_FAIL_MACRO; + goto fail; + } + if (!addCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + block = m_vol->clusterStartBlock(m_curCluster); + pc = m_vol->cacheFetchData(block, FatCache::CACHE_RESERVE_FOR_WRITE); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + memset(pc, 0, 512); + // zero rest of clusters + for (uint8_t i = 1; i < m_vol->blocksPerCluster(); i++) { + if (!m_vol->writeBlock(block + i, pc->data)) { + DBG_FAIL_MACRO; + goto fail; + } + } + // Set position to EOF to avoid inconsistent curCluster/curPosition. + m_curPosition += 512UL*m_vol->blocksPerCluster(); + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +// cache a file's directory entry +// return pointer to cached entry or null for failure +dir_t* FatFile::cacheDirEntry(uint8_t action) { + cache_t* pc; + pc = m_vol->cacheFetchData(m_dirBlock, action); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + return pc->dir + (m_dirIndex & 0XF); + +fail: + return 0; +} +//------------------------------------------------------------------------------ +bool FatFile::close() { + bool rtn = sync(); + m_attr = FILE_ATTR_CLOSED; + return rtn; +} +//------------------------------------------------------------------------------ +bool FatFile::contiguousRange(uint32_t* bgnBlock, uint32_t* endBlock) { + // error if no blocks + if (m_firstCluster == 0) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint32_t c = m_firstCluster; ; c++) { + uint32_t next; + int8_t fg = m_vol->fatGet(c, &next); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + // check for contiguous + if (fg == 0 || next != (c + 1)) { + // error if not end of chain + if (fg) { + DBG_FAIL_MACRO; + goto fail; + } + *bgnBlock = m_vol->clusterStartBlock(m_firstCluster); + *endBlock = m_vol->clusterStartBlock(c) + + m_vol->blocksPerCluster() - 1; + return true; + } + } + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::createContiguous(FatFile* dirFile, + const char* path, uint32_t size) { + uint32_t count; + // don't allow zero length file + if (size == 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (!open(dirFile, path, O_CREAT | O_EXCL | O_RDWR)) { + DBG_FAIL_MACRO; + goto fail; + } + // calculate number of clusters needed + count = ((size - 1) >> (m_vol->clusterSizeShift() + 9)) + 1; + + // allocate clusters + if (!m_vol->allocContiguous(count, &m_firstCluster)) { + remove(); + DBG_FAIL_MACRO; + goto fail; + } + m_fileSize = size; + + // insure sync() will update dir entry + m_flags |= F_FILE_DIR_DIRTY; + + return sync(); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::dirEntry(dir_t* dst) { + dir_t* dir; + // Make sure fields on device are correct. + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + // read entry + dir = cacheDirEntry(FatCache::CACHE_FOR_READ); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // copy to caller's struct + memcpy(dst, dir, sizeof(dir_t)); + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +uint8_t FatFile::dirName(const dir_t* dir, char* name) { + uint8_t j = 0; + uint8_t lcBit = DIR_NT_LC_BASE; + for (uint8_t i = 0; i < 11; i++) { + if (dir->name[i] == ' ') { + continue; + } + if (i == 8) { + // Position bit for extension. + lcBit = DIR_NT_LC_EXT; + name[j++] = '.'; + } + char c = dir->name[i]; + if ('A' <= c && c <= 'Z' && (lcBit & dir->reservedNT)) { + c += 'a' - 'A'; + } + name[j++] = c; + } + name[j] = 0; + return j; +} + +//------------------------------------------------------------------------------ +uint32_t FatFile::dirSize() { + int8_t fg; + if (!isDir()) { + return 0; + } + if (isRootFixed()) { + return 32*m_vol->rootDirEntryCount(); + } + uint16_t n = 0; + uint32_t c = isRoot32() ? m_vol->rootDirStart() : m_firstCluster; + do { + fg = m_vol->fatGet(c, &c); + if (fg < 0 || n > 4095) { + return 0; + } + n += m_vol->blocksPerCluster(); + } while (fg); + return 512UL*n; +} +//------------------------------------------------------------------------------ +int16_t FatFile::fgets(char* str, int16_t num, char* delim) { + char ch; + int16_t n = 0; + int16_t r = -1; + while ((n + 1) < num && (r = read(&ch, 1)) == 1) { + // delete CR + if (ch == '\r') { + continue; + } + str[n++] = ch; + if (!delim) { + if (ch == '\n') { + break; + } + } else { + if (strchr(delim, ch)) { + break; + } + } + } + if (r < 0) { + // read error + return -1; + } + str[n] = '\0'; + return n; +} +//------------------------------------------------------------------------------ +void FatFile::getpos(FatPos_t* pos) { + pos->position = m_curPosition; + pos->cluster = m_curCluster; +} +//------------------------------------------------------------------------------ +bool FatFile::mkdir(FatFile* parent, const char* path, bool pFlag) { + fname_t fname; + FatFile tmpDir; + + if (isOpen() || !parent->isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + if (isDirSeparator(*path)) { + while (isDirSeparator(*path)) { + path++; + } + if (!tmpDir.openRoot(parent->m_vol)) { + DBG_FAIL_MACRO; + goto fail; + } + parent = &tmpDir; + } + while (1) { + if (!parsePathName(path, &fname, &path)) { + DBG_FAIL_MACRO; + goto fail; + } + if (!*path) { + break; + } + if (!open(parent, &fname, O_READ)) { + if (!pFlag || !mkdir(parent, &fname)) { + DBG_FAIL_MACRO; + goto fail; + } + } + tmpDir = *this; + parent = &tmpDir; + close(); + } + return mkdir(parent, &fname); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::mkdir(FatFile* parent, fname_t* fname) { + uint32_t block; + dir_t dot; + dir_t* dir; + cache_t* pc; + + if (!parent->isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + // create a normal file + if (!open(parent, fname, O_CREAT | O_EXCL | O_RDWR)) { + DBG_FAIL_MACRO; + goto fail; + } + // convert file to directory + m_flags = O_READ; + m_attr = FILE_ATTR_SUBDIR; + + // allocate and zero first cluster + if (!addDirCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + m_firstCluster = m_curCluster; + // Set to start of dir + rewind(); + // force entry to device + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + // cache entry - should already be in cache due to sync() call + dir = cacheDirEntry(FatCache::CACHE_FOR_WRITE); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // change directory entry attribute + dir->attributes = DIR_ATT_DIRECTORY; + + // make entry for '.' + memcpy(&dot, dir, sizeof(dot)); + dot.name[0] = '.'; + for (uint8_t i = 1; i < 11; i++) { + dot.name[i] = ' '; + } + + // cache block for '.' and '..' + block = m_vol->clusterStartBlock(m_firstCluster); + pc = m_vol->cacheFetchData(block, FatCache::CACHE_FOR_WRITE); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + // copy '.' to block + memcpy(&pc->dir[0], &dot, sizeof(dot)); + // make entry for '..' + dot.name[1] = '.'; + dot.firstClusterLow = parent->m_firstCluster & 0XFFFF; + dot.firstClusterHigh = parent->m_firstCluster >> 16; + // copy '..' to block + memcpy(&pc->dir[1], &dot, sizeof(dot)); + // write first block + return m_vol->cacheSync(); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::open(FatFileSystem* fs, const char* path, uint8_t oflag) { + return open(fs->vwd(), path, oflag); +} +//------------------------------------------------------------------------------ +bool FatFile::open(FatFile* dirFile, const char* path, uint8_t oflag) { + FatFile tmpDir; + fname_t fname; + + // error if already open + if (isOpen() || !dirFile->isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + if (isDirSeparator(*path)) { + while (isDirSeparator(*path)) { + path++; + } + if (*path == 0) { + return openRoot(dirFile->m_vol); + } + if (!tmpDir.openRoot(dirFile->m_vol)) { + DBG_FAIL_MACRO; + goto fail; + } + dirFile = &tmpDir; + } + while (1) { + if (!parsePathName(path, &fname, &path)) { + DBG_FAIL_MACRO; + goto fail; + } + if (*path == 0) { + break; + } + if (!open(dirFile, &fname, O_READ)) { + DBG_FAIL_MACRO; + goto fail; + } + tmpDir = *this; + dirFile = &tmpDir; + close(); + } + return open(dirFile, &fname, oflag); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::open(FatFile* dirFile, uint16_t index, uint8_t oflag) { + uint8_t chksum = 0; + uint8_t lfnOrd = 0; + dir_t* dir; + ldir_t*ldir; + + // Error if already open. + if (isOpen() || !dirFile->isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + // Don't open existing file if O_EXCL - user call error. + if (oflag & O_EXCL) { + DBG_FAIL_MACRO; + goto fail; + } + if (index) { + // Check for LFN. + if (!dirFile->seekSet(32UL*(index -1))) { + DBG_FAIL_MACRO; + goto fail; + } + ldir = reinterpret_cast(dirFile->readDirCache()); + if (!ldir) { + DBG_FAIL_MACRO; + goto fail; + } + if (ldir->attr == DIR_ATT_LONG_NAME) { + if (1 == (ldir->ord & 0X1F)) { + chksum = ldir->chksum; + // Use largest possible number. + lfnOrd = index > 20 ? 20 : index; + } + } + } else { + dirFile->rewind(); + } + // read entry into cache + dir = dirFile->readDirCache(); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // error if empty slot or '.' or '..' + if (dir->name[0] == DIR_NAME_DELETED || + dir->name[0] == DIR_NAME_FREE || + dir->name[0] == '.') { + DBG_FAIL_MACRO; + goto fail; + } + if (lfnOrd && chksum != lfnChecksum(dir->name)) { + DBG_FAIL_MACRO; + goto fail; + } + // open cached entry + if (!openCachedEntry(dirFile, index, oflag, lfnOrd)) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +// open a cached directory entry. + +bool FatFile::openCachedEntry(FatFile* dirFile, uint16_t dirIndex, + uint8_t oflag, uint8_t lfnOrd) { + uint32_t firstCluster; + memset(this, 0, sizeof(FatFile)); + // location of entry in cache + m_vol = dirFile->m_vol; + m_dirIndex = dirIndex; + m_dirCluster = dirFile->m_firstCluster; + dir_t* dir = &m_vol->cacheAddress()->dir[0XF & dirIndex]; + + // Must be file or subdirectory. + if (!DIR_IS_FILE_OR_SUBDIR(dir)) { + DBG_FAIL_MACRO; + goto fail; + } + m_attr = dir->attributes & FILE_ATTR_COPY; + if (DIR_IS_FILE(dir)) { + m_attr |= FILE_ATTR_FILE; + } + m_lfnOrd = lfnOrd; + // Write, truncate, or at end is an error for a directory or read-only file. + if (oflag & (O_WRITE | O_TRUNC | O_AT_END)) { + if (isSubDir() || isReadOnly()) { + DBG_FAIL_MACRO; + goto fail; + } + } + // save open flags for read/write + m_flags = oflag & F_OFLAG; + + m_dirBlock = m_vol->cacheBlockNumber(); + + // copy first cluster number for directory fields + firstCluster = ((uint32_t)dir->firstClusterHigh << 16) + | dir->firstClusterLow; + + if (oflag & O_TRUNC) { + if (firstCluster && !m_vol->freeChain(firstCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + // need to update directory entry + m_flags |= F_FILE_DIR_DIRTY; + } else { + m_firstCluster = firstCluster; + m_fileSize = dir->fileSize; + } + if ((oflag & O_AT_END) && !seekSet(m_fileSize)) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + +fail: + m_attr = FILE_ATTR_CLOSED; + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::openNext(FatFile* dirFile, uint8_t oflag) { + uint8_t chksum = 0; + ldir_t* ldir; + uint8_t lfnOrd = 0; + uint16_t index; + + // Check for not open and valid directory.. + if (isOpen() || !dirFile->isDir() || (dirFile->curPosition() & 0X1F)) { + DBG_FAIL_MACRO; + goto fail; + } + while (1) { + // read entry into cache + index = dirFile->curPosition()/32; + dir_t* dir = dirFile->readDirCache(); + if (!dir) { + if (dirFile->getError()) { + DBG_FAIL_MACRO; + } + goto fail; + } + // done if last entry + if (dir->name[0] == DIR_NAME_FREE) { + goto fail; + } + // skip empty slot or '.' or '..' + if (dir->name[0] == '.' || dir->name[0] == DIR_NAME_DELETED) { + lfnOrd = 0; + } else if (DIR_IS_FILE_OR_SUBDIR(dir)) { + if (lfnOrd && chksum != lfnChecksum(dir->name)) { + DBG_FAIL_MACRO; + goto fail; + } + if (!openCachedEntry(dirFile, index, oflag, lfnOrd)) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + } else if (DIR_IS_LONG_NAME(dir)) { + ldir = reinterpret_cast(dir); + if (ldir->ord & LDIR_ORD_LAST_LONG_ENTRY) { + lfnOrd = ldir->ord & 0X1F; + chksum = ldir->chksum; + } + } else { + lfnOrd = 0; + } + } + +fail: + return false; +} +#ifndef DOXYGEN_SHOULD_SKIP_THIS +//------------------------------------------------------------------------------ +/** Open a file's parent directory. + * + * \param[in] file Parent of this directory will be opened. Must not be root. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ +bool FatFile::openParent(FatFile* dirFile) { + FatFile dotdot; + uint32_t lbn; + dir_t* dir; + uint32_t ddc; + cache_t* cb; + + if (isOpen() || !dirFile->isOpen()) { + goto fail; + } + if (dirFile->m_dirCluster == 0) { + return openRoot(dirFile->m_vol); + } + lbn = dirFile->m_vol->clusterStartBlock(dirFile->m_dirCluster); + cb = dirFile->m_vol->cacheFetchData(lbn, FatCache::CACHE_FOR_READ); + if (!cb) { + DBG_FAIL_MACRO; + goto fail; + } + // Point to dir entery for .. + dir = cb->dir + 1; + ddc = dir->firstClusterLow | ((uint32_t)dir->firstClusterHigh << 16); + if (ddc == 0) { + if (!dotdot.openRoot(dirFile->m_vol)) { + DBG_FAIL_MACRO; + goto fail; + } + } else { + memset(&dotdot, 0, sizeof(FatFile)); + dotdot.m_attr = FILE_ATTR_SUBDIR; + dotdot.m_flags = O_READ; + dotdot.m_vol = dirFile->m_vol; + dotdot.m_firstCluster = ddc; + } + uint32_t di; + do { + di = dotdot.curPosition()/32; + dir = dotdot.readDirCache(); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + ddc = dir->firstClusterLow | ((uint32_t)dir->firstClusterHigh << 16); + } while (ddc != dirFile->m_dirCluster); + return open(&dotdot, di, O_READ); + +fail: + return false; +} +#endif // DOXYGEN_SHOULD_SKIP_THIS +//------------------------------------------------------------------------------ +bool FatFile::openRoot(FatVolume* vol) { + // error if file is already open + if (isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + memset(this, 0, sizeof(FatFile)); + + m_vol = vol; + switch (vol->fatType()) { +#if FAT12_SUPPORT + case 12: +#endif // FAT12_SUPPORT + case 16: + m_attr = FILE_ATTR_ROOT_FIXED; + break; + + case 32: + m_attr = FILE_ATTR_ROOT32; + break; + + default: + DBG_FAIL_MACRO; + goto fail; + } + // read only + m_flags = O_READ; + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +int FatFile::peek() { + FatPos_t pos; + getpos(&pos); + int c = read(); + if (c >= 0) { + setpos(&pos); + } + return c; +} +//------------------------------------------------------------------------------ +int FatFile::read(void* buf, size_t nbyte) { + int8_t fg; + uint8_t blockOfCluster = 0; + uint8_t* dst = reinterpret_cast(buf); + uint16_t offset; + size_t toRead; + uint32_t block; // raw device block number + cache_t* pc; + + // error if not open for read + if (!isOpen() || !(m_flags & O_READ)) { + DBG_FAIL_MACRO; + goto fail; + } + + if (isFile()) { + uint32_t tmp32 = m_fileSize - m_curPosition; + if (nbyte >= tmp32) { + nbyte = tmp32; + } + } else if (isRootFixed()) { + uint16_t tmp16 = 32*m_vol->m_rootDirEntryCount - (uint16_t)m_curPosition; + if (nbyte > tmp16) { + nbyte = tmp16; + } + } + toRead = nbyte; + while (toRead) { + size_t n; + offset = m_curPosition & 0X1FF; // offset in block + if (isRootFixed()) { + block = m_vol->rootDirStart() + (m_curPosition >> 9); + } else { + blockOfCluster = m_vol->blockOfCluster(m_curPosition); + if (offset == 0 && blockOfCluster == 0) { + // start of new cluster + if (m_curPosition == 0) { + // use first cluster in file + m_curCluster = isRoot32() ? m_vol->rootDirStart() : m_firstCluster; + } else { + // get next cluster from FAT + fg = m_vol->fatGet(m_curCluster, &m_curCluster); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (fg == 0) { + if (isDir()) { + break; + } + DBG_FAIL_MACRO; + goto fail; + } + } + } + block = m_vol->clusterStartBlock(m_curCluster) + blockOfCluster; + } + if (offset != 0 || toRead < 512 || block == m_vol->cacheBlockNumber()) { + // amount to be read from current block + n = 512 - offset; + if (n > toRead) { + n = toRead; + } + // read block to cache and copy data to caller + pc = m_vol->cacheFetchData(block, FatCache::CACHE_FOR_READ); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + uint8_t* src = pc->data + offset; + memcpy(dst, src, n); +#if USE_MULTI_BLOCK_IO + } else if (toRead >= 1024) { + uint8_t nb = toRead >> 9; + if (!isRootFixed()) { + uint8_t mb = m_vol->blocksPerCluster() - blockOfCluster; + if (mb < nb) { + nb = mb; + } + } + n = 512*nb; + if (m_vol->cacheBlockNumber() <= block + && block < (m_vol->cacheBlockNumber() + nb)) { + // flush cache if a block is in the cache + if (!m_vol->cacheSync()) { + DBG_FAIL_MACRO; + goto fail; + } + } + if (!m_vol->readBlocks(block, dst, nb)) { + DBG_FAIL_MACRO; + goto fail; + } +#endif // USE_MULTI_BLOCK_IO + } else { + // read single block + n = 512; + if (!m_vol->readBlock(block, dst)) { + DBG_FAIL_MACRO; + goto fail; + } + } + dst += n; + m_curPosition += n; + toRead -= n; + } + return nbyte - toRead; + +fail: + m_error |= READ_ERROR; + return -1; +} +//------------------------------------------------------------------------------ +int8_t FatFile::readDir(dir_t* dir) { + int16_t n; + // if not a directory file or miss-positioned return an error + if (!isDir() || (0X1F & m_curPosition)) { + return -1; + } + + while (1) { + n = read(dir, sizeof(dir_t)); + if (n != sizeof(dir_t)) { + return n == 0 ? 0 : -1; + } + // last entry if DIR_NAME_FREE + if (dir->name[0] == DIR_NAME_FREE) { + return 0; + } + // skip empty entries and entry for . and .. + if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.') { + continue; + } + // return if normal file or subdirectory + if (DIR_IS_FILE_OR_SUBDIR(dir)) { + return n; + } + } +} +//------------------------------------------------------------------------------ +// Read next directory entry into the cache +// Assumes file is correctly positioned +dir_t* FatFile::readDirCache(bool skipReadOk) { +// uint8_t b; + uint8_t i = (m_curPosition >> 5) & 0XF; + + if (i == 0 || !skipReadOk) { + int8_t n = read(&n, 1); + if (n != 1) { + if (n != 0) { + DBG_FAIL_MACRO; + } + goto fail; + } + m_curPosition += 31; + } else { + m_curPosition += 32; + } + // return pointer to entry + return m_vol->cacheAddress()->dir + i; + +fail: + return 0; +} +//------------------------------------------------------------------------------ +bool FatFile::remove(FatFile* dirFile, const char* path) { + FatFile file; + if (!file.open(dirFile, path, O_WRITE)) { + DBG_FAIL_MACRO; + goto fail; + } + return file.remove(); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::rename(FatFile* dirFile, const char* newPath) { + dir_t entry; + uint32_t dirCluster = 0; + FatFile file; + FatFile oldFile; + cache_t* pc; + dir_t* dir; + + // Must be an open file or subdirectory. + if (!(isFile() || isSubDir())) { + DBG_FAIL_MACRO; + goto fail; + } + // Can't rename LFN in 8.3 mode. + if (!USE_LONG_FILE_NAMES && isLFN()) { + DBG_FAIL_MACRO; + goto fail; + } + // Can't move file to new volume. + if (m_vol != dirFile->m_vol) { + DBG_FAIL_MACRO; + goto fail; + } + // sync() and cache directory entry + sync(); + oldFile = *this; + dir = cacheDirEntry(FatCache::CACHE_FOR_READ); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // save directory entry + memcpy(&entry, dir, sizeof(entry)); + // make directory entry for new path + if (isFile()) { + if (!file.open(dirFile, newPath, O_CREAT | O_EXCL | O_WRITE)) { + DBG_FAIL_MACRO; + goto fail; + } + } else { + // don't create missing path prefix components + if (!file.mkdir(dirFile, newPath, false)) { + DBG_FAIL_MACRO; + goto fail; + } + // save cluster containing new dot dot + dirCluster = file.m_firstCluster; + } + // change to new directory entry + + m_dirBlock = file.m_dirBlock; + m_dirIndex = file.m_dirIndex; + m_lfnOrd = file.m_lfnOrd; + m_dirCluster = file.m_dirCluster; + // mark closed to avoid possible destructor close call + file.m_attr = FILE_ATTR_CLOSED; + + // cache new directory entry + dir = cacheDirEntry(FatCache::CACHE_FOR_WRITE); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // copy all but name and name flags to new directory entry + memcpy(&dir->creationTimeTenths, &entry.creationTimeTenths, + sizeof(entry) - sizeof(dir->name) - 2); + dir->attributes = entry.attributes; + + // update dot dot if directory + if (dirCluster) { + // get new dot dot + uint32_t block = m_vol->clusterStartBlock(dirCluster); + pc = m_vol->cacheFetchData(block, FatCache::CACHE_FOR_READ); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + memcpy(&entry, &pc->dir[1], sizeof(entry)); + + // free unused cluster + if (!m_vol->freeChain(dirCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + // store new dot dot + block = m_vol->clusterStartBlock(m_firstCluster); + pc = m_vol->cacheFetchData(block, FatCache::CACHE_FOR_WRITE); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + memcpy(&pc->dir[1], &entry, sizeof(entry)); + } + // Remove old directory entry; + oldFile.m_firstCluster = 0; + oldFile.m_flags = O_WRITE; + oldFile.m_attr = FILE_ATTR_FILE; + if (!oldFile.remove()) { + DBG_FAIL_MACRO; + goto fail; + } + return m_vol->cacheSync(); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::rmdir() { + // must be open subdirectory + if (!isSubDir() || (!USE_LONG_FILE_NAMES && isLFN())) { + DBG_FAIL_MACRO; + goto fail; + } + rewind(); + + // make sure directory is empty + while (1) { + dir_t* dir = readDirCache(true); + if (!dir) { + // EOF if no error. + if (!getError()) { + break; + } + DBG_FAIL_MACRO; + goto fail; + } + // done if past last used entry + if (dir->name[0] == DIR_NAME_FREE) { + break; + } + // skip empty slot, '.' or '..' + if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.') { + continue; + } + // error not empty + if (DIR_IS_FILE_OR_SUBDIR(dir)) { + DBG_FAIL_MACRO; + goto fail; + } + } + // convert empty directory to normal file for remove + m_attr = FILE_ATTR_FILE; + m_flags |= O_WRITE; + return remove(); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::rmRfStar() { + uint16_t index; + FatFile f; + if (!isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + rewind(); + while (1) { + // remember position + index = m_curPosition/32; + + dir_t* dir = readDirCache(); + if (!dir) { + // At EOF if no error. + if (!getError()) { + break; + } + DBG_FAIL_MACRO; + goto fail; + } + // done if past last entry + if (dir->name[0] == DIR_NAME_FREE) { + break; + } + + // skip empty slot or '.' or '..' + if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.') { + continue; + } + + // skip if part of long file name or volume label in root + if (!DIR_IS_FILE_OR_SUBDIR(dir)) { + continue; + } + + if (!f.open(this, index, O_READ)) { + DBG_FAIL_MACRO; + goto fail; + } + if (f.isSubDir()) { + // recursively delete + if (!f.rmRfStar()) { + DBG_FAIL_MACRO; + goto fail; + } + } else { + // ignore read-only + f.m_flags |= O_WRITE; + if (!f.remove()) { + DBG_FAIL_MACRO; + goto fail; + } + } + // position to next entry if required + if (m_curPosition != (32UL*(index + 1))) { + if (!seekSet(32UL*(index + 1))) { + DBG_FAIL_MACRO; + goto fail; + } + } + } + // don't try to delete root + if (!isRoot()) { + if (!rmdir()) { + DBG_FAIL_MACRO; + goto fail; + } + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::seekSet(uint32_t pos) { + uint32_t nCur; + uint32_t nNew; + uint32_t tmp = m_curCluster; + // error if file not open + if (!isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + // Optimize O_APPEND writes. + if (pos == m_curPosition) { + return true; + } + if (pos == 0) { + // set position to start of file + m_curCluster = 0; + goto done; + } + if (isFile()) { + if (pos > m_fileSize) { + DBG_FAIL_MACRO; + goto fail; + } + } else if (isRootFixed()) { + if (pos <= 32*m_vol->rootDirEntryCount()) { + goto done; + } + DBG_FAIL_MACRO; + goto fail; + } + // calculate cluster index for cur and new position + nCur = (m_curPosition - 1) >> (m_vol->clusterSizeShift() + 9); + nNew = (pos - 1) >> (m_vol->clusterSizeShift() + 9); + + if (nNew < nCur || m_curPosition == 0) { + // must follow chain from first cluster + m_curCluster = isRoot32() ? m_vol->rootDirStart() : m_firstCluster; + } else { + // advance from curPosition + nNew -= nCur; + } + while (nNew--) { + if (m_vol->fatGet(m_curCluster, &m_curCluster) <= 0) { + DBG_FAIL_MACRO; + goto fail; + } + } + +done: + m_curPosition = pos; + return true; + +fail: + m_curCluster = tmp; + return false; +} +//------------------------------------------------------------------------------ +void FatFile::setpos(FatPos_t* pos) { + m_curPosition = pos->position; + m_curCluster = pos->cluster; +} +//------------------------------------------------------------------------------ +bool FatFile::sync() { + if (!isOpen()) { + return true; + } + + if (m_flags & F_FILE_DIR_DIRTY) { + dir_t* dir = cacheDirEntry(FatCache::CACHE_FOR_WRITE); + // check for deleted by another open file object + if (!dir || dir->name[0] == DIR_NAME_DELETED) { + DBG_FAIL_MACRO; + goto fail; + } + // do not set filesize for dir files + if (isFile()) { + dir->fileSize = m_fileSize; + } + + // update first cluster fields + dir->firstClusterLow = m_firstCluster & 0XFFFF; + dir->firstClusterHigh = m_firstCluster >> 16; + + // set modify time if user supplied a callback date/time function + if (m_dateTime) { + m_dateTime(&dir->lastWriteDate, &dir->lastWriteTime); + dir->lastAccessDate = dir->lastWriteDate; + } + // clear directory dirty + m_flags &= ~F_FILE_DIR_DIRTY; + } + if (m_vol->cacheSync()) { + return true; + } + DBG_FAIL_MACRO; + +fail: + m_error |= WRITE_ERROR; + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::timestamp(FatFile* file) { + dir_t* dir; + dir_t srcDir; + + // most be files get timestamps + if (!isFile() || !file->isFile() || !file->dirEntry(&srcDir)) { + DBG_FAIL_MACRO; + goto fail; + } + // update directory fields + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + dir = cacheDirEntry(FatCache::CACHE_FOR_WRITE); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // copy timestamps + dir->lastAccessDate = srcDir.lastAccessDate; + dir->creationDate = srcDir.creationDate; + dir->creationTime = srcDir.creationTime; + dir->creationTimeTenths = srcDir.creationTimeTenths; + dir->lastWriteDate = srcDir.lastWriteDate; + dir->lastWriteTime = srcDir.lastWriteTime; + + // write back entry + return m_vol->cacheSync(); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::timestamp(uint8_t flags, uint16_t year, uint8_t month, + uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) { + uint16_t dirDate; + uint16_t dirTime; + dir_t* dir; + + if (!isFile() + || year < 1980 + || year > 2107 + || month < 1 + || month > 12 + || day < 1 + || day > 31 + || hour > 23 + || minute > 59 + || second > 59) { + DBG_FAIL_MACRO; + goto fail; + } + // update directory entry + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + dir = cacheDirEntry(FatCache::CACHE_FOR_WRITE); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + dirDate = FAT_DATE(year, month, day); + dirTime = FAT_TIME(hour, minute, second); + if (flags & T_ACCESS) { + dir->lastAccessDate = dirDate; + } + if (flags & T_CREATE) { + dir->creationDate = dirDate; + dir->creationTime = dirTime; + // seems to be units of 1/100 second not 1/10 as Microsoft states + dir->creationTimeTenths = second & 1 ? 100 : 0; + } + if (flags & T_WRITE) { + dir->lastWriteDate = dirDate; + dir->lastWriteTime = dirTime; + } + return m_vol->cacheSync(); + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::truncate(uint32_t length) { + uint32_t newPos; + // error if not a normal file or read-only + if (!isFile() || !(m_flags & O_WRITE)) { + DBG_FAIL_MACRO; + goto fail; + } + // error if length is greater than current size + if (length > m_fileSize) { + DBG_FAIL_MACRO; + goto fail; + } + // fileSize and length are zero - nothing to do + if (m_fileSize == 0) { + return true; + } + + // remember position for seek after truncation + newPos = m_curPosition > length ? length : m_curPosition; + + // position to last cluster in truncated file + if (!seekSet(length)) { + DBG_FAIL_MACRO; + goto fail; + } + if (length == 0) { + // free all clusters + if (!m_vol->freeChain(m_firstCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + m_firstCluster = 0; + } else { + uint32_t toFree; + int8_t fg = m_vol->fatGet(m_curCluster, &toFree); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (fg) { + // free extra clusters + if (!m_vol->freeChain(toFree)) { + DBG_FAIL_MACRO; + goto fail; + } + // current cluster is end of chain + if (!m_vol->fatPutEOC(m_curCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + } + } + m_fileSize = length; + + // need to update directory entry + m_flags |= F_FILE_DIR_DIRTY; + + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + // set file to correct position + return seekSet(newPos); + +fail: + return false; +} +//------------------------------------------------------------------------------ +int FatFile::write(const void* buf, size_t nbyte) { + // convert void* to uint8_t* - must be before goto statements + const uint8_t* src = reinterpret_cast(buf); + cache_t* pc; + uint8_t cacheOption; + // number of bytes left to write - must be before goto statements + size_t nToWrite = nbyte; + size_t n; + // error if not a normal file or is read-only + if (!isFile() || !(m_flags & O_WRITE)) { + DBG_FAIL_MACRO; + goto fail; + } + // seek to end of file if append flag + if ((m_flags & O_APPEND)) { + if (!seekSet(m_fileSize)) { + DBG_FAIL_MACRO; + goto fail; + } + } + // Don't exceed max fileSize. + if (nbyte > (0XFFFFFFFF - m_curPosition)) { + DBG_FAIL_MACRO; + goto fail; + } + while (nToWrite) { + uint8_t blockOfCluster = m_vol->blockOfCluster(m_curPosition); + uint16_t blockOffset = m_curPosition & 0X1FF; + if (blockOfCluster == 0 && blockOffset == 0) { + // start of new cluster + if (m_curCluster != 0) { + int8_t fg = m_vol->fatGet(m_curCluster, &m_curCluster); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (fg == 0) { + // add cluster if at end of chain + if (!addCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + } + } else { + if (m_firstCluster == 0) { + // allocate first cluster of file + if (!addCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + m_firstCluster = m_curCluster; + } else { + m_curCluster = m_firstCluster; + } + } + } + // block for data write + uint32_t block = m_vol->clusterStartBlock(m_curCluster) + blockOfCluster; + + if (blockOffset != 0 || nToWrite < 512) { + // partial block - must use cache + // max space in block + n = 512 - blockOffset; + // lesser of space and amount to write + if (n > nToWrite) { + n = nToWrite; + } + + if (blockOffset == 0 && m_curPosition >= m_fileSize) { + // start of new block don't need to read into cache + cacheOption = FatCache::CACHE_RESERVE_FOR_WRITE; + } else { + // rewrite part of block + cacheOption = FatCache::CACHE_FOR_WRITE; + } + pc = m_vol->cacheFetchData(block, cacheOption); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + uint8_t* dst = pc->data + blockOffset; + memcpy(dst, src, n); + if (512 == (n + blockOffset)) { + // Force write if block is full - improves large writes. + if (!m_vol->cacheSyncData()) { + DBG_FAIL_MACRO; + goto fail; + } + } +#if USE_MULTI_BLOCK_IO + } else if (nToWrite >= 1024) { + // use multiple block write command + uint8_t maxBlocks = m_vol->blocksPerCluster() - blockOfCluster; + uint8_t nBlock = nToWrite >> 9; + if (nBlock > maxBlocks) { + nBlock = maxBlocks; + } + n = 512*nBlock; + if (m_vol->cacheBlockNumber() <= block + && block < (m_vol->cacheBlockNumber() + nBlock)) { + // invalidate cache if block is in cache + m_vol->cacheInvalidate(); + } + if (!m_vol->writeBlocks(block, src, nBlock)) { + DBG_FAIL_MACRO; + goto fail; + } +#endif // USE_MULTI_BLOCK_IO + } else { + // use single block write command + n = 512; + if (m_vol->cacheBlockNumber() == block) { + m_vol->cacheInvalidate(); + } + if (!m_vol->writeBlock(block, src)) { + DBG_FAIL_MACRO; + goto fail; + } + } + m_curPosition += n; + src += n; + nToWrite -= n; + } + if (m_curPosition > m_fileSize) { + // update fileSize and insure sync will update dir entry + m_fileSize = m_curPosition; + m_flags |= F_FILE_DIR_DIRTY; + } else if (m_dateTime) { + // insure sync will update modified date and time + m_flags |= F_FILE_DIR_DIRTY; + } + + if (m_flags & O_SYNC) { + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + } + return nbyte; + +fail: + // return for write error + m_error |= WRITE_ERROR; + return -1; +} diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/FatFile.h b/Firmware_V2/src/libraries/SdFat/FatLib/FatFile.h new file mode 100644 index 0000000..c6214aa --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/FatFile.h @@ -0,0 +1,983 @@ +/* FatLib Library + * Copyright (C) 2012 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#ifndef FatFile_h +#define FatFile_h +/** + * \file + * \brief FatFile class + */ +// #include +#include +#include +#include +#include "FatLibConfig.h" +#include "FatApiConstants.h" +#include "FatStructs.h" +#include "FatVolume.h" +class FatFileSystem; +//------------------------------------------------------------------------------ +// Stuff to store strings in AVR flash. +#ifdef __AVR__ +#include +#else // __AVR__ +#ifndef PSTR +/** store literal string in flash for ARM */ +#define PSTR(x) (x) +#endif // PSTR +#ifndef pgm_read_byte +/** read 8-bits from flash for ARM */ +#define pgm_read_byte(addr) (*(const unsigned char*)(addr)) +#endif // pgm_read_byte +#ifndef pgm_read_word +/** read 16-bits from flash for ARM */ +#define pgm_read_word(addr) (*(const uint16_t*)(addr)) +#endif // pgm_read_word +#ifndef PROGMEM +/** store in flash for ARM */ +#define PROGMEM +#endif // PROGMEM +#endif // __AVR__ +//------------------------------------------------------------------------------ +/** + * \struct FatPos_t + * \brief Internal type for file position - do not use in user apps. + */ +struct FatPos_t { + /** stream position */ + uint32_t position; + /** cluster for position */ + uint32_t cluster; + FatPos_t() : position(0), cluster(0) {} +}; +//------------------------------------------------------------------------------ +/** Expression for path name separator. */ +#define isDirSeparator(c) ((c) == '/') +//------------------------------------------------------------------------------ +/** + * \struct fname_t + * \brief Internal type for Short File Name - do not use in user apps. + */ +struct fname_t { + /** Flags for base and extension character case and LFN. */ + uint8_t flags; + /** length of Long File Name */ + size_t len; + /** Long File Name start. */ + const char* lfn; + /** position for sequence number */ + uint8_t seqPos; + /** Short File Name */ + uint8_t sfn[11]; +}; +/** Derived from a LFN with loss or conversion of characters. */ +const uint8_t FNAME_FLAG_LOST_CHARS = 0X01; +/** Base-name or extension has mixed case. */ +const uint8_t FNAME_FLAG_MIXED_CASE = 0X02; +/** LFN entries are required for file name. */ +const uint8_t FNAME_FLAG_NEED_LFN = + FNAME_FLAG_LOST_CHARS | FNAME_FLAG_MIXED_CASE; +/** Filename base-name is all lower case */ +const uint8_t FNAME_FLAG_LC_BASE = DIR_NT_LC_BASE; +/** Filename extension is all lower case. */ +const uint8_t FNAME_FLAG_LC_EXT = DIR_NT_LC_EXT; +//============================================================================== +/** + * \class FatFile + * \brief Basic file class. + */ +class FatFile { + public: + /** Create an instance. */ + FatFile() : m_attr(FILE_ATTR_CLOSED), m_error(0) {} + /** Create a file object and open it in the current working directory. + * + * \param[in] path A path with a valid 8.3 DOS name for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive + * OR of open flags. see FatFile::open(FatFile*, const char*, uint8_t). + */ + FatFile(const char* path, uint8_t oflag) { + m_attr = FILE_ATTR_CLOSED; + m_error = 0; + open(path, oflag); + } +#if DESTRUCTOR_CLOSES_FILE + ~FatFile() { + if (isOpen()) { + close(); + } + } +#endif // DESTRUCTOR_CLOSES_FILE + +#if ENABLE_ARDUINO_FEATURES + /** List directory contents. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + */ + void ls(uint8_t flags = 0) { + ls(&Serial, flags); + } + /** %Print a directory date field. + * + * Format is yyyy-mm-dd. + * + * \param[in] fatDate The date field from a directory entry. + */ + static void printFatDate(uint16_t fatDate) { + printFatDate(&Serial, fatDate); + } + /** %Print a directory time field. + * + * Format is hh:mm:ss. + * + * \param[in] fatTime The time field from a directory entry. + */ + static void printFatTime(uint16_t fatTime) { + printFatTime(&Serial, fatTime); + } + /** Print a file's name. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + size_t printName() { + return FatFile::printName(&Serial); + } +#endif // ENABLE_ARDUINO_FEATURES + + /** \return value of writeError */ + bool getWriteError() { + return m_error & WRITE_ERROR; + } + /** Set writeError to zero */ + void clearWriteError() { + m_error &= ~WRITE_ERROR; + } + /** Clear all error bits. */ + void clearError() { + m_error = 0; + } + /** \return All error bits. */ + uint8_t getError() { + return m_error; + } + /** get position for streams + * \param[out] pos struct to receive position + */ + void getpos(FatPos_t* pos); + /** set position for streams + * \param[out] pos struct with value for new position + */ + void setpos(FatPos_t* pos); + /** \return The number of bytes available from the current position + * to EOF for normal files. Zero is returned for directory files. + */ + uint32_t available() { + return isFile() ? fileSize() - curPosition() : 0; + } + /** Close a file and force cached data and directory information + * to be written to the storage device. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool close(); + /** Check for contiguous file and return its raw block range. + * + * \param[out] bgnBlock the first block address for the file. + * \param[out] endBlock the last block address for the file. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool contiguousRange(uint32_t* bgnBlock, uint32_t* endBlock); + /** Create and open a new contiguous file of a specified size. + * + * \param[in] dirFile The directory where the file will be created. + * \param[in] path A path with a valid DOS 8.3 file name. + * \param[in] size The desired file size. + * + * \return The value true is returned for success and + * the value false, is returned for failure. + */ + bool createContiguous(FatFile* dirFile, + const char* path, uint32_t size); + /** \return The current cluster number for a file or directory. */ + uint32_t curCluster() const { + return m_curCluster; + } + /** \return The current position for a file or directory. */ + uint32_t curPosition() const { + return m_curPosition; + } + /** \return Current working directory */ + static FatFile* cwd() { + return m_cwd; + } + /** Set the date/time callback function + * + * \param[in] dateTime The user's call back function. The callback + * function is of the form: + * + * \code + * void dateTime(uint16_t* date, uint16_t* time) { + * uint16_t year; + * uint8_t month, day, hour, minute, second; + * + * // User gets date and time from GPS or real-time clock here + * + * // return date using FAT_DATE macro to format fields + * *date = FAT_DATE(year, month, day); + * + * // return time using FAT_TIME macro to format fields + * *time = FAT_TIME(hour, minute, second); + * } + * \endcode + * + * Sets the function that is called when a file is created or when + * a file's directory entry is modified by sync(). All timestamps, + * access, creation, and modify, are set when a file is created. + * sync() maintains the last access date and last modify date/time. + * + * See the timestamp() function. + */ + static void dateTimeCallback( + void (*dateTime)(uint16_t* date, uint16_t* time)) { + m_dateTime = dateTime; + } + /** Cancel the date/time callback function. */ + static void dateTimeCallbackCancel() { + m_dateTime = 0; + } + /** Return a file's directory entry. + * + * \param[out] dir Location for return of the file's directory entry. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool dirEntry(dir_t* dir); + /** + * \return The index of this file in it's directory. + */ + uint16_t dirIndex() { + return m_dirIndex; + } + /** Format the name field of \a dir into the 13 byte array + * \a name in standard 8.3 short name format. + * + * \param[in] dir The directory structure containing the name. + * \param[out] name A 13 byte char array for the formatted name. + * \return length of the name. + */ + static uint8_t dirName(const dir_t* dir, char* name); + /** \return The number of bytes allocated to a directory or zero + * if an error occurs. + */ + uint32_t dirSize(); + /** Dump file in Hex + * \param[in] pr Print stream for list. + * \param[in] pos Start position in file. + * \param[in] n number of locations to dump. + */ + void dmpFile(print_t* pr, uint32_t pos, size_t n); + /** Test for the existence of a file in a directory + * + * \param[in] path Path of the file to be tested for. + * + * The calling instance must be an open directory file. + * + * dirFile.exists("TOFIND.TXT") searches for "TOFIND.TXT" in the directory + * dirFile. + * + * \return true if the file exists else false. + */ + bool exists(const char* path) { + FatFile file; + return file.open(this, path, O_READ); + } + /** + * Get a string from a file. + * + * fgets() reads bytes from a file into the array pointed to by \a str, until + * \a num - 1 bytes are read, or a delimiter is read and transferred to \a str, + * or end-of-file is encountered. The string is then terminated + * with a null byte. + * + * fgets() deletes CR, '\\r', from the string. This insures only a '\\n' + * terminates the string for Windows text files which use CRLF for newline. + * + * \param[out] str Pointer to the array where the string is stored. + * \param[in] num Maximum number of characters to be read + * (including the final null byte). Usually the length + * of the array \a str is used. + * \param[in] delim Optional set of delimiters. The default is "\n". + * + * \return For success fgets() returns the length of the string in \a str. + * If no data is read, fgets() returns zero for EOF or -1 if an error occurred. + */ + int16_t fgets(char* str, int16_t num, char* delim = 0); + /** \return The total number of bytes in a file. */ + uint32_t fileSize() const { + return m_fileSize; + } + /** \return The first cluster number for a file or directory. */ + uint32_t firstCluster() const { + return m_firstCluster; + } + /** + * Get a file's name followed by a zero byte. + * + * \param[out] name An array of characters for the file's name. + * \param[in] size The size of the array in bytes. The array + * must be at least 13 bytes long. The file's name will be + * truncated if the file's name is too long. + * \return The value true, is returned for success and + * the value false, is returned for failure. + */ + bool getName(char* name, size_t size); + /** + * Get a file's Short File Name followed by a zero byte. + * + * \param[out] name An array of characters for the file's name. + * The array must be at least 13 bytes long. + * \return The value true, is returned for success and + * the value false, is returned for failure. + */ + bool getSFN(char* name); + /** \return True if this is a directory else false. */ + bool isDir() const { + return m_attr & FILE_ATTR_DIR; + } + /** \return True if this is a normal file else false. */ + bool isFile() const { + return m_attr & FILE_ATTR_FILE; + } + /** \return True if this is a hidden file else false. */ + bool isHidden() const { + return m_attr & FILE_ATTR_HIDDEN; + } + /** \return true if this file has a Long File Name. */ + bool isLFN() const { + return m_lfnOrd; + } + /** \return True if this is an open file/directory else false. */ + bool isOpen() const { + return m_attr; + } + /** \return True if this is the root directory. */ + bool isRoot() const { + return m_attr & FILE_ATTR_ROOT; + } + /** \return True if this is the FAT32 root directory. */ + bool isRoot32() const { + return m_attr & FILE_ATTR_ROOT32; + } + /** \return True if this is the FAT12 of FAT16 root directory. */ + bool isRootFixed() const { + return m_attr & FILE_ATTR_ROOT_FIXED; + } + /** \return True if file is read-only */ + bool isReadOnly() const { + return m_attr & FILE_ATTR_READ_ONLY; + } + /** \return True if this is a subdirectory else false. */ + bool isSubDir() const { + return m_attr & FILE_ATTR_SUBDIR; + } + /** \return True if this is a system file else false. */ + bool isSystem() const { + return m_attr & FILE_ATTR_SYSTEM; + } + /** Check for a legal 8.3 character. + * \param[in] c Character to be checked. + * \return true for a legal 8.3 character else false. + */ + static bool legal83Char(uint8_t c) { + if (c == '"' || c == '|') { + return false; + } + // *+,./ + if (0X2A <= c && c <= 0X2F && c != 0X2D) { + return false; + } + // :;<=>? + if (0X3A <= c && c <= 0X3F) { + return false; + } + // [\] + if (0X5B <= c && c <= 0X5D) { + return false; + } + return 0X20 < c && c < 0X7F; + } + /** List directory contents. + * + * \param[in] pr Print stream for list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \param[in] indent Amount of space before file name. Used for recursive + * list to indicate subdirectory level. + */ + void ls(print_t* pr, uint8_t flags = 0, uint8_t indent = 0); + /** Make a new directory. + * + * \param[in] dir An open FatFile instance for the directory that will + * contain the new directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the new directory. + * + * \param[in] pFlag Create missing parent directories if true. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool mkdir(FatFile* dir, const char* path, bool pFlag = true); + /** Open a file in the volume working directory of a FatFileSystem. + * + * \param[in] fs File System where the file is located. + * + * \param[in] path with a valid 8.3 DOS name for a file to be opened. + * + * \param[in] oflag bitwise-inclusive OR of open mode flags. + * See see FatFile::open(FatFile*, const char*, uint8_t). + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool open(FatFileSystem* fs, const char* path, uint8_t oflag); + /** Open a file by index. + * + * \param[in] dirFile An open FatFile instance for the directory. + * + * \param[in] index The \a index of the directory entry for the file to be + * opened. The value for \a index is (directory file position)/32. + * + * \param[in] oflag bitwise-inclusive OR of open mode flags. + * See see FatFile::open(FatFile*, const char*, uint8_t). + * + * See open() by path for definition of flags. + * \return true for success or false for failure. + */ + bool open(FatFile* dirFile, uint16_t index, uint8_t oflag); + /** Open a file or directory by name. + * + * \param[in] dirFile An open FatFile instance for the directory containing + * the file to be opened. + * + * \param[in] path A path with a valid 8.3 DOS name for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a + * bitwise-inclusive OR of flags from the following list + * + * O_READ - Open for reading. + * + * O_RDONLY - Same as O_READ. + * + * O_WRITE - Open for writing. + * + * O_WRONLY - Same as O_WRITE. + * + * O_RDWR - Open for reading and writing. + * + * O_APPEND - If set, the file offset shall be set to the end of the + * file prior to each write. + * + * O_AT_END - Set the initial position at the end of the file. + * + * O_CREAT - If the file exists, this flag has no effect except as noted + * under O_EXCL below. Otherwise, the file shall be created + * + * O_EXCL - If O_CREAT and O_EXCL are set, open() shall fail if the file exists. + * + * O_SYNC - Call sync() after each write. This flag should not be used with + * write(uint8_t) or any functions do character at a time writes since sync() + * will be called after each byte. + * + * O_TRUNC - If the file exists and is a regular file, and the file is + * successfully opened and is not read only, its length shall be truncated to 0. + * + * WARNING: A given file must not be opened by more than one FatFile object + * or file corruption may occur. + * + * \note Directory files must be opened read only. Write and truncation is + * not allowed for directory files. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool open(FatFile* dirFile, const char* path, uint8_t oflag); + /** Open a file in the current working directory. + * + * \param[in] path A path with a valid 8.3 DOS name for a file to be opened. + * + * \param[in] oflag bitwise-inclusive OR of open mode flags. + * See see FatFile::open(FatFile*, const char*, uint8_t). + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool open(const char* path, uint8_t oflag = O_READ) { + return open(m_cwd, path, oflag); + } + /** Open the next file or subdirectory in a directory. + * + * \param[in] dirFile An open FatFile instance for the directory + * containing the file to be opened. + * + * \param[in] oflag bitwise-inclusive OR of open mode flags. + * See see FatFile::open(FatFile*, const char*, uint8_t). + * + * \return true for success or false for failure. + */ + bool openNext(FatFile* dirFile, uint8_t oflag = O_READ); + /** Open a volume's root directory. + * + * \param[in] vol The FAT volume containing the root directory to be opened. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool openRoot(FatVolume* vol); + /** Return the next available byte without consuming it. + * + * \return The byte if no error and not at eof else -1; + */ + int peek(); + /** Print a file's creation date and time + * + * \param[in] pr Print stream for output. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool printCreateDateTime(print_t* pr); + /** %Print a directory date field. + * + * Format is yyyy-mm-dd. + * + * \param[in] pr Print stream for output. + * \param[in] fatDate The date field from a directory entry. + */ + static void printFatDate(print_t* pr, uint16_t fatDate); + /** %Print a directory time field. + * + * Format is hh:mm:ss. + * + * \param[in] pr Print stream for output. + * \param[in] fatTime The time field from a directory entry. + */ + static void printFatTime(print_t* pr, uint16_t fatTime); + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + int printField(float value, char term, uint8_t prec = 2); + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \return The number of bytes written or -1 if an error occurs. + */ + int printField(int16_t value, char term); + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \return The number of bytes written or -1 if an error occurs. + */ + int printField(uint16_t value, char term); + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \return The number of bytes written or -1 if an error occurs. + */ + int printField(int32_t value, char term); + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \return The number of bytes written or -1 if an error occurs. + */ + int printField(uint32_t value, char term); + /** Print a file's modify date and time + * + * \param[in] pr Print stream for output. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool printModifyDateTime(print_t* pr); + /** Print a file's name + * + * \param[in] pr Print stream for output. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + size_t printName(print_t* pr); + /** Print a file's size. + * + * \param[in] pr Print stream for output. + * + * \return The number of characters printed is returned + * for success and zero is returned for failure. + */ + size_t printFileSize(print_t* pr); + /** Print a file's Short File Name. + * + * \param[in] pr Print stream for output. + * + * \return The number of characters printed is returned + * for success and zero is returned for failure. + */ + size_t printSFN(print_t* pr); + /** Read the next byte from a file. + * + * \return For success read returns the next byte in the file as an int. + * If an error occurs or end of file is reached -1 is returned. + */ + int read() { + uint8_t b; + return read(&b, 1) == 1 ? b : -1; + } + /** Read data from a file starting at the current position. + * + * \param[out] buf Pointer to the location that will receive the data. + * + * \param[in] nbyte Maximum number of bytes to read. + * + * \return For success read() returns the number of bytes read. + * A value less than \a nbyte, including zero, will be returned + * if end of file is reached. + * If an error occurs, read() returns -1. Possible errors include + * read() called before a file has been opened, corrupt file system + * or an I/O error occurred. + */ + int read(void* buf, size_t nbyte); + /** Read the next directory entry from a directory file. + * + * \param[out] dir The dir_t struct that will receive the data. + * + * \return For success readDir() returns the number of bytes read. + * A value of zero will be returned if end of file is reached. + * If an error occurs, readDir() returns -1. Possible errors include + * readDir() called before a directory has been opened, this is not + * a directory file or an I/O error occurred. + */ + int8_t readDir(dir_t* dir); + /** Remove a file. + * + * The directory entry and all data for the file are deleted. + * + * \note This function should not be used to delete the 8.3 version of a + * file that has a long name. For example if a file has the long name + * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT". + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool remove(); + /** Remove a file. + * + * The directory entry and all data for the file are deleted. + * + * \param[in] dirFile The directory that contains the file. + * \param[in] path Path for the file to be removed. + * + * \note This function should not be used to delete the 8.3 version of a + * file that has a long name. For example if a file has the long name + * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT". + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + static bool remove(FatFile* dirFile, const char* path); + /** Set the file's current position to zero. */ + void rewind() { + seekSet(0); + } + /** Rename a file or subdirectory. + * + * \param[in] dirFile Directory for the new path. + * \param[in] newPath New path name for the file/directory. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool rename(FatFile* dirFile, const char* newPath); + /** Remove a directory file. + * + * The directory file will be removed only if it is empty and is not the + * root directory. rmdir() follows DOS and Windows and ignores the + * read-only attribute for the directory. + * + * \note This function should not be used to delete the 8.3 version of a + * directory that has a long name. For example if a directory has the + * long name "New folder" you should not delete the 8.3 name "NEWFOL~1". + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool rmdir(); + /** Recursively delete a directory and all contained files. + * + * This is like the Unix/Linux 'rm -rf *' if called with the root directory + * hence the name. + * + * Warning - This will remove all contents of the directory including + * subdirectories. The directory will then be removed if it is not root. + * The read-only attribute for files will be ignored. + * + * \note This function should not be used to delete the 8.3 version of + * a directory that has a long name. See remove() and rmdir(). + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool rmRfStar(); + /** Set the files position to current position + \a pos. See seekSet(). + * \param[in] offset The new position in bytes from the current position. + * \return true for success or false for failure. + */ + bool seekCur(int32_t offset) { + return seekSet(m_curPosition + offset); + } + /** Set the files position to end-of-file + \a offset. See seekSet(). + * Can't be used for directory files since file size is not defined. + * \param[in] offset The new position in bytes from end-of-file. + * \return true for success or false for failure. + */ + bool seekEnd(int32_t offset = 0) { + return isFile() ? seekSet(m_fileSize + offset) : false; + } + /** Sets a file's position. + * + * \param[in] pos The new position in bytes from the beginning of the file. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool seekSet(uint32_t pos); + /** Set the current working directory. + * + * \param[in] dir New current working directory. + * + * \return true for success else false. + */ + static bool setCwd(FatFile* dir) { + if (!dir->isDir()) { + return false; + } + m_cwd = dir; + return true; + } + /** The sync() call causes all modified data and directory fields + * to be written to the storage device. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool sync(); + /** Copy a file's timestamps + * + * \param[in] file File to copy timestamps from. + * + * \note + * Modify and access timestamps may be overwritten if a date time callback + * function has been set by dateTimeCallback(). + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool timestamp(FatFile* file); + /** Set a file's timestamps in its directory entry. + * + * \param[in] flags Values for \a flags are constructed by a bitwise-inclusive + * OR of flags from the following list + * + * T_ACCESS - Set the file's last access date. + * + * T_CREATE - Set the file's creation date and time. + * + * T_WRITE - Set the file's last write/modification date and time. + * + * \param[in] year Valid range 1980 - 2107 inclusive. + * + * \param[in] month Valid range 1 - 12 inclusive. + * + * \param[in] day Valid range 1 - 31 inclusive. + * + * \param[in] hour Valid range 0 - 23 inclusive. + * + * \param[in] minute Valid range 0 - 59 inclusive. + * + * \param[in] second Valid range 0 - 59 inclusive + * + * \note It is possible to set an invalid date since there is no check for + * the number of days in a month. + * + * \note + * Modify and access timestamps may be overwritten if a date time callback + * function has been set by dateTimeCallback(). + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool timestamp(uint8_t flags, uint16_t year, uint8_t month, uint8_t day, + uint8_t hour, uint8_t minute, uint8_t second); + /** Type of file. You should use isFile() or isDir() instead of fileType() + * if possible. + * + * \return The file or directory type. + */ + uint8_t fileAttr() const { + return m_attr; + } + /** Truncate a file to a specified length. The current file position + * will be maintained if it is less than or equal to \a length otherwise + * it will be set to end of file. + * + * \param[in] length The desired length for the file. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool truncate(uint32_t length); + /** \return FatVolume that contains this file. */ + FatVolume* volume() const { + return m_vol; + } + /** Write a string to a file. Used by the Arduino Print class. + * \param[in] str Pointer to the string. + * Use getWriteError to check for errors. + * \return count of characters written for success or -1 for failure. + */ + int write(const char* str) { + return write(str, strlen(str)); + } + /** Write a single byte. + * \param[in] b The byte to be written. + * \return +1 for success or -1 for failure. + */ + int write(uint8_t b) { + return write(&b, 1); + } + /** Write data to an open file. + * + * \note Data is moved to the cache but may not be written to the + * storage device until sync() is called. + * + * \param[in] buf Pointer to the location of the data to be written. + * + * \param[in] nbyte Number of bytes to write. + * + * \return For success write() returns the number of bytes written, always + * \a nbyte. If an error occurs, write() returns -1. Possible errors + * include write() is called before a file has been opened, write is called + * for a read-only file, device is full, a corrupt file system or an I/O error. + * + */ + int write(const void* buf, size_t nbyte); +//------------------------------------------------------------------------------ + private: + /** This file has not been opened. */ + static const uint8_t FILE_ATTR_CLOSED = 0; + /** File is read-only. */ + static const uint8_t FILE_ATTR_READ_ONLY = DIR_ATT_READ_ONLY; + /** File should be hidden in directory listings. */ + static const uint8_t FILE_ATTR_HIDDEN = DIR_ATT_HIDDEN; + /** Entry is for a system file. */ + static const uint8_t FILE_ATTR_SYSTEM = DIR_ATT_SYSTEM; + /** Entry for normal data file */ + static const uint8_t FILE_ATTR_FILE = 0X08; + /** Entry is for a subdirectory */ + static const uint8_t FILE_ATTR_SUBDIR = DIR_ATT_DIRECTORY; + /** A FAT12 or FAT16 root directory */ + static const uint8_t FILE_ATTR_ROOT_FIXED = 0X20; + /** A FAT32 root directory */ + static const uint8_t FILE_ATTR_ROOT32 = 0X40; + /** Entry is for root. */ + static const uint8_t FILE_ATTR_ROOT = FILE_ATTR_ROOT_FIXED | FILE_ATTR_ROOT32; + /** Directory type bits */ + static const uint8_t FILE_ATTR_DIR = FILE_ATTR_SUBDIR | FILE_ATTR_ROOT; + /** Attributes to copy from directory entry */ + static const uint8_t FILE_ATTR_COPY = DIR_ATT_READ_ONLY | DIR_ATT_HIDDEN | + DIR_ATT_SYSTEM | DIR_ATT_DIRECTORY; + + /** experimental don't use */ + + bool openParent(FatFile* dir); + + // private functions + bool addCluster(); + bool addDirCluster(); + dir_t* cacheDirEntry(uint8_t action); + static uint8_t lfnChecksum(uint8_t* name); + bool lfnUniqueSfn(fname_t* fname); + bool openCluster(FatFile* file); + static bool parsePathName(const char* str, fname_t* fname, const char** ptr); + bool mkdir(FatFile* parent, fname_t* fname); + bool open(FatFile* dirFile, fname_t* fname, uint8_t oflag); + bool openCachedEntry(FatFile* dirFile, uint16_t cacheIndex, uint8_t oflag, + uint8_t lfnOrd); + bool readLBN(uint32_t* lbn); + dir_t* readDirCache(bool skipReadOk = false); + bool setDirSize(); + + // bits defined in m_flags + // should be 0X0F + static uint8_t const F_OFLAG = (O_ACCMODE | O_APPEND | O_SYNC); + // sync of directory entry required + static uint8_t const F_FILE_DIR_DIRTY = 0X80; + + // global pointer to cwd dir + static FatFile* m_cwd; + // data time callback function + static void (*m_dateTime)(uint16_t* date, uint16_t* time); + // private data + static const uint8_t WRITE_ERROR = 0X1; + static const uint8_t READ_ERROR = 0X2; + uint8_t m_attr; // File attributes + uint8_t m_error; // Error bits. + uint8_t m_flags; // See above for definition of m_flags bits + uint8_t m_lfnOrd; + uint16_t m_dirIndex; // index of directory entry in dir file + FatVolume* m_vol; // volume where file is located + uint32_t m_dirCluster; + uint32_t m_curCluster; // cluster for current file position + uint32_t m_curPosition; // current file position + uint32_t m_dirBlock; // block for this files directory entry + uint32_t m_fileSize; // file size in bytes + uint32_t m_firstCluster; // first cluster of file +}; +#endif // FatFile_h diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/FatFileLFN.cpp b/Firmware_V2/src/libraries/SdFat/FatLib/FatFileLFN.cpp new file mode 100644 index 0000000..5ab9f45 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/FatFileLFN.cpp @@ -0,0 +1,683 @@ +/* FatLib Library + * Copyright (C) 2012 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#include "FatFile.h" +//------------------------------------------------------------------------------ +// +uint8_t FatFile::lfnChecksum(uint8_t* name) { + uint8_t sum = 0; + for (uint8_t i = 0; i < 11; i++) { + sum = (((sum & 1) << 7) | ((sum & 0xfe) >> 1)) + name[i]; + } + return sum; +} +#if USE_LONG_FILE_NAMES +//------------------------------------------------------------------------------ +// Saves about 90 bytes of flash on 328 over tolower(). +inline char lfnToLower(char c) { + return 'A' <= c && c <= 'Z' ? c + 'a' - 'A' : c; +} +//------------------------------------------------------------------------------ +// Daniel Bernstein University of Illinois at Chicago. +// Original had + instead of ^ +static uint16_t Bernstein(uint16_t hash, const char *str, size_t len) { + for (size_t i = 0; i < len; i++) { + // hash = hash * 33 ^ str[i]; + hash = ((hash << 5) + hash) ^ str[i]; + } + return hash; +} +//------------------------------------------------------------------------------ +/** + * Fetch a 16-bit long file name character. + * + * \param[in] ldir Pointer to long file name directory entry. + * \param[in] i Index of character. + * \return The 16-bit character. + */ +static uint16_t lfnGetChar(ldir_t *ldir, uint8_t i) { + if (i < LDIR_NAME1_DIM) { + return ldir->name1[i]; + } else if (i < (LDIR_NAME1_DIM + LDIR_NAME2_DIM)) { + return ldir->name2[i - LDIR_NAME1_DIM]; + } else if (i < (LDIR_NAME1_DIM + LDIR_NAME2_DIM + LDIR_NAME2_DIM)) { + return ldir->name3[i - LDIR_NAME1_DIM - LDIR_NAME2_DIM]; + } + return 0; +} +//------------------------------------------------------------------------------ +static bool lfnGetName(ldir_t *ldir, char* name, size_t n) { + uint8_t i; + size_t k = 13*((ldir->ord & 0X1F) - 1); + for (i = 0; i < 13; i++) { + uint16_t c = lfnGetChar(ldir, i); + if (c == 0 || k >= n) { + break; + } + name[k++] = c >= 0X7F ? '?' : c; + } + // Terminate with zero byte if name fits. + if (k < n && (ldir->ord & LDIR_ORD_LAST_LONG_ENTRY)) { + name[k] = 0; + } + // Truncate if name is too long. + name[n - 1] = 0; + return true; +} +//------------------------------------------------------------------------------ +inline bool lfnLegalChar(char c) { + if (c == '/' || c == '\\' || c == '"' || c == '*' || + c == ':' || c == '<' || c == '>' || c == '?' || c == '|') { + return false; + } + return 0X1F < c && c < 0X7F; +} +//------------------------------------------------------------------------------ +/** + * Store a 16-bit long file name character. + * + * \param[in] ldir Pointer to long file name directory entry. + * \param[in] i Index of character. + * \param[in] c The 16-bit character. + */ +static void lfnPutChar(ldir_t *ldir, uint8_t i, uint16_t c) { + if (i < LDIR_NAME1_DIM) { + ldir->name1[i] = c; + } else if (i < (LDIR_NAME1_DIM + LDIR_NAME2_DIM)) { + ldir->name2[i - LDIR_NAME1_DIM] = c; + } else if (i < (LDIR_NAME1_DIM + LDIR_NAME2_DIM + LDIR_NAME2_DIM)) { + ldir->name3[i - LDIR_NAME1_DIM - LDIR_NAME2_DIM] = c; + } +} +//------------------------------------------------------------------------------ +static void lfnPutName(ldir_t *ldir, const char* name, size_t n) { + size_t k = 13*((ldir->ord & 0X1F) - 1); + for (uint8_t i = 0; i < 13; i++, k++) { + uint16_t c = k < n ? name[k] : k == n ? 0 : 0XFFFF; + lfnPutChar(ldir, i, c); + } +} +//============================================================================== +bool FatFile::getName(char* name, size_t size) { + FatFile dirFile; + ldir_t* ldir; + if (!isOpen() || size < 13) { + DBG_FAIL_MACRO; + goto fail; + } + if (!isLFN()) { + return getSFN(name); + } + if (!dirFile.openCluster(this)) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t ord = 1; ord <= m_lfnOrd; ord++) { + if (!dirFile.seekSet(32UL*(m_dirIndex - ord))) { + DBG_FAIL_MACRO; + goto fail; + } + ldir = reinterpret_cast(dirFile.readDirCache()); + if (!ldir) { + DBG_FAIL_MACRO; + goto fail; + } + if (ldir->attr != DIR_ATT_LONG_NAME) { + DBG_FAIL_MACRO; + goto fail; + } + if (ord != (ldir->ord & 0X1F)) { + DBG_FAIL_MACRO; + goto fail; + } + if (!lfnGetName(ldir, name, size)) { + DBG_FAIL_MACRO; + goto fail; + } + if (ldir->ord & LDIR_ORD_LAST_LONG_ENTRY) { + return true; + } + } + // Fall into fail. + DBG_FAIL_MACRO; + +fail: + name[0] = 0; + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::openCluster(FatFile* file) { + if (file->m_dirCluster == 0) { + return openRoot(file->m_vol); + } + memset(this, 0, sizeof(FatFile)); + m_attr = FILE_ATTR_SUBDIR; + m_flags = O_READ; + m_vol = file->m_vol; + m_firstCluster = file->m_dirCluster; + return true; +} +//------------------------------------------------------------------------------ +bool FatFile::parsePathName(const char* path, + fname_t* fname, const char** ptr) { + char c; + bool is83; + uint8_t bit = DIR_NT_LC_BASE; + uint8_t lc = 0; + uint8_t uc = 0; + uint8_t i = 0; + uint8_t in = 7; + int end; + int len = 0; + int si; + int dot; + + // Skip leading spaces. + while (*path == ' ') { + path++; + } + fname->lfn = path; + + for (len = 0; ; len++) { + c = path[len]; + if (c == 0 || isDirSeparator(c)) { + break; + } + if (!lfnLegalChar(c)) { + return false; + } + } + // Advance to next path component. + for (end = len; path[end] == ' ' || isDirSeparator(path[end]); end++) {} + *ptr = &path[end]; + + // Back over spaces and dots. + while (len) { + c = path[len - 1]; + if (c != '.' && c != ' ') { + break; + } + len--; + } + // Max length of LFN is 255. + if (len > 255) { + return false; + } + fname->len = len; + // Blank file short name. + for (uint8_t k = 0; k < 11; k++) { + fname->sfn[k] = ' '; + } + // skip leading spaces and dots. + for (si = 0; path[si] == '.' || path[si] == ' '; si++) {} + // Not 8.3 if leading dot or space. + is83 = !si; + + // find last dot. + for (dot = len - 1; dot >= 0 && path[dot] != '.'; dot--) {} + for (; si < len; si++) { + c = path[si]; + if (c == ' ' || (c == '.' && dot != si)) { + is83 = false; + continue; + } + if (!legal83Char(c) && si != dot) { + is83 = false; + c = '_'; + } + if (si == dot || i > in) { + if (in == 10) { + // Done - extension longer than three characters. + is83 = false; + break; + } + if (si != dot) { + is83 = false; + } + // Break if no dot and base-name is longer than eight characters. + if (si > dot) { + break; + } + si = dot; + in = 10; // Max index for full 8.3 name. + i = 8; // Place for extension. + bit = DIR_NT_LC_EXT; // bit for extension. + } else { + if ('a' <= c && c <= 'z') { + c += 'A' - 'a'; + lc |= bit; + } else if ('A' <= c && c <= 'Z') { + uc |= bit; + } + fname->sfn[i++] = c; + if (i < 7) { + fname->seqPos = i; + } + } + } + if (fname->sfn[0] == ' ') { + return false; + } + + if (is83) { + fname->flags = lc & uc ? FNAME_FLAG_MIXED_CASE : lc; + } else { + fname->flags = FNAME_FLAG_LOST_CHARS; + fname->sfn[fname->seqPos] = '~'; + fname->sfn[fname->seqPos + 1] = '1'; + } + return true; +} +//------------------------------------------------------------------------------ +bool FatFile::open(FatFile* dirFile, fname_t* fname, uint8_t oflag) { + bool fnameFound = false; + uint8_t lfnOrd = 0; + uint8_t freeNeed; + uint8_t freeFound = 0; + uint8_t ord = 0; + uint8_t chksum = 0; + uint16_t freeIndex = 0; + uint16_t curIndex; + dir_t* dir; + ldir_t* ldir; + size_t len = fname->len; + + if (!dirFile->isDir() || isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + // Number of directory entries needed. + freeNeed = fname->flags & FNAME_FLAG_NEED_LFN ? 1 + (len + 12)/13 : 1; + + dirFile->rewind(); + while (1) { + curIndex = dirFile->m_curPosition/32; + dir = dirFile->readDirCache(true); + if (!dir) { + if (dirFile->getError()) { + DBG_FAIL_MACRO; + goto fail; + } + // At EOF + goto create; + } + if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == DIR_NAME_FREE) { + if (freeFound == 0) { + freeIndex = curIndex; + } + if (freeFound < freeNeed) { + freeFound++; + } + if (dir->name[0] == DIR_NAME_FREE) { + goto create; + } + } else { + if (freeFound < freeNeed) { + freeFound = 0; + } + } + // skip empty slot or '.' or '..' + if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.') { + lfnOrd = 0; + } else if (DIR_IS_LONG_NAME(dir)) { + ldir_t *ldir = reinterpret_cast(dir); + if (!lfnOrd) { + if ((ldir->ord & LDIR_ORD_LAST_LONG_ENTRY) == 0) { + continue; + } + lfnOrd = ord = ldir->ord & 0X1F; + chksum = ldir->chksum; + } else if (ldir->ord != --ord || chksum != ldir->chksum) { + lfnOrd = 0; + continue; + } + size_t k = 13*(ord - 1); + if (k >= len) { + // Not found. + lfnOrd = 0; + continue; + } + for (uint8_t i = 0; i < 13; i++) { + uint16_t u = lfnGetChar(ldir, i); + if (k == len) { + if (u != 0) { + // Not found. + lfnOrd = 0; + } + break; + } + if (u > 255 || lfnToLower(u) != lfnToLower(fname->lfn[k++])) { + // Not found. + lfnOrd = 0; + break; + } + } + } else if (DIR_IS_FILE_OR_SUBDIR(dir)) { + if (lfnOrd) { + if (1 == ord && lfnChecksum(dir->name) == chksum) { + goto found; + } + DBG_FAIL_MACRO; + goto fail; + } + if (!memcmp(dir->name, fname->sfn, sizeof(fname->sfn))) { + if (!(fname->flags & FNAME_FLAG_LOST_CHARS)) { + goto found; + } + fnameFound = true; + } + } else { + lfnOrd = 0; + } + } + +found: + // Don't open if create only. + if (oflag & O_EXCL) { + DBG_FAIL_MACRO; + goto fail; + } + goto open; + +create: + // don't create unless O_CREAT and O_WRITE + if (!(oflag & O_CREAT) || !(oflag & O_WRITE)) { + DBG_FAIL_MACRO; + goto fail; + } + // If at EOF start in next cluster. + if (freeFound == 0) { + freeIndex = curIndex; + } + + while (freeFound < freeNeed) { + dir = dirFile->readDirCache(); + if (!dir) { + if (dirFile->getError()) { + DBG_FAIL_MACRO; + goto fail; + } + // EOF if no error. + break; + } + freeFound++; + } + while (freeFound < freeNeed) { + // Will fail if FAT16 root. + if (!dirFile->addDirCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + // Done if more than one block per cluster. Max freeNeed is 21. + if (dirFile->m_vol->blocksPerCluster() > 1) { + break; + } + freeFound += 16; + } + if (fnameFound) { + if (!dirFile->lfnUniqueSfn(fname)) { + goto fail; + } + } + if (!dirFile->seekSet(32UL*freeIndex)) { + DBG_FAIL_MACRO; + goto fail; + } + lfnOrd = freeNeed - 1; + for (uint8_t ord = lfnOrd ; ord ; ord--) { + ldir = reinterpret_cast(dirFile->readDirCache()); + if (!ldir) { + DBG_FAIL_MACRO; + goto fail; + } + dirFile->m_vol->cacheDirty(); + ldir->ord = ord == lfnOrd ? LDIR_ORD_LAST_LONG_ENTRY | ord : ord; + ldir->attr = DIR_ATT_LONG_NAME; + ldir->type = 0; + ldir->chksum = lfnChecksum(fname->sfn); + ldir->mustBeZero = 0; + lfnPutName(ldir, fname->lfn, len); + } + curIndex = dirFile->m_curPosition/32; + dir = dirFile->readDirCache(); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // initialize as empty file + memset(dir, 0, sizeof(dir_t)); + memcpy(dir->name, fname->sfn, 11); + + // Set base-name and extension lower case bits. + dir->reservedNT = (DIR_NT_LC_BASE | DIR_NT_LC_EXT) & fname->flags; + + // set timestamps + if (m_dateTime) { + // call user date/time function + m_dateTime(&dir->creationDate, &dir->creationTime); + } else { + // use default date/time + dir->creationDate = FAT_DEFAULT_DATE; + dir->creationTime = FAT_DEFAULT_TIME; + } + dir->lastAccessDate = dir->creationDate; + dir->lastWriteDate = dir->creationDate; + dir->lastWriteTime = dir->creationTime; + + // Force write of entry to device. + dirFile->m_vol->cacheDirty(); + +open: + // open entry in cache. + if (!openCachedEntry(dirFile, curIndex, oflag, lfnOrd)) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +size_t FatFile::printName(print_t* pr) { + FatFile dirFile; + uint16_t u; + size_t n = 0; + ldir_t* ldir; + + if (!isLFN()) { + return printSFN(pr); + } + if (!dirFile.openCluster(this)) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t ord = 1; ord <= m_lfnOrd; ord++) { + if (!dirFile.seekSet(32UL*(m_dirIndex - ord))) { + DBG_FAIL_MACRO; + goto fail; + } + ldir = reinterpret_cast(dirFile.readDirCache()); + if (!ldir) { + DBG_FAIL_MACRO; + goto fail; + } + if (ldir->attr != DIR_ATT_LONG_NAME || + ord != (ldir->ord & 0X1F)) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t i = 0; i < 13; i++) { + u = lfnGetChar(ldir, i); + if (u == 0) { + // End of name. + break; + } + if (u > 0X7E) { + u = '?'; + } + pr->write(static_cast(u)); + n++; + } + if (ldir->ord & LDIR_ORD_LAST_LONG_ENTRY) { + return n; + } + } + // Fall into fail; + DBG_FAIL_MACRO; + +fail: + return 0; +} +//------------------------------------------------------------------------------ +bool FatFile::remove() { + bool last; + uint8_t chksum; + uint8_t ord; + FatFile dirFile; + dir_t* dir; + ldir_t* ldir; + + // Cant' remove not open for write. + if (!isFile() || !(m_flags & O_WRITE)) { + DBG_FAIL_MACRO; + goto fail; + } + // Free any clusters. + if (m_firstCluster && !m_vol->freeChain(m_firstCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + // Cache directory entry. + dir = cacheDirEntry(FatCache::CACHE_FOR_WRITE); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + chksum = lfnChecksum(dir->name); + + // Mark entry deleted. + dir->name[0] = DIR_NAME_DELETED; + + // Set this file closed. + m_attr = FILE_ATTR_CLOSED; + + // Write entry to device. + if (!m_vol->cacheSync()) { + DBG_FAIL_MACRO; + goto fail; + } + if (!isLFN()) { + // Done, no LFN entries. + return true; + } + if (!dirFile.openCluster(this)) { + DBG_FAIL_MACRO; + goto fail; + } + for (ord = 1; ord <= m_lfnOrd; ord++) { + if (!dirFile.seekSet(32UL*(m_dirIndex - ord))) { + DBG_FAIL_MACRO; + goto fail; + } + ldir = reinterpret_cast(dirFile.readDirCache()); + if (!ldir) { + DBG_FAIL_MACRO; + goto fail; + } + if (ldir->attr != DIR_ATT_LONG_NAME || + ord != (ldir->ord & 0X1F) || + chksum != ldir->chksum) { + DBG_FAIL_MACRO; + goto fail; + } + last = ldir->ord & LDIR_ORD_LAST_LONG_ENTRY; + ldir->ord = DIR_NAME_DELETED; + m_vol->cacheDirty(); + if (last) { + if (!m_vol->cacheSync()) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + } + } + // Fall into fail. + DBG_FAIL_MACRO; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::lfnUniqueSfn(fname_t* fname) { + const uint8_t FIRST_HASH_SEQ = 2; // min value is 2 + uint8_t pos = fname->seqPos;; + dir_t *dir; + uint16_t hex; + + DBG_HALT_IF(!(fname->flags & FNAME_FLAG_LOST_CHARS)); + DBG_HALT_IF(fname->sfn[pos] != '~' && fname->sfn[pos + 1] != '1'); + + for (uint8_t seq = 2; seq < 100; seq++) { + if (seq < FIRST_HASH_SEQ) { + fname->sfn[pos + 1] = '0' + seq; + } else { + DBG_PRINT_IF(seq > FIRST_HASH_SEQ); + hex = Bernstein(seq + fname->len, fname->lfn, fname->len); + if (pos > 3) { + // Make space in name for ~HHHH. + pos = 3; + } + for (uint8_t i = pos + 4 ; i > pos; i--) { + uint8_t h = hex & 0XF; + fname->sfn[i] = h < 10 ? h + '0' : h + 'A' - 10; + hex >>= 4; + } + } + fname->sfn[pos] = '~'; + rewind(); + while (1) { + dir = readDirCache(true); + if (!dir) { + if (!getError()) { + // At EOF and name not found if no error. + goto done; + } + DBG_FAIL_MACRO; + goto fail; + } + if (dir->name[0] == DIR_NAME_FREE) { + goto done; + } + if (DIR_IS_FILE_OR_SUBDIR(dir) && !memcmp(fname->sfn, dir->name, 11)) { + // Name found - try another. + break; + } + } + } + // fall inti fail - too many tries. + DBG_FAIL_MACRO; + +fail: + return false; + +done: + return true; +} +#endif // #if USE_LONG_FILE_NAMES diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/FatFilePrint.cpp b/Firmware_V2/src/libraries/SdFat/FatLib/FatFilePrint.cpp new file mode 100644 index 0000000..c6a0661 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/FatFilePrint.cpp @@ -0,0 +1,248 @@ +/* FatLib Library + * Copyright (C) 2012 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#include +#include "FatFile.h" +#include "FmtNumber.h" +//------------------------------------------------------------------------------ +// print uint8_t with width 2 +static void print2u(print_t* pr, uint8_t v) { + char c0 = '?'; + char c1 = '?'; + if (v < 100) { + c1 = v/10; + c0 = v - 10*c1 + '0'; + c1 += '0'; + } + pr->write(c1); + pr->write(c0); +} +//------------------------------------------------------------------------------ +static void printU32(print_t* pr, uint32_t v) { + char buf[11]; + char* ptr = buf + sizeof(buf); + *--ptr = 0; + pr->write(fmtDec(v, ptr)); +} +//------------------------------------------------------------------------------ +static void printHex(print_t* pr, uint8_t w, uint16_t h) { + char buf[5]; + char* ptr = buf + sizeof(buf); + *--ptr = 0; + for (uint8_t i = 0; i < w; i++) { + char c = h & 0XF; + *--ptr = c < 10 ? c + '0' : c + 'A' - 10; + h >>= 4; + } + pr->write(ptr); +} +//------------------------------------------------------------------------------ +void FatFile::dmpFile(print_t* pr, uint32_t pos, size_t n) { + char text[17]; + text[16] = 0; + if (n >= 0XFFF0) { + n = 0XFFF0; + } + if (!seekSet(pos)) { + return; + } + for (size_t i = 0; i <= n; i++) { + if ((i & 15) == 0) { + if (i) { + pr->write(' '); + pr->write(text); + if (i == n) { + break; + } + } + pr->write('\r'); + pr->write('\n'); + if (i >= n) { + break; + } + printHex(pr, 4, i); + pr->write(' '); + } + int16_t h = read(); + if (h < 0) { + break; + } + pr->write(' '); + printHex(pr, 2, h); + text[i&15] = ' ' <= h && h < 0X7F ? h : '.'; + } + pr->write('\r'); + pr->write('\n'); +} +//------------------------------------------------------------------------------ +void FatFile::ls(print_t* pr, uint8_t flags, uint8_t indent) { + FatFile file; + rewind(); + while (file.openNext(this, O_READ)) { + // indent for dir level + if (!file.isHidden() || (flags & LS_A)) { + for (uint8_t i = 0; i < indent; i++) { + pr->write(' '); + } + if (flags & LS_DATE) { + file.printModifyDateTime(pr); + pr->write(' '); + } + if (flags & LS_SIZE) { + file.printFileSize(pr); + pr->write(' '); + } + file.printName(pr); + if (file.isDir()) { + pr->write('/'); + } + pr->write('\r'); + pr->write('\n'); + if ((flags & LS_R) && file.isDir()) { + file.ls(pr, flags, indent + 2); + } + } + file.close(); + } +} +//------------------------------------------------------------------------------ +bool FatFile::printCreateDateTime(print_t* pr) { + dir_t dir; + if (!dirEntry(&dir)) { + DBG_FAIL_MACRO; + goto fail; + } + printFatDate(pr, dir.creationDate); + pr->write(' '); + printFatTime(pr, dir.creationTime); + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +void FatFile::printFatDate(print_t* pr, uint16_t fatDate) { + printU32(pr, FAT_YEAR(fatDate)); + pr->write('-'); + print2u(pr, FAT_MONTH(fatDate)); + pr->write('-'); + print2u(pr, FAT_DAY(fatDate)); +} +//------------------------------------------------------------------------------ +void FatFile::printFatTime(print_t* pr, uint16_t fatTime) { + print2u(pr, FAT_HOUR(fatTime)); + pr->write(':'); + print2u(pr, FAT_MINUTE(fatTime)); + pr->write(':'); + print2u(pr, FAT_SECOND(fatTime)); +} +//------------------------------------------------------------------------------ +/** Template for FatFile::printField() */ +template +static int printFieldT(FatFile* file, char sign, Type value, char term) { + char buf[3*sizeof(Type) + 3]; + char* str = &buf[sizeof(buf)]; + + if (term) { + *--str = term; + if (term == '\n') { + *--str = '\r'; + } + } +#ifdef OLD_FMT + do { + Type m = value; + value /= 10; + *--str = '0' + m - 10*value; + } while (value); +#else // OLD_FMT + str = fmtDec(value, str); +#endif // OLD_FMT + if (sign) { + *--str = sign; + } + return file->write(str, &buf[sizeof(buf)] - str); +} +//------------------------------------------------------------------------------ + +int FatFile::printField(float value, char term, uint8_t prec) { + char buf[24]; + char* str = &buf[sizeof(buf)]; + if (term) { + *--str = term; + if (term == '\n') { + *--str = '\r'; + } + } + str = fmtFloat(value, str, prec); + return write(str, buf + sizeof(buf) - str); +} +//------------------------------------------------------------------------------ +int FatFile::printField(uint16_t value, char term) { + return printFieldT(this, 0, value, term); +} +//------------------------------------------------------------------------------ +int FatFile::printField(int16_t value, char term) { + char sign = 0; + if (value < 0) { + sign = '-'; + value = -value; + } + return printFieldT(this, sign, (uint16_t)value, term); +} +//------------------------------------------------------------------------------ +int FatFile::printField(uint32_t value, char term) { + return printFieldT(this, 0, value, term); +} +//------------------------------------------------------------------------------ +int FatFile::printField(int32_t value, char term) { + char sign = 0; + if (value < 0) { + sign = '-'; + value = -value; + } + return printFieldT(this, sign, (uint32_t)value, term); +} +//------------------------------------------------------------------------------ +bool FatFile::printModifyDateTime(print_t* pr) { + dir_t dir; + if (!dirEntry(&dir)) { + DBG_FAIL_MACRO; + goto fail; + } + printFatDate(pr, dir.lastWriteDate); + pr->write(' '); + printFatTime(pr, dir.lastWriteTime); + return true; + +fail: + return false; +} + +//------------------------------------------------------------------------------ +size_t FatFile::printFileSize(print_t* pr) { + char buf[11]; + char *ptr = buf + sizeof(buf); + *--ptr = 0; + ptr = fmtDec(fileSize(), ptr); + while (ptr > buf) { + *--ptr = ' '; + } + return pr->write(buf); +} diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/FatFileSFN.cpp b/Firmware_V2/src/libraries/SdFat/FatLib/FatFileSFN.cpp new file mode 100644 index 0000000..4b89f09 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/FatFileSFN.cpp @@ -0,0 +1,273 @@ +/* FatLib Library + * Copyright (C) 2012 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#include "FatFile.h" +#include "FatFileSystem.h" +//------------------------------------------------------------------------------ +bool FatFile::getSFN(char* name) { + dir_t* dir; + if (!isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + if (isRoot()) { + name[0] = '/'; + name[1] = '\0'; + return true; + } + // cache entry + dir = cacheDirEntry(FatCache::CACHE_FOR_READ); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // format name + dirName(dir, name); + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +size_t FatFile::printSFN(print_t* pr) { + char name[13]; + if (!getSFN(name)) { + DBG_FAIL_MACRO; + goto fail; + } + return pr->write(name); + +fail: + return 0; +} +#if !USE_LONG_FILE_NAMES +//------------------------------------------------------------------------------ +bool FatFile::getName(char* name, size_t size) { + return size < 13 ? 0 : getSFN(name); +} +//------------------------------------------------------------------------------ +// format directory name field from a 8.3 name string +bool FatFile::parsePathName(const char* path, fname_t* fname, + const char** ptr) { + uint8_t uc = 0; + uint8_t lc = 0; + uint8_t bit = FNAME_FLAG_LC_BASE; + // blank fill name and extension + for (uint8_t i = 0; i < 11; i++) { + fname->sfn[i] = ' '; + } + + for (uint8_t i = 0, n = 7;; path++) { + uint8_t c = *path; + if (c == 0 || isDirSeparator(c)) { + // Done. + break; + } + if (c == '.' && n == 7) { + n = 10; // max index for full 8.3 name + i = 8; // place for extension + + // bit for extension. + bit = FNAME_FLAG_LC_EXT; + } else { + if (!legal83Char(c) || i > n) { + DBG_FAIL_MACRO; + goto fail; + } + if ('a' <= c && c <= 'z') { + c += 'A' - 'a'; + lc |= bit; + } else if ('A' <= c && c <= 'Z') { + uc |= bit; + } + fname->sfn[i++] = c; + } + } + // must have a file name, extension is optional + if (fname->sfn[0] == ' ') { + DBG_FAIL_MACRO; + goto fail; + } + // Set base-name and extension bits. + fname->flags = lc & uc ? 0 : lc; + while (isDirSeparator(*path)) { + path++; + } + *ptr = path; + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +// open with filename in fname +#define SFN_OPEN_USES_CHKSUM 0 +bool FatFile::open(FatFile* dirFile, fname_t* fname, uint8_t oflag) { + bool emptyFound = false; +#if SFN_OPEN_USES_CHKSUM + uint8_t chksum; +#endif + uint8_t lfnOrd = 0; + uint16_t emptyIndex; + uint16_t index = 0; + dir_t* dir; + ldir_t* ldir; + + dirFile->rewind(); + while (1) { + if (!emptyFound) { + emptyIndex = index; + } + dir = dirFile->readDirCache(true); + if (!dir) { + if (dirFile->getError()) { + DBG_FAIL_MACRO; + goto fail; + } + // At EOF if no error. + break; + } + if (dir->name[0] == DIR_NAME_FREE) { + emptyFound = true; + break; + } + if (dir->name[0] == DIR_NAME_DELETED) { + lfnOrd = 0; + emptyFound = true; + } else if (DIR_IS_FILE_OR_SUBDIR(dir)) { + if (!memcmp(fname->sfn, dir->name, 11)) { + // don't open existing file if O_EXCL + if (oflag & O_EXCL) { + DBG_FAIL_MACRO; + goto fail; + } +#if SFN_OPEN_USES_CHKSUM + if (lfnOrd && chksum != lfnChecksum(dir->name)) { + DBG_FAIL_MACRO; + goto fail; + } +#endif // SFN_OPEN_USES_CHKSUM + if (!openCachedEntry(dirFile, index, oflag, lfnOrd)) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + } else { + lfnOrd = 0; + } + } else if (DIR_IS_LONG_NAME(dir)) { + ldir = reinterpret_cast(dir); + if (ldir->ord & LDIR_ORD_LAST_LONG_ENTRY) { + lfnOrd = ldir->ord & 0X1F; +#if SFN_OPEN_USES_CHKSUM + chksum = ldir->chksum; +#endif // SFN_OPEN_USES_CHKSUM + } + } else { + lfnOrd = 0; + } + index++; + } + // don't create unless O_CREAT and O_WRITE + if (!(oflag & O_CREAT) || !(oflag & O_WRITE)) { + DBG_FAIL_MACRO; + goto fail; + } + if (emptyFound) { + index = emptyIndex; + } else { + if (!dirFile->addDirCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + } + if (!dirFile->seekSet(32UL*index)) { + DBG_FAIL_MACRO; + goto fail; + } + dir = dirFile->readDirCache(); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // initialize as empty file + memset(dir, 0, sizeof(dir_t)); + memcpy(dir->name, fname->sfn, 11); + + // Set base-name and extension lower case bits. + dir->reservedNT = (DIR_NT_LC_BASE | DIR_NT_LC_EXT) & fname->flags; + + // set timestamps + if (m_dateTime) { + // call user date/time function + m_dateTime(&dir->creationDate, &dir->creationTime); + } else { + // use default date/time + dir->creationDate = FAT_DEFAULT_DATE; + dir->creationTime = FAT_DEFAULT_TIME; + } + dir->lastAccessDate = dir->creationDate; + dir->lastWriteDate = dir->creationDate; + dir->lastWriteTime = dir->creationTime; + + // Force write of entry to device. + dirFile->m_vol->cacheDirty(); + + // open entry in cache. + return openCachedEntry(dirFile, index, oflag, 0); + +fail: + return false; +} +//------------------------------------------------------------------------------ +size_t FatFile::printName(print_t* pr) { + return printSFN(pr); +} +//------------------------------------------------------------------------------ +bool FatFile::remove() { + dir_t* dir; + // Can't remove if LFN or not open for write. + if (!isFile() || isLFN() || !(m_flags & O_WRITE)) { + DBG_FAIL_MACRO; + goto fail; + } + // Free any clusters. + if (m_firstCluster && !m_vol->freeChain(m_firstCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + // Cache directory entry. + dir = cacheDirEntry(FatCache::CACHE_FOR_WRITE); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // Mark entry deleted. + dir->name[0] = DIR_NAME_DELETED; + + // Set this file closed. + m_attr = FILE_ATTR_CLOSED; + + // Write entry to device. + return m_vol->cacheSync(); + +fail: + return false; +} +#endif // !USE_LONG_FILE_NAMES diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/FatFileSystem.h b/Firmware_V2/src/libraries/SdFat/FatLib/FatFileSystem.h new file mode 100644 index 0000000..84ae5c6 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/FatFileSystem.h @@ -0,0 +1,319 @@ +/* FatLib Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#ifndef FatFileSystem_h +#define FatFileSystem_h +#include "FatVolume.h" +#include "FatFile.h" +#include "ArduinoFiles.h" +/** + * \file + * \brief FatFileSystem class + */ +//------------------------------------------------------------------------------ +/** + * \class FatFileSystem + * \brief Integration class for the FatLib library. + */ +class FatFileSystem : public FatVolume { + public: + /** + * Initialize an FatFileSystem object. + * \param[in] part partition to initialize. + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool begin(uint8_t part = 0) { + vwd()->close(); + return (part ? init(part) : init(1) || init(0)) + && vwd()->openRoot(this) && FatFile::setCwd(vwd()); + } +#if ENABLE_ARDUINO_FEATURES + /** List the directory contents of the volume working directory to Serial. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + */ + void ls(uint8_t flags = 0) { + ls(&Serial, flags); + } + /** List the directory contents of a directory to Serial. + * + * \param[in] path directory to list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + */ + void ls(const char* path, uint8_t flags = 0) { + ls(&Serial, path, flags); + } + /** open a file + * + * \param[in] path location of file to be opened. + * \param[in] mode open mode flags. + * \return a File object. + */ + File open(const char *path, uint8_t mode = FILE_READ) { + File tmpFile; + tmpFile.open(vwd(), path, mode); + return tmpFile; + } + /** open a file + * + * \param[in] path location of file to be opened. + * \param[in] mode open mode flags. + * \return a File object. + */ + File open(const String &path, uint8_t mode = FILE_READ) { + return open(path.c_str(), mode ); + } +#endif // ENABLE_ARDUINO_FEATURES + /** Change a volume's working directory to root + * + * Changes the volume's working directory to the SD's root directory. + * Optionally set the current working directory to the volume's + * working directory. + * + * \param[in] set_cwd Set the current working directory to this volume's + * working directory if true. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool chdir(bool set_cwd = false) { + vwd()->close(); + return vwd()->openRoot(this) && (set_cwd ? FatFile::setCwd(vwd()) : true); + } + /** Change a volume's working directory + * + * Changes the volume working directory to the \a path subdirectory. + * Optionally set the current working directory to the volume's + * working directory. + * + * Example: If the volume's working directory is "/DIR", chdir("SUB") + * will change the volume's working directory from "/DIR" to "/DIR/SUB". + * + * If path is "/", the volume's working directory will be changed to the + * root directory + * + * \param[in] path The name of the subdirectory. + * + * \param[in] set_cwd Set the current working directory to this volume's + * working directory if true. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + //---------------------------------------------------------------------------- + bool chdir(const char *path, bool set_cwd = false) { + FatFile dir; + if (path[0] == '/' && path[1] == '\0') { + return chdir(set_cwd); + } + if (!dir.open(vwd(), path, O_READ)) { + goto fail; + } + if (!dir.isDir()) { + goto fail; + } +// *m_vwd = dir; + m_vwd = dir; + if (set_cwd) { + FatFile::setCwd(vwd()); + } + return true; + +fail: + return false; + } + //---------------------------------------------------------------------------- + /** Set the current working directory to a volume's working directory. + * + * This is useful with multiple SD cards. + * + * The current working directory is changed to this + * volume's working directory. + * + * This is like the Windows/DOS \: command. + */ + void chvol() { + FatFile::setCwd(vwd()); + } + //---------------------------------------------------------------------------- + /** + * Test for the existence of a file. + * + * \param[in] path Path of the file to be tested for. + * + * \return true if the file exists else false. + */ + bool exists(const char* path) { + return vwd()->exists(path); + } + //---------------------------------------------------------------------------- + /** List the directory contents of the volume working directory. + * + * \param[in] pr Print stream for list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + */ + void ls(print_t* pr, uint8_t flags = 0) { + vwd()->ls(pr, flags); + } + //---------------------------------------------------------------------------- + /** List the directory contents of a directory. + * + * \param[in] pr Print stream for list. + * + * \param[in] path directory to list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + */ + void ls(print_t* pr, const char* path, uint8_t flags) { + FatFile dir; + dir.open(vwd(), path, O_READ); + dir.ls(pr, flags); + } + //---------------------------------------------------------------------------- + /** Make a subdirectory in the volume working directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the subdirectory. + * + * \param[in] pFlag Create missing parent directories if true. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool mkdir(const char* path, bool pFlag = true) { + FatFile sub; + return sub.mkdir(vwd(), path, pFlag); + } + //---------------------------------------------------------------------------- + /** Remove a file from the volume working directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the file. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool remove(const char* path) { + return FatFile::remove(vwd(), path); + } + //---------------------------------------------------------------------------- + /** Rename a file or subdirectory. + * + * \param[in] oldPath Path name to the file or subdirectory to be renamed. + * + * \param[in] newPath New path name of the file or subdirectory. + * + * The \a newPath object must not exist before the rename call. + * + * The file to be renamed must not be open. The directory entry may be + * moved and file system corruption could occur if the file is accessed by + * a file object that was opened before the rename() call. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool rename(const char *oldPath, const char *newPath) { + FatFile file; + if (!file.open(vwd(), oldPath, O_READ)) { + return false; + } + return file.rename(vwd(), newPath); + } + //---------------------------------------------------------------------------- + /** Remove a subdirectory from the volume's working directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the subdirectory. + * + * The subdirectory file will be removed only if it is empty. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool rmdir(const char* path) { + FatFile sub; + if (!sub.open(vwd(), path, O_READ)) { + return false; + } + return sub.rmdir(); + } + //---------------------------------------------------------------------------- + /** Truncate a file to a specified length. The current file position + * will be maintained if it is less than or equal to \a length otherwise + * it will be set to end of file. + * + * \param[in] path A path with a valid 8.3 DOS name for the file. + * \param[in] length The desired length for the file. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool truncate(const char* path, uint32_t length) { + FatFile file; + if (!file.open(vwd(), path, O_WRITE)) { + return false; + } + return file.truncate(length); + } + /** \return a pointer to the FatVolume object. */ + FatVolume* vol() { + return this; + } + /** \return a pointer to the volume working directory. */ + FatFile* vwd() { + return &m_vwd; + } + /** Wipe all data from the volume. You must reinitialize the volume before + * accessing it again. + * \param[in] pr print stream for status dots. + * \return true for success else false. + */ + bool wipe(print_t* pr = 0) { + vwd()->close(); + return FatVolume::wipe(pr); + } + + private: + FatFile m_vwd; +}; +#endif // FatFileSystem_h diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/FatLib.h b/Firmware_V2/src/libraries/SdFat/FatLib/FatLib.h new file mode 100644 index 0000000..425a92b --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/FatLib.h @@ -0,0 +1,33 @@ +/* FatLib Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#ifndef FatLib_h +#define FatLib_h +#include "ArduinoFiles.h" +#include "ArduinoStream.h" +#include "FatFileSystem.h" +#include "FatLibConfig.h" +#include "FatVolume.h" +#include "FatFile.h" +#include "StdioStream.h" +#include "fstream.h" +//------------------------------------------------------------------------------ +/** FatFileSystem version YYYYMMDD */ +#define FAT_LIB_VERSION 20150131 +#endif // FatLib_h diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/FatLibConfig.h b/Firmware_V2/src/libraries/SdFat/FatLib/FatLibConfig.h new file mode 100644 index 0000000..bc81bdd --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/FatLibConfig.h @@ -0,0 +1,143 @@ +/* FatLib Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +/** + * \file + * \brief configuration definitions + */ +#ifndef FatLibConfig_h +#define FatLibConfig_h +#include +// Allow this file to override defaults. +#include "../SdFatConfig.h" + +#ifdef __AVR__ +#include +#endif // __AVR__ +//------------------------------------------------------------------------------ +/** + * Set USE_LONG_FILE_NAMES nonzero to use long file names (LFN). + * Long File Name are limited to a maximum length of 255 characters. + * + * This implementation allows 7-bit characters in the range + * 0X20 to 0X7E. The following characters are not allowed: + * + * < (less than) + * > (greater than) + * : (colon) + * " (double quote) + * / (forward slash) + * \ (backslash) + * | (vertical bar or pipe) + * ? (question mark) + * * (asterisk) + * + */ +#ifndef USE_LONG_FILE_NAMES +#define USE_LONG_FILE_NAMES 1 +#endif // USE_LONG_FILE_NAMES +//------------------------------------------------------------------------------ +/** + * Set ARDUINO_FILE_USES_STREAM nonzero to use Stream as the base class + * for the Arduino File class. If ARDUINO_FILE_USES_STREAM is zero, Print + * will be used as the base class for the Arduino File class. + * + * You can save some flash if you do not use Stream input functions such as + * find(), findUntil(), readBytesUntil(), readString(), readStringUntil(), + * parseInt(), and parsefloat(). + */ +#ifndef ARDUINO_FILE_USES_STREAM +#define ARDUINO_FILE_USES_STREAM 1 +#endif // ARDUINO_FILE_USES_STREAM +//------------------------------------------------------------------------------ +/** + * Set USE_SEPARATE_FAT_CACHE non-zero to use a second 512 byte cache + * for FAT table entries. Improves performance for large writes that + * are not a multiple of 512 bytes. + */ +#ifndef USE_SEPARATE_FAT_CACHE +#ifdef __arm__ +#define USE_SEPARATE_FAT_CACHE 1 +#else // __arm__ +#define USE_SEPARATE_FAT_CACHE 0 +#endif // __arm__ +#endif // USE_SEPARATE_FAT_CACHE +//------------------------------------------------------------------------------ +/** + * Set USE_MULTI_BLOCK_IO non-zero to use multi-block SD read/write. + * + * Don't use mult-block read/write on small AVR boards. + */ +#ifndef USE_MULTI_BLOCK_IO +#if defined(RAMEND) && RAMEND < 3000 +#define USE_MULTI_BLOCK_IO 0 +#else // RAMEND +#define USE_MULTI_BLOCK_IO 1 +#endif // RAMEND +#endif // USE_MULTI_BLOCK_IO +//------------------------------------------------------------------------------ +/** + * Set DESTRUCTOR_CLOSES_FILE non-zero to close a file in its destructor. + * + * Causes use of lots of heap in ARM. + */ +#ifndef DESTRUCTOR_CLOSES_FILE +#define DESTRUCTOR_CLOSES_FILE 0 +#endif // DESTRUCTOR_CLOSES_FILE +//------------------------------------------------------------------------------ +/** + * Call flush for endl if ENDL_CALLS_FLUSH is non-zero + * + * The standard for iostreams is to call flush. This is very costly for + * SdFat. Each call to flush causes 2048 bytes of I/O to the SD. + * + * SdFat has a single 512 byte buffer for I/O so it must write the current + * data block to the SD, read the directory block from the SD, update the + * directory entry, write the directory block to the SD and read the data + * block back into the buffer. + * + * The SD flash memory controller is not designed for this many rewrites + * so performance may be reduced by more than a factor of 100. + * + * If ENDL_CALLS_FLUSH is zero, you must call flush and/or close to force + * all data to be written to the SD. + */ +#ifndef ENDL_CALLS_FLUSH +#define ENDL_CALLS_FLUSH 0 +#endif // ENDL_CALLS_FLUSH +//------------------------------------------------------------------------------ +/** + * Allow FAT12 volumes if FAT12_SUPPORT is non-zero. + * FAT12 has not been well tested. + */ +#ifndef FAT12_SUPPORT +#define FAT12_SUPPORT 0 +#endif // FAT12_SUPPORT +//------------------------------------------------------------------------------ +/** + * Enable Extra features for Arduino. + */ +#ifndef ENABLE_ARDUINO_FEATURES +#if defined(ARDUINO) || defined(PLATFORM_ID) || defined(DOXYGEN) +#define ENABLE_ARDUINO_FEATURES 1 +#else // #if defined(ARDUINO) || defined(DOXYGEN) +#define ENABLE_ARDUINO_FEATURES 0 +#endif // defined(ARDUINO) || defined(DOXYGEN) +#endif // ENABLE_ARDUINO_FEATURES +#endif // FatLibConfig_h diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/FatStructs.h b/Firmware_V2/src/libraries/SdFat/FatLib/FatStructs.h new file mode 100644 index 0000000..528db54 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/FatStructs.h @@ -0,0 +1,877 @@ +/* FatLib Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#ifndef FatStructs_h +#define FatStructs_h +/** + * \file + * \brief FAT file structures + */ +/* + * mostly from Microsoft document fatgen103.doc + * http://www.microsoft.com/whdc/system/platform/firmware/fatgen.mspx + */ +//------------------------------------------------------------------------------ +/** Value for byte 510 of boot block or MBR */ +uint8_t const BOOTSIG0 = 0X55; +/** Value for byte 511 of boot block or MBR */ +uint8_t const BOOTSIG1 = 0XAA; +/** Value for bootSignature field int FAT/FAT32 boot sector */ +uint8_t const EXTENDED_BOOT_SIG = 0X29; +//------------------------------------------------------------------------------ +/** + * \struct partitionTable + * \brief MBR partition table entry + * + * A partition table entry for a MBR formatted storage device. + * The MBR partition table has four entries. + */ +struct partitionTable { + /** + * Boot Indicator . Indicates whether the volume is the active + * partition. Legal values include: 0X00. Do not use for booting. + * 0X80 Active partition. + */ + uint8_t boot; + /** + * Head part of Cylinder-head-sector address of the first block in + * the partition. Legal values are 0-255. Only used in old PC BIOS. + */ + uint8_t beginHead; + /** + * Sector part of Cylinder-head-sector address of the first block in + * the partition. Legal values are 1-63. Only used in old PC BIOS. + */ + unsigned beginSector : 6; + /** High bits cylinder for first block in partition. */ + unsigned beginCylinderHigh : 2; + /** + * Combine beginCylinderLow with beginCylinderHigh. Legal values + * are 0-1023. Only used in old PC BIOS. + */ + uint8_t beginCylinderLow; + /** + * Partition type. See defines that begin with PART_TYPE_ for + * some Microsoft partition types. + */ + uint8_t type; + /** + * head part of cylinder-head-sector address of the last sector in the + * partition. Legal values are 0-255. Only used in old PC BIOS. + */ + uint8_t endHead; + /** + * Sector part of cylinder-head-sector address of the last sector in + * the partition. Legal values are 1-63. Only used in old PC BIOS. + */ + unsigned endSector : 6; + /** High bits of end cylinder */ + unsigned endCylinderHigh : 2; + /** + * Combine endCylinderLow with endCylinderHigh. Legal values + * are 0-1023. Only used in old PC BIOS. + */ + uint8_t endCylinderLow; + /** Logical block address of the first block in the partition. */ + uint32_t firstSector; + /** Length of the partition, in blocks. */ + uint32_t totalSectors; +} __attribute__((packed)); +/** Type name for partitionTable */ +typedef struct partitionTable part_t; +//------------------------------------------------------------------------------ +/** + * \struct masterBootRecord + * + * \brief Master Boot Record + * + * The first block of a storage device that is formatted with a MBR. + */ +struct masterBootRecord { + /** Code Area for master boot program. */ + uint8_t codeArea[440]; + /** Optional Windows NT disk signature. May contain boot code. */ + uint32_t diskSignature; + /** Usually zero but may be more boot code. */ + uint16_t usuallyZero; + /** Partition tables. */ + part_t part[4]; + /** First MBR signature byte. Must be 0X55 */ + uint8_t mbrSig0; + /** Second MBR signature byte. Must be 0XAA */ + uint8_t mbrSig1; +} __attribute__((packed)); +/** Type name for masterBootRecord */ +typedef struct masterBootRecord mbr_t; +//------------------------------------------------------------------------------ +/** + * \struct biosParmBlock + * + * \brief BIOS parameter block + * + * The BIOS parameter block describes the physical layout of a FAT volume. + */ +struct biosParmBlock { + /** + * Count of bytes per sector. This value may take on only the + * following values: 512, 1024, 2048 or 4096 + */ + uint16_t bytesPerSector; + /** + * Number of sectors per allocation unit. This value must be a + * power of 2 that is greater than 0. The legal values are + * 1, 2, 4, 8, 16, 32, 64, and 128. + */ + uint8_t sectorsPerCluster; + /** + * Number of sectors before the first FAT. + * This value must not be zero. + */ + uint16_t reservedSectorCount; + /** The count of FAT data structures on the volume. This field should + * always contain the value 2 for any FAT volume of any type. + */ + uint8_t fatCount; + /** + * For FAT12 and FAT16 volumes, this field contains the count of + * 32-byte directory entries in the root directory. For FAT32 volumes, + * this field must be set to 0. For FAT12 and FAT16 volumes, this + * value should always specify a count that when multiplied by 32 + * results in a multiple of bytesPerSector. FAT16 volumes should + * use the value 512. + */ + uint16_t rootDirEntryCount; + /** + * This field is the old 16-bit total count of sectors on the volume. + * This count includes the count of all sectors in all four regions + * of the volume. This field can be 0; if it is 0, then totalSectors32 + * must be nonzero. For FAT32 volumes, this field must be 0. For + * FAT12 and FAT16 volumes, this field contains the sector count, and + * totalSectors32 is 0 if the total sector count fits + * (is less than 0x10000). + */ + uint16_t totalSectors16; + /** + * This dates back to the old MS-DOS 1.x media determination and is + * no longer usually used for anything. 0xF8 is the standard value + * for fixed (nonremovable) media. For removable media, 0xF0 is + * frequently used. Legal values are 0xF0 or 0xF8-0xFF. + */ + uint8_t mediaType; + /** + * Count of sectors occupied by one FAT on FAT12/FAT16 volumes. + * On FAT32 volumes this field must be 0, and sectorsPerFat32 + * contains the FAT size count. + */ + uint16_t sectorsPerFat16; + /** Sectors per track for interrupt 0x13. Not used otherwise. */ + uint16_t sectorsPerTrtack; + /** Number of heads for interrupt 0x13. Not used otherwise. */ + uint16_t headCount; + /** + * Count of hidden sectors preceding the partition that contains this + * FAT volume. This field is generally only relevant for media + * visible on interrupt 0x13. + */ + uint32_t hidddenSectors; + /** + * This field is the new 32-bit total count of sectors on the volume. + * This count includes the count of all sectors in all four regions + * of the volume. This field can be 0; if it is 0, then + * totalSectors16 must be nonzero. + */ + uint32_t totalSectors32; + /** + * Count of sectors occupied by one FAT on FAT32 volumes. + */ + uint32_t sectorsPerFat32; + /** + * This field is only defined for FAT32 media and does not exist on + * FAT12 and FAT16 media. + * Bits 0-3 -- Zero-based number of active FAT. + * Only valid if mirroring is disabled. + * Bits 4-6 -- Reserved. + * Bit 7 -- 0 means the FAT is mirrored at runtime into all FATs. + * -- 1 means only one FAT is active; it is the one referenced in bits 0-3. + * Bits 8-15 -- Reserved. + */ + uint16_t fat32Flags; + /** + * FAT32 version. High byte is major revision number. + * Low byte is minor revision number. Only 0.0 define. + */ + uint16_t fat32Version; + /** + * Cluster number of the first cluster of the root directory for FAT32. + * This usually 2 but not required to be 2. + */ + uint32_t fat32RootCluster; + /** + * Sector number of FSINFO structure in the reserved area of the + * FAT32 volume. Usually 1. + */ + uint16_t fat32FSInfo; + /** + * If nonzero, indicates the sector number in the reserved area + * of the volume of a copy of the boot record. Usually 6. + * No value other than 6 is recommended. + */ + uint16_t fat32BackBootBlock; + /** + * Reserved for future expansion. Code that formats FAT32 volumes + * should always set all of the bytes of this field to 0. + */ + uint8_t fat32Reserved[12]; +} __attribute__((packed)); +/** Type name for biosParmBlock */ +typedef struct biosParmBlock bpb_t; +//------------------------------------------------------------------------------ +/** + * \struct fat_boot + * + * \brief Boot sector for a FAT12/FAT16 volume. + * + */ +struct fat_boot { + /** + * The first three bytes of the boot sector must be valid, + * executable x 86-based CPU instructions. This includes a + * jump instruction that skips the next non-executable bytes. + */ + uint8_t jump[3]; + /** + * This is typically a string of characters that identifies + * the operating system that formatted the volume. + */ + char oemId[8]; + /** + * The size of a hardware sector. Valid decimal values for this + * field are 512, 1024, 2048, and 4096. For most disks used in + * the United States, the value of this field is 512. + */ + uint16_t bytesPerSector; + /** + * Number of sectors per allocation unit. This value must be a + * power of 2 that is greater than 0. The legal values are + * 1, 2, 4, 8, 16, 32, 64, and 128. 128 should be avoided. + */ + uint8_t sectorsPerCluster; + /** + * The number of sectors preceding the start of the first FAT, + * including the boot sector. The value of this field is always 1. + */ + uint16_t reservedSectorCount; + /** + * The number of copies of the FAT on the volume. + * The value of this field is always 2. + */ + uint8_t fatCount; + /** + * For FAT12 and FAT16 volumes, this field contains the count of + * 32-byte directory entries in the root directory. For FAT32 volumes, + * this field must be set to 0. For FAT12 and FAT16 volumes, this + * value should always specify a count that when multiplied by 32 + * results in a multiple of bytesPerSector. FAT16 volumes should + * use the value 512. + */ + uint16_t rootDirEntryCount; + /** + * This field is the old 16-bit total count of sectors on the volume. + * This count includes the count of all sectors in all four regions + * of the volume. This field can be 0; if it is 0, then totalSectors32 + * must be non-zero. For FAT32 volumes, this field must be 0. For + * FAT12 and FAT16 volumes, this field contains the sector count, and + * totalSectors32 is 0 if the total sector count fits + * (is less than 0x10000). + */ + uint16_t totalSectors16; + /** + * This dates back to the old MS-DOS 1.x media determination and is + * no longer usually used for anything. 0xF8 is the standard value + * for fixed (non-removable) media. For removable media, 0xF0 is + * frequently used. Legal values are 0xF0 or 0xF8-0xFF. + */ + uint8_t mediaType; + /** + * Count of sectors occupied by one FAT on FAT12/FAT16 volumes. + * On FAT32 volumes this field must be 0, and sectorsPerFat32 + * contains the FAT size count. + */ + uint16_t sectorsPerFat16; + /** Sectors per track for interrupt 0x13. Not used otherwise. */ + uint16_t sectorsPerTrack; + /** Number of heads for interrupt 0x13. Not used otherwise. */ + uint16_t headCount; + /** + * Count of hidden sectors preceding the partition that contains this + * FAT volume. This field is generally only relevant for media + * visible on interrupt 0x13. + */ + uint32_t hidddenSectors; + /** + * This field is the new 32-bit total count of sectors on the volume. + * This count includes the count of all sectors in all four regions + * of the volume. This field can be 0; if it is 0, then + * totalSectors16 must be non-zero. + */ + uint32_t totalSectors32; + /** + * Related to the BIOS physical drive number. Floppy drives are + * identified as 0x00 and physical hard disks are identified as + * 0x80, regardless of the number of physical disk drives. + * Typically, this value is set prior to issuing an INT 13h BIOS + * call to specify the device to access. The value is only + * relevant if the device is a boot device. + */ + uint8_t driveNumber; + /** used by Windows NT - should be zero for FAT */ + uint8_t reserved1; + /** 0X29 if next three fields are valid */ + uint8_t bootSignature; + /** + * A random serial number created when formatting a disk, + * which helps to distinguish between disks. + * Usually generated by combining date and time. + */ + uint32_t volumeSerialNumber; + /** + * A field once used to store the volume label. The volume label + * is now stored as a special file in the root directory. + */ + char volumeLabel[11]; + /** + * A field with a value of either FAT, FAT12 or FAT16, + * depending on the disk format. + */ + char fileSystemType[8]; + /** X86 boot code */ + uint8_t bootCode[448]; + /** must be 0X55 */ + uint8_t bootSectorSig0; + /** must be 0XAA */ + uint8_t bootSectorSig1; +} __attribute__((packed)); +/** Type name for FAT Boot Sector */ +typedef struct fat_boot fat_boot_t; +//------------------------------------------------------------------------------ +/** + * \struct fat32_boot + * + * \brief Boot sector for a FAT32 volume. + * + */ +struct fat32_boot { + /** + * The first three bytes of the boot sector must be valid, + * executable x 86-based CPU instructions. This includes a + * jump instruction that skips the next non-executable bytes. + */ + uint8_t jump[3]; + /** + * This is typically a string of characters that identifies + * the operating system that formatted the volume. + */ + char oemId[8]; + /** + * The size of a hardware sector. Valid decimal values for this + * field are 512, 1024, 2048, and 4096. For most disks used in + * the United States, the value of this field is 512. + */ + uint16_t bytesPerSector; + /** + * Number of sectors per allocation unit. This value must be a + * power of 2 that is greater than 0. The legal values are + * 1, 2, 4, 8, 16, 32, 64, and 128. 128 should be avoided. + */ + uint8_t sectorsPerCluster; + /** + * The number of sectors preceding the start of the first FAT, + * including the boot sector. Must not be zero + */ + uint16_t reservedSectorCount; + /** + * The number of copies of the FAT on the volume. + * The value of this field is always 2. + */ + uint8_t fatCount; + /** + * FAT12/FAT16 only. For FAT32 volumes, this field must be set to 0. + */ + uint16_t rootDirEntryCount; + /** + * For FAT32 volumes, this field must be 0. + */ + uint16_t totalSectors16; + /** + * This dates back to the old MS-DOS 1.x media determination and is + * no longer usually used for anything. 0xF8 is the standard value + * for fixed (non-removable) media. For removable media, 0xF0 is + * frequently used. Legal values are 0xF0 or 0xF8-0xFF. + */ + uint8_t mediaType; + /** + * On FAT32 volumes this field must be 0, and sectorsPerFat32 + * contains the FAT size count. + */ + uint16_t sectorsPerFat16; + /** Sectors per track for interrupt 0x13. Not used otherwise. */ + uint16_t sectorsPerTrack; + /** Number of heads for interrupt 0x13. Not used otherwise. */ + uint16_t headCount; + /** + * Count of hidden sectors preceding the partition that contains this + * FAT volume. This field is generally only relevant for media + * visible on interrupt 0x13. + */ + uint32_t hidddenSectors; + /** + * Contains the total number of sectors in the FAT32 volume. + */ + uint32_t totalSectors32; + /** + * Count of sectors occupied by one FAT on FAT32 volumes. + */ + uint32_t sectorsPerFat32; + /** + * This field is only defined for FAT32 media and does not exist on + * FAT12 and FAT16 media. + * Bits 0-3 -- Zero-based number of active FAT. + * Only valid if mirroring is disabled. + * Bits 4-6 -- Reserved. + * Bit 7 -- 0 means the FAT is mirrored at runtime into all FATs. + * -- 1 means only one FAT is active; it is the one referenced + * in bits 0-3. + * Bits 8-15 -- Reserved. + */ + uint16_t fat32Flags; + /** + * FAT32 version. High byte is major revision number. + * Low byte is minor revision number. Only 0.0 define. + */ + uint16_t fat32Version; + /** + * Cluster number of the first cluster of the root directory for FAT32. + * This usually 2 but not required to be 2. + */ + uint32_t fat32RootCluster; + /** + * Sector number of FSINFO structure in the reserved area of the + * FAT32 volume. Usually 1. + */ + uint16_t fat32FSInfo; + /** + * If non-zero, indicates the sector number in the reserved area + * of the volume of a copy of the boot record. Usually 6. + * No value other than 6 is recommended. + */ + uint16_t fat32BackBootBlock; + /** + * Reserved for future expansion. Code that formats FAT32 volumes + * should always set all of the bytes of this field to 0. + */ + uint8_t fat32Reserved[12]; + /** + * Related to the BIOS physical drive number. Floppy drives are + * identified as 0x00 and physical hard disks are identified as + * 0x80, regardless of the number of physical disk drives. + * Typically, this value is set prior to issuing an INT 13h BIOS + * call to specify the device to access. The value is only + * relevant if the device is a boot device. + */ + uint8_t driveNumber; + /** used by Windows NT - should be zero for FAT */ + uint8_t reserved1; + /** 0X29 if next three fields are valid */ + uint8_t bootSignature; + /** + * A random serial number created when formatting a disk, + * which helps to distinguish between disks. + * Usually generated by combining date and time. + */ + uint32_t volumeSerialNumber; + /** + * A field once used to store the volume label. The volume label + * is now stored as a special file in the root directory. + */ + char volumeLabel[11]; + /** + * A text field with a value of FAT32. + */ + char fileSystemType[8]; + /** X86 boot code */ + uint8_t bootCode[420]; + /** must be 0X55 */ + uint8_t bootSectorSig0; + /** must be 0XAA */ + uint8_t bootSectorSig1; +} __attribute__((packed)); +/** Type name for FAT32 Boot Sector */ +typedef struct fat32_boot fat32_boot_t; +//------------------------------------------------------------------------------ +/** Lead signature for a FSINFO sector */ +uint32_t const FSINFO_LEAD_SIG = 0x41615252; +/** Struct signature for a FSINFO sector */ +uint32_t const FSINFO_STRUCT_SIG = 0x61417272; +/** + * \struct fat32_fsinfo + * + * \brief FSINFO sector for a FAT32 volume. + * + */ +struct fat32_fsinfo { + /** must be 0X52, 0X52, 0X61, 0X41 */ + uint32_t leadSignature; + /** must be zero */ + uint8_t reserved1[480]; + /** must be 0X72, 0X72, 0X41, 0X61 */ + uint32_t structSignature; + /** + * Contains the last known free cluster count on the volume. + * If the value is 0xFFFFFFFF, then the free count is unknown + * and must be computed. Any other value can be used, but is + * not necessarily correct. It should be range checked at least + * to make sure it is <= volume cluster count. + */ + uint32_t freeCount; + /** + * This is a hint for the FAT driver. It indicates the cluster + * number at which the driver should start looking for free clusters. + * If the value is 0xFFFFFFFF, then there is no hint and the driver + * should start looking at cluster 2. + */ + uint32_t nextFree; + /** must be zero */ + uint8_t reserved2[12]; + /** must be 0X00, 0X00, 0X55, 0XAA */ + uint8_t tailSignature[4]; +} __attribute__((packed)); +/** Type name for FAT32 FSINFO Sector */ +typedef struct fat32_fsinfo fat32_fsinfo_t; +//------------------------------------------------------------------------------ +// End Of Chain values for FAT entries +/** FAT12 end of chain value used by Microsoft. */ +uint16_t const FAT12EOC = 0XFFF; +/** Minimum value for FAT12 EOC. Use to test for EOC. */ +uint16_t const FAT12EOC_MIN = 0XFF8; +/** FAT16 end of chain value used by Microsoft. */ +uint16_t const FAT16EOC = 0XFFFF; +/** Minimum value for FAT16 EOC. Use to test for EOC. */ +uint16_t const FAT16EOC_MIN = 0XFFF8; +/** FAT32 end of chain value used by Microsoft. */ +uint32_t const FAT32EOC = 0X0FFFFFFF; +/** Minimum value for FAT32 EOC. Use to test for EOC. */ +uint32_t const FAT32EOC_MIN = 0X0FFFFFF8; +/** Mask a for FAT32 entry. Entries are 28 bits. */ +uint32_t const FAT32MASK = 0X0FFFFFFF; +//------------------------------------------------------------------------------ +/** + * \struct directoryEntry + * \brief FAT short directory entry + * + * Short means short 8.3 name, not the entry size. + * + * Date Format. A FAT directory entry date stamp is a 16-bit field that is + * basically a date relative to the MS-DOS epoch of 01/01/1980. Here is the + * format (bit 0 is the LSB of the 16-bit word, bit 15 is the MSB of the + * 16-bit word): + * + * Bits 9-15: Count of years from 1980, valid value range 0-127 + * inclusive (1980-2107). + * + * Bits 5-8: Month of year, 1 = January, valid value range 1-12 inclusive. + * + * Bits 0-4: Day of month, valid value range 1-31 inclusive. + * + * Time Format. A FAT directory entry time stamp is a 16-bit field that has + * a granularity of 2 seconds. Here is the format (bit 0 is the LSB of the + * 16-bit word, bit 15 is the MSB of the 16-bit word). + * + * Bits 11-15: Hours, valid value range 0-23 inclusive. + * + * Bits 5-10: Minutes, valid value range 0-59 inclusive. + * + * Bits 0-4: 2-second count, valid value range 0-29 inclusive (0 - 58 seconds). + * + * The valid time range is from Midnight 00:00:00 to 23:59:58. + */ +struct directoryEntry { + /** Short 8.3 name. + * + * The first eight bytes contain the file name with blank fill. + * The last three bytes contain the file extension with blank fill. + */ + uint8_t name[11]; + /** Entry attributes. + * + * The upper two bits of the attribute byte are reserved and should + * always be set to 0 when a file is created and never modified or + * looked at after that. See defines that begin with DIR_ATT_. + */ + uint8_t attributes; + /** + * Reserved for use by Windows NT. Set value to 0 when a file is + * created and never modify or look at it after that. + */ + uint8_t reservedNT; + /** + * The granularity of the seconds part of creationTime is 2 seconds + * so this field is a count of tenths of a second and its valid + * value range is 0-199 inclusive. (WHG note - seems to be hundredths) + */ + uint8_t creationTimeTenths; + /** Time file was created. */ + uint16_t creationTime; + /** Date file was created. */ + uint16_t creationDate; + /** + * Last access date. Note that there is no last access time, only + * a date. This is the date of last read or write. In the case of + * a write, this should be set to the same date as lastWriteDate. + */ + uint16_t lastAccessDate; + /** + * High word of this entry's first cluster number (always 0 for a + * FAT12 or FAT16 volume). + */ + uint16_t firstClusterHigh; + /** Time of last write. File creation is considered a write. */ + uint16_t lastWriteTime; + /** Date of last write. File creation is considered a write. */ + uint16_t lastWriteDate; + /** Low word of this entry's first cluster number. */ + uint16_t firstClusterLow; + /** 32-bit unsigned holding this file's size in bytes. */ + uint32_t fileSize; +} __attribute__((packed)); +/** Type name for directoryEntry */ +typedef struct directoryEntry dir_t; +//------------------------------------------------------------------------------ +// Definitions for directory entries +// +/** escape for name[0] = 0XE5 */ +uint8_t const DIR_NAME_0XE5 = 0X05; +/** name[0] value for entry that is free after being "deleted" */ +uint8_t const DIR_NAME_DELETED = 0XE5; +/** name[0] value for entry that is free and no allocated entries follow */ +uint8_t const DIR_NAME_FREE = 0X00; +/** file is read-only */ +uint8_t const DIR_ATT_READ_ONLY = 0X01; +/** File should e hidden in directory listings */ +uint8_t const DIR_ATT_HIDDEN = 0X02; +/** Entry is for a system file */ +uint8_t const DIR_ATT_SYSTEM = 0X04; +/** Directory entry contains the volume label */ +uint8_t const DIR_ATT_VOLUME_ID = 0X08; +/** Entry is for a directory */ +uint8_t const DIR_ATT_DIRECTORY = 0X10; +/** Old DOS archive bit for backup support */ +uint8_t const DIR_ATT_ARCHIVE = 0X20; +/** Test value for long name entry. Test is + (d->attributes & DIR_ATT_LONG_NAME_MASK) == DIR_ATT_LONG_NAME. */ +uint8_t const DIR_ATT_LONG_NAME = 0X0F; +/** Test mask for long name entry */ +uint8_t const DIR_ATT_LONG_NAME_MASK = 0X3F; +/** defined attribute bits */ +uint8_t const DIR_ATT_DEFINED_BITS = 0X3F; + +/** Mask for file/subdirectory tests */ +uint8_t const DIR_ATT_FILE_TYPE_MASK = (DIR_ATT_VOLUME_ID | DIR_ATT_DIRECTORY); + +/** Filename base-name is all lower case */ +const uint8_t DIR_NT_LC_BASE = 0X08; +/** Filename extension is all lower case.*/ +const uint8_t DIR_NT_LC_EXT = 0X10; + + +/** Directory entry is for a file + * \param[in] dir Pointer to a directory entry. + * + * \return true if the entry is for a normal file else false. + */ +static inline uint8_t DIR_IS_FILE(const dir_t* dir) { + return (dir->attributes & DIR_ATT_FILE_TYPE_MASK) == 0; +} +/** Directory entry is for a file or subdirectory + * \param[in] dir Pointer to a directory entry. + * + * \return true if the entry is for a normal file or subdirectory else false. + */ +static inline uint8_t DIR_IS_FILE_OR_SUBDIR(const dir_t* dir) { + return (dir->attributes & DIR_ATT_VOLUME_ID) == 0; +} +/** Directory entry is part of a long name + * \param[in] dir Pointer to a directory entry. + * + * \return true if the entry is for part of a long name else false. + */ +static inline uint8_t DIR_IS_LONG_NAME(const dir_t* dir) { + return dir->attributes == DIR_ATT_LONG_NAME; +} +/** Directory entry is hidden + * \param[in] dir Pointer to a directory entry. + * + * \return true if the entry is hidden else false. + */ +static inline uint8_t DIR_IS_HIDDEN(const dir_t* dir) { + return dir->attributes & DIR_ATT_HIDDEN; +} +/** Directory entry is for a subdirectory + * \param[in] dir Pointer to a directory entry. + * + * \return true if the entry is for a subdirectory else false. + */ +static inline uint8_t DIR_IS_SUBDIR(const dir_t* dir) { + return (dir->attributes & DIR_ATT_FILE_TYPE_MASK) == DIR_ATT_DIRECTORY; +} +/** Directory entry is system type + * \param[in] dir Pointer to a directory entry. + * + * \return true if the entry is system else false. + */ +static inline uint8_t DIR_IS_SYSTEM(const dir_t* dir) { + return dir->attributes & DIR_ATT_SYSTEM; +} +/** date field for FAT directory entry + * \param[in] year [1980,2107] + * \param[in] month [1,12] + * \param[in] day [1,31] + * + * \return Packed date for dir_t entry. + */ +static inline uint16_t FAT_DATE(uint16_t year, uint8_t month, uint8_t day) { + return (year - 1980) << 9 | month << 5 | day; +} +/** year part of FAT directory date field + * \param[in] fatDate Date in packed dir format. + * + * \return Extracted year [1980,2107] + */ +static inline uint16_t FAT_YEAR(uint16_t fatDate) { + return 1980 + (fatDate >> 9); +} +/** month part of FAT directory date field + * \param[in] fatDate Date in packed dir format. + * + * \return Extracted month [1,12] + */ +static inline uint8_t FAT_MONTH(uint16_t fatDate) { + return (fatDate >> 5) & 0XF; +} +/** day part of FAT directory date field + * \param[in] fatDate Date in packed dir format. + * + * \return Extracted day [1,31] + */ +static inline uint8_t FAT_DAY(uint16_t fatDate) { + return fatDate & 0X1F; +} +/** time field for FAT directory entry + * \param[in] hour [0,23] + * \param[in] minute [0,59] + * \param[in] second [0,59] + * + * \return Packed time for dir_t entry. + */ +static inline uint16_t FAT_TIME(uint8_t hour, uint8_t minute, uint8_t second) { + return hour << 11 | minute << 5 | second >> 1; +} +/** hour part of FAT directory time field + * \param[in] fatTime Time in packed dir format. + * + * \return Extracted hour [0,23] + */ +static inline uint8_t FAT_HOUR(uint16_t fatTime) { + return fatTime >> 11; +} +/** minute part of FAT directory time field + * \param[in] fatTime Time in packed dir format. + * + * \return Extracted minute [0,59] + */ +static inline uint8_t FAT_MINUTE(uint16_t fatTime) { + return (fatTime >> 5) & 0X3F; +} +/** second part of FAT directory time field + * Note second/2 is stored in packed time. + * + * \param[in] fatTime Time in packed dir format. + * + * \return Extracted second [0,58] + */ +static inline uint8_t FAT_SECOND(uint16_t fatTime) { + return 2*(fatTime & 0X1F); +} +/** Default date for file timestamps is 1 Jan 2000 */ +uint16_t const FAT_DEFAULT_DATE = ((2000 - 1980) << 9) | (1 << 5) | 1; +/** Default time for file timestamp is 1 am */ +uint16_t const FAT_DEFAULT_TIME = (1 << 11); +//------------------------------------------------------------------------------ +/** Dimension of first name field in long directory entry */ +const uint8_t LDIR_NAME1_DIM = 5; +/** Dimension of first name field in long directory entry */ +const uint8_t LDIR_NAME2_DIM = 6; +/** Dimension of first name field in long directory entry */ +const uint8_t LDIR_NAME3_DIM = 2; +/** + * \struct longDirectoryEntry + * \brief FAT long directory entry + */ +struct longDirectoryEntry { + /** + * The order of this entry in the sequence of long dir entries + * associated with the short dir entry at the end of the long dir set. + * + * If masked with 0X40 (LAST_LONG_ENTRY), this indicates the + * entry is the last long dir entry in a set of long dir entries. + * All valid sets of long dir entries must begin with an entry having + * this mask. + */ + uint8_t ord; + /** Characters 1-5 of the long-name sub-component in this entry. */ + uint16_t name1[LDIR_NAME1_DIM]; + /** Attributes - must be ATTR_LONG_NAME */ + uint8_t attr; + /** + * If zero, indicates a directory entry that is a sub-component of a + * long name. NOTE: Other values reserved for future extensions. + * + * Non-zero implies other directory entry types. + */ + uint8_t type; + /** + * Checksum of name in the short dir entry at the end of the + * long dir set. + */ + uint8_t chksum; + /** Characters 6-11 of the long-name sub-component in this entry. */ + uint16_t name2[LDIR_NAME2_DIM]; + /** Must be ZERO. This is an artifact of the FAT "first cluster" */ + uint16_t mustBeZero; + /** Characters 12 and 13 of the long-name sub-component in this entry. */ + uint16_t name3[LDIR_NAME3_DIM]; +} __attribute__((packed)); +/** Type name for longDirectoryEntry */ +typedef struct longDirectoryEntry ldir_t; +/** + * Ord mast that indicates the entry is the last long dir entry in a + * set of long dir entries. All valid sets of long dir entries must + * begin with an entry having this mask. + */ +const uint8_t LDIR_ORD_LAST_LONG_ENTRY = 0X40; +#endif // FatStructs_h diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/FatVolume.cpp b/Firmware_V2/src/libraries/SdFat/FatLib/FatVolume.cpp new file mode 100644 index 0000000..1f02819 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/FatVolume.cpp @@ -0,0 +1,601 @@ +/* FatLib Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#include +#include "FatVolume.h" +//------------------------------------------------------------------------------ +cache_t* FatCache::read(uint32_t lbn, uint8_t option) { + if (m_lbn != lbn) { + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + if (!(option & CACHE_OPTION_NO_READ)) { + if (!m_vol->readBlock(lbn, m_block.data)) { + DBG_FAIL_MACRO; + goto fail; + } + } + m_status = 0; + m_lbn = lbn; + } + m_status |= option & CACHE_STATUS_MASK; + return &m_block; + +fail: + return 0; +} +//------------------------------------------------------------------------------ +bool FatCache::sync() { + if (m_status & CACHE_STATUS_DIRTY) { + if (!m_vol->writeBlock(m_lbn, m_block.data)) { + DBG_FAIL_MACRO; + goto fail; + } + // mirror second FAT + if (m_status & CACHE_STATUS_MIRROR_FAT) { + uint32_t lbn = m_lbn + m_vol->blocksPerFat(); + if (!m_vol->writeBlock(lbn, m_block.data)) { + DBG_FAIL_MACRO; + goto fail; + } + } + m_status &= ~CACHE_STATUS_DIRTY; + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatVolume::allocateCluster(uint32_t current, uint32_t* next) { + uint32_t find = current ? current : m_allocSearchStart; + uint32_t start = find; + while (1) { + find++; + // If at end of FAT go to beginning of FAT. + if (find > m_lastCluster) { + find = 2; + } + uint32_t f; + int8_t fg = fatGet(find, &f); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (fg && f == 0) { + break; + } + if (find == start) { + // Can't find space checked all clusters. + DBG_FAIL_MACRO; + goto fail; + } + } + // mark end of chain + if (!fatPutEOC(find)) { + DBG_FAIL_MACRO; + goto fail; + } + if (current) { + // link clusters + if (!fatPut(current, find)) { + DBG_FAIL_MACRO; + goto fail; + } + } else { + // Remember place for search start. + m_allocSearchStart = find; + } + updateFreeClusterCount(-1); + *next = find; + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +// find a contiguous group of clusters +bool FatVolume::allocContiguous(uint32_t count, uint32_t* firstCluster) { + // flag to save place to start next search + bool setStart = true; + // start of group + uint32_t bgnCluster; + // end of group + uint32_t endCluster; + // Start at cluster after last allocated cluster. + uint32_t startCluster = m_allocSearchStart; + endCluster = bgnCluster = startCluster + 1; + + // search the FAT for free clusters + while (1) { + // If past end - start from beginning of FAT. + if (endCluster > m_lastCluster) { + bgnCluster = endCluster = 2; + } + uint32_t f; + int8_t fg = fatGet(endCluster, &f); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (f || fg == 0) { + // cluster in use try next cluster as bgnCluster + bgnCluster = endCluster + 1; + + // don't update search start if unallocated clusters before endCluster. + if (bgnCluster != endCluster) { + setStart = false; + } + } else if ((endCluster - bgnCluster + 1) == count) { + // done - found space + break; + } + // Can't find space if all clusters checked. + if (startCluster == endCluster) { + DBG_FAIL_MACRO; + goto fail; + } + endCluster++; + } + // remember possible next free cluster + if (setStart) { + m_allocSearchStart = endCluster + 1; + } + + // mark end of chain + if (!fatPutEOC(endCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + // link clusters + while (endCluster > bgnCluster) { + if (!fatPut(endCluster - 1, endCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + endCluster--; + } + // Maintain count of free clusters. + updateFreeClusterCount(-count); + + // return first cluster number to caller + *firstCluster = bgnCluster; + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +uint32_t FatVolume::clusterStartBlock(uint32_t cluster) const { + return m_dataStartBlock + ((cluster - 2) << m_clusterSizeShift); +} +//------------------------------------------------------------------------------ +// Fetch a FAT entry - return -1 error, 0 EOC, else 1. +int8_t FatVolume::fatGet(uint32_t cluster, uint32_t* value) { + uint32_t lba; + uint32_t next; + cache_t* pc; + + // error if reserved cluster of beyond FAT + DBG_HALT_IF(cluster < 2 || cluster > m_lastCluster); + + if (m_fatType == 32) { + lba = m_fatStartBlock + (cluster >> 7); + pc = cacheFetchFat(lba, FatCache::CACHE_FOR_READ); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + next = pc->fat32[cluster & 0X7F] & FAT32MASK; + goto done; + } + + if (m_fatType == 16) { + lba = m_fatStartBlock + ((cluster >> 8) & 0XFF); + pc = cacheFetchFat(lba, FatCache::CACHE_FOR_READ); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + next = pc->fat16[cluster & 0XFF]; + goto done; + } + if (FAT12_SUPPORT && m_fatType == 12) { + uint16_t index = cluster; + index += index >> 1; + lba = m_fatStartBlock + (index >> 9); + pc = cacheFetchFat(lba, FatCache::CACHE_FOR_READ); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + index &= 0X1FF; + uint16_t tmp = pc->data[index]; + index++; + if (index == 512) { + pc = cacheFetchFat(lba + 1, FatCache::CACHE_FOR_READ); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + index = 0; + } + tmp |= pc->data[index] << 8; + next = cluster & 1 ? tmp >> 4 : tmp & 0XFFF; + goto done; + } else { + DBG_FAIL_MACRO; + goto fail; + } +done: + if (isEOC(next)) { + return 0; + } + *value = next; + return 1; + +fail: + return -1; +} +//------------------------------------------------------------------------------ +// Store a FAT entry +bool FatVolume::fatPut(uint32_t cluster, uint32_t value) { + uint32_t lba; + cache_t* pc; + + // error if reserved cluster of beyond FAT + DBG_HALT_IF(cluster < 2 || cluster > m_lastCluster); + + if (m_fatType == 32) { + lba = m_fatStartBlock + (cluster >> 7); + pc = cacheFetchFat(lba, FatCache::CACHE_FOR_WRITE); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + pc->fat32[cluster & 0X7F] = value; + return true; + } + + if (m_fatType == 16) { + lba = m_fatStartBlock + ((cluster >> 8) & 0XFF); + pc = cacheFetchFat(lba, FatCache::CACHE_FOR_WRITE); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + pc->fat16[cluster & 0XFF] = value; + return true; + } + + if (FAT12_SUPPORT && m_fatType == 12) { + uint16_t index = cluster; + index += index >> 1; + lba = m_fatStartBlock + (index >> 9); + pc = cacheFetchFat(lba, FatCache::CACHE_FOR_WRITE); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + index &= 0X1FF; + uint8_t tmp = value; + if (cluster & 1) { + tmp = (pc->data[index] & 0XF) | tmp << 4; + } + pc->data[index] = tmp; + + index++; + if (index == 512) { + lba++; + index = 0; + pc = cacheFetchFat(lba, FatCache::CACHE_FOR_WRITE); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + } + tmp = value >> 4; + if (!(cluster & 1)) { + tmp = ((pc->data[index] & 0XF0)) | tmp >> 4; + } + pc->data[index] = tmp; + return true; + } else { + DBG_FAIL_MACRO; + goto fail; + } + +fail: + return false; +} +//------------------------------------------------------------------------------ +// free a cluster chain +bool FatVolume::freeChain(uint32_t cluster) { + uint32_t next; + int8_t fg; + do { + fg = fatGet(cluster, &next); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + // free cluster + if (!fatPut(cluster, 0)) { + DBG_FAIL_MACRO; + goto fail; + } + // Add one to count of free clusters. + updateFreeClusterCount(1); + + if (cluster < m_allocSearchStart) { + m_allocSearchStart = cluster; + } + cluster = next; + } while (fg); + + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +int32_t FatVolume::freeClusterCount() { +#if MAINTAIN_FREE_CLUSTER_COUNT + if (m_freeClusterCount >= 0) { + return m_freeClusterCount; + } +#endif // MAINTAIN_FREE_CLUSTER_COUNT + uint32_t free = 0; + uint32_t lba; + uint32_t todo = m_lastCluster + 1; + uint16_t n; + + if (FAT12_SUPPORT && m_fatType == 12) { + for (unsigned i = 2; i < todo; i++) { + uint32_t c; + int8_t fg = fatGet(i, &c); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (fg && c == 0) { + free++; + } + } + } else if (m_fatType == 16 || m_fatType == 32) { + lba = m_fatStartBlock; + while (todo) { + cache_t* pc = cacheFetchFat(lba++, FatCache::CACHE_FOR_READ); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + n = m_fatType == 16 ? 256 : 128; + if (todo < n) { + n = todo; + } + if (m_fatType == 16) { + for (uint16_t i = 0; i < n; i++) { + if (pc->fat16[i] == 0) { + free++; + } + } + } else { + for (uint16_t i = 0; i < n; i++) { + if (pc->fat32[i] == 0) { + free++; + } + } + } + todo -= n; + } + } else { + // invalid FAT type + DBG_FAIL_MACRO; + goto fail; + } + setFreeClusterCount(free); + return free; + +fail: + return -1; +} +//------------------------------------------------------------------------------ +bool FatVolume::init(uint8_t part) { + uint32_t clusterCount; + uint32_t totalBlocks; + uint32_t volumeStartBlock = 0; + fat32_boot_t* fbs; + cache_t* pc; + uint8_t tmp; + m_fatType = 0; + m_allocSearchStart = 1; + + m_cache.init(this); +#if USE_SEPARATE_FAT_CACHE + m_fatCache.init(this); +#endif // USE_SEPARATE_FAT_CACHE + + // if part == 0 assume super floppy with FAT boot sector in block zero + // if part > 0 assume mbr volume with partition table + if (part) { + if (part > 4) { + DBG_FAIL_MACRO; + goto fail; + } + pc = cacheFetchData(0, FatCache::CACHE_FOR_READ); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + part_t* p = &pc->mbr.part[part - 1]; + if ((p->boot & 0X7F) != 0 || p->firstSector == 0) { + // not a valid partition + DBG_FAIL_MACRO; + goto fail; + } + volumeStartBlock = p->firstSector; + } + pc = cacheFetchData(volumeStartBlock, FatCache::CACHE_FOR_READ); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + fbs = &(pc->fbs32); + if (fbs->bytesPerSector != 512 || + fbs->fatCount != 2 || + fbs->reservedSectorCount == 0) { + // not valid FAT volume + DBG_FAIL_MACRO; + goto fail; + } + m_blocksPerCluster = fbs->sectorsPerCluster; + m_clusterBlockMask = m_blocksPerCluster - 1; + + // determine shift that is same as multiply by m_blocksPerCluster + m_clusterSizeShift = 0; + for (tmp = 1; m_blocksPerCluster != tmp; tmp <<= 1, m_clusterSizeShift++) { + if (tmp == 0) { + DBG_FAIL_MACRO; + goto fail; + } + } + + m_blocksPerFat = fbs->sectorsPerFat16 ? + fbs->sectorsPerFat16 : fbs->sectorsPerFat32; + + m_fatStartBlock = volumeStartBlock + fbs->reservedSectorCount; + + // count for FAT16 zero for FAT32 + m_rootDirEntryCount = fbs->rootDirEntryCount; + + // directory start for FAT16 dataStart for FAT32 + m_rootDirStart = m_fatStartBlock + 2 * m_blocksPerFat; + // data start for FAT16 and FAT32 + m_dataStartBlock = m_rootDirStart + ((32 * fbs->rootDirEntryCount + 511)/512); + + // total blocks for FAT16 or FAT32 + totalBlocks = fbs->totalSectors16 ? + fbs->totalSectors16 : fbs->totalSectors32; + // total data blocks + clusterCount = totalBlocks - (m_dataStartBlock - volumeStartBlock); + + // divide by cluster size to get cluster count + clusterCount >>= m_clusterSizeShift; + m_lastCluster = clusterCount + 1; + + // Indicate unknown number of free clusters. + setFreeClusterCount(-1); + + // FAT type is determined by cluster count + if (clusterCount < 4085) { + m_fatType = 12; + if (!FAT12_SUPPORT) { + DBG_FAIL_MACRO; + goto fail; + } + } else if (clusterCount < 65525) { + m_fatType = 16; + } else { + m_rootDirStart = fbs->fat32RootCluster; + m_fatType = 32; + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatVolume::wipe(print_t* pr) { + cache_t* cache; + uint16_t count; + uint32_t lbn; + if (!m_fatType) { + DBG_FAIL_MACRO; + goto fail; + } + cache = cacheClear(); + if (!cache) { + DBG_FAIL_MACRO; + goto fail; + } + memset(cache->data, 0, 512); + // Zero root. + if (m_fatType == 32) { + lbn = clusterStartBlock(m_rootDirStart); + count = m_blocksPerCluster; + } else { + lbn = m_rootDirStart; + count = m_rootDirEntryCount/16; + } + for (uint32_t n = 0; n < count; n++) { + if (!writeBlock(lbn + n, cache->data)) { + DBG_FAIL_MACRO; + goto fail; + } + } + // Clear FATs. + count = 2*m_blocksPerFat; + lbn = m_fatStartBlock; + for (uint32_t nb = 0; nb < count; nb++) { + if (pr && (nb & 0XFF) == 0) { + pr->write('.'); + } + if (!writeBlock(lbn + nb, cache->data)) { + DBG_FAIL_MACRO; + goto fail; + } + } + // Reserve first two clusters. + if (m_fatType == 32) { + cache->fat32[0] = 0x0FFFFFF8; + cache->fat32[1] = 0x0FFFFFFF; + } else if (m_fatType == 16) { + cache->fat16[0] = 0XFFF8; + cache->fat16[1] = 0XFFFF; + } else if (FAT12_SUPPORT && m_fatType == 12) { + cache->fat32[0] = 0XFFFFF8; + } else { + DBG_FAIL_MACRO; + goto fail; + } + if (!writeBlock(m_fatStartBlock, cache->data) || + !writeBlock(m_fatStartBlock + m_blocksPerFat, cache->data)) { + DBG_FAIL_MACRO; + goto fail; + } + if (m_fatType == 32) { + // Reserve root cluster. + if (!fatPutEOC(m_rootDirStart) || !cacheSync()) { + DBG_FAIL_MACRO; + goto fail; + } + } + if (pr) { + pr->write('\r'); + pr->write('\n'); + } + m_fatType = 0; + return true; + +fail: + m_fatType = 0; + return false; +} diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/FatVolume.h b/Firmware_V2/src/libraries/SdFat/FatLib/FatVolume.h new file mode 100644 index 0000000..74d1e55 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/FatVolume.h @@ -0,0 +1,354 @@ +/* FatLib Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#ifndef FatVolume_h +#define FatVolume_h +/** + * \file + * \brief FatVolume class + */ +#include +#include "SysCall.h" +#include "FatLibConfig.h" +#include "FatStructs.h" +//------------------------------------------------------------------------------ +#ifndef DOXYGEN_SHOULD_SKIP_THIS +/** Macro for debug. */ +#define DEBUG_MODE 0 +#if DEBUG_MODE +#define DBG_FAIL_MACRO Serial.print(F(__FILE__)); Serial.println(__LINE__) +#define DBG_PRINT_IF(b) if (b) {Serial.println(F(#b)); DBG_FAIL_MACRO;} +#define DBG_HALT_IF(b) if (b) {Serial.println(F(#b));\ + DBG_FAIL_MACRO; SysCall::halt();} +#else // DEBUG_MODE +#define DBG_FAIL_MACRO +#define DBG_PRINT_IF(b) +#define DBG_HALT_IF(b) +#endif // DEBUG_MODE +#endif // DOXYGEN_SHOULD_SKIP_THIS +//------------------------------------------------------------------------------ +#if ENABLE_ARDUINO_FEATURES +/** Use Print for Arduino */ +typedef Print print_t; +#else // ENABLE_ARDUINO_FEATURES +/** + * \class CharWriter + * \brief Character output - often serial port. + */ +class CharWriter { + public: + virtual size_t write(char c) = 0; + virtual size_t write(const char* s) = 0; +}; +typedef CharWriter print_t; +#endif // ENABLE_ARDUINO_FEATURES +//------------------------------------------------------------------------------ +// Forward declaration of FatVolume. +class FatVolume; +//------------------------------------------------------------------------------ +/** + * \brief Cache for an raw data block. + */ +union cache_t { + /** Used to access cached file data blocks. */ + uint8_t data[512]; + /** Used to access cached FAT16 entries. */ + uint16_t fat16[256]; + /** Used to access cached FAT32 entries. */ + uint32_t fat32[128]; + /** Used to access cached directory entries. */ + dir_t dir[16]; + /** Used to access a cached Master Boot Record. */ + mbr_t mbr; + /** Used to access to a cached FAT boot sector. */ + fat_boot_t fbs; + /** Used to access to a cached FAT32 boot sector. */ + fat32_boot_t fbs32; + /** Used to access to a cached FAT32 FSINFO sector. */ + fat32_fsinfo_t fsinfo; +}; +//============================================================================== +/** + * \class FatCache + * \brief Block cache. + */ +class FatCache { + public: + /** Cached block is dirty */ + static const uint8_t CACHE_STATUS_DIRTY = 1; + /** Cashed block is FAT entry and must be mirrored in second FAT. */ + static const uint8_t CACHE_STATUS_MIRROR_FAT = 2; + /** Cache block status bits */ + static const uint8_t CACHE_STATUS_MASK + = CACHE_STATUS_DIRTY | CACHE_STATUS_MIRROR_FAT; + /** Sync existing block but do not read new block. */ + static const uint8_t CACHE_OPTION_NO_READ = 4; + /** Cache block for read. */ + static uint8_t const CACHE_FOR_READ = 0; + /** Cache block for write. */ + static uint8_t const CACHE_FOR_WRITE = CACHE_STATUS_DIRTY; + /** Reserve cache block for write - do not read from block device. */ + static uint8_t const CACHE_RESERVE_FOR_WRITE + = CACHE_STATUS_DIRTY | CACHE_OPTION_NO_READ; + /** \return Cache block address. */ + cache_t* block() { + return &m_block; + } + /** Set current block dirty. */ + void dirty() { + m_status |= CACHE_STATUS_DIRTY; + } + /** Initialize the cache. + * \param[in] vol FatVolume that owns this FatCache. + */ + void init(FatVolume *vol) { + m_vol = vol; + invalidate(); + } + /** Invalidate current cache block. */ + void invalidate() { + m_status = 0; + m_lbn = 0XFFFFFFFF; + } + /** \return Logical block number for cached block. */ + uint32_t lbn() { + return m_lbn; + } + /** Read a block into the cache. + * \param[in] lbn Block to read. + * \param[in] option mode for cached block. + * \return Address of cached block. */ + cache_t* read(uint32_t lbn, uint8_t option); + /** Write current block if dirty. + * \return true for success else false. + */ + bool sync(); + + private: + uint8_t m_status; + FatVolume* m_vol; + uint32_t m_lbn; + cache_t m_block; +}; +//============================================================================== +/** + * \class FatVolume + * \brief Access FAT16 and FAT32 volumes on raw file devices. + */ +class FatVolume { + public: + /** Create an instance of FatVolume + */ + FatVolume() : m_fatType(0) {} + + /** \return The volume's cluster size in blocks. */ + uint8_t blocksPerCluster() const { + return m_blocksPerCluster; + } + /** \return The number of blocks in one FAT. */ + uint32_t blocksPerFat() const { + return m_blocksPerFat; + } + /** Clear the cache and returns a pointer to the cache. Not for normal apps. + * \return A pointer to the cache buffer or zero if an error occurs. + */ + cache_t* cacheClear() { + if (!cacheSync()) { + return 0; + } + m_cache.invalidate(); + return m_cache.block(); + } + /** \return The total number of clusters in the volume. */ + uint32_t clusterCount() const { + return m_lastCluster - 1; + } + /** \return The shift count required to multiply by blocksPerCluster. */ + uint8_t clusterSizeShift() const { + return m_clusterSizeShift; + } + /** \return The logical block number for the start of file data. */ + uint32_t dataStartBlock() const { + return m_dataStartBlock; + } + /** \return The number of File Allocation Tables. */ + uint8_t fatCount() { + return 2; + } + /** \return The logical block number for the start of the first FAT. */ + uint32_t fatStartBlock() const { + return m_fatStartBlock; + } + /** \return The FAT type of the volume. Values are 12, 16 or 32. */ + uint8_t fatType() const { + return m_fatType; + } + /** Volume free space in clusters. + * + * \return Count of free clusters for success or -1 if an error occurs. + */ + int32_t freeClusterCount(); + /** Initialize a FAT volume. Try partition one first then try super + * floppy format. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool init() { + return init(1) || init(0); + } + /** Initialize a FAT volume. + + * \param[in] part The partition to be used. Legal values for \a part are + * 1-4 to use the corresponding partition on a device formatted with + * a MBR, Master Boot Record, or zero if the device is formatted as + * a super floppy with the FAT boot sector in block zero. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool init(uint8_t part); + /** \return The number of entries in the root directory for FAT16 volumes. */ + uint16_t rootDirEntryCount() const { + return m_rootDirEntryCount; + } + /** \return The logical block number for the start of the root directory + on FAT16 volumes or the first cluster number on FAT32 volumes. */ + uint32_t rootDirStart() const { + return m_rootDirStart; + } + /** \return The number of blocks in the volume */ + uint32_t volumeBlockCount() const { + return blocksPerCluster()*clusterCount(); + } + /** Wipe all data from the volume. + * \param[in] pr print stream for status dots. + * \return true for success else false. + */ + bool wipe(print_t* pr = 0); + /** Debug access to FAT table + * + * \param[in] n cluster number. + * \param[out] v value of entry + * \return true for success or false for failure + */ + int8_t dbgFat(uint32_t n, uint32_t* v) { + return fatGet(n, v); + } +//------------------------------------------------------------------------------ + private: + // Allow FatFile and FatCache access to FatVolume private functions. + friend class FatCache; + friend class FatFile; +//------------------------------------------------------------------------------ + uint8_t m_blocksPerCluster; // Cluster size in blocks. + uint8_t m_clusterBlockMask; // Mask to extract block of cluster. + uint8_t m_clusterSizeShift; // Cluster count to block count shift. + uint8_t m_fatType; // Volume type (12, 16, OR 32). + uint16_t m_rootDirEntryCount; // Number of entries in FAT16 root dir. + uint32_t m_allocSearchStart; // Start cluster for alloc search. + uint32_t m_blocksPerFat; // FAT size in blocks + uint32_t m_dataStartBlock; // First data block number. + uint32_t m_fatStartBlock; // Start block for first FAT. + uint32_t m_lastCluster; // Last cluster number in FAT. + uint32_t m_rootDirStart; // Start block for FAT16, cluster for FAT32. +//------------------------------------------------------------------------------ +#if MAINTAIN_FREE_CLUSTER_COUNT + int32_t m_freeClusterCount; // Count of free clusters in volume. + void setFreeClusterCount(int32_t value) { + m_freeClusterCount = value; + } + void updateFreeClusterCount(int32_t change) { + if (m_freeClusterCount >= 0) { + m_freeClusterCount += change; + } + } +#else // MAINTAIN_FREE_CLUSTER_COUNT + void setFreeClusterCount(int32_t value) { + (void)value; + } + void updateFreeClusterCount(int32_t change) { + (void)change; + } +#endif // MAINTAIN_FREE_CLUSTER_COUNT + +// block caches + FatCache m_cache; +#if USE_SEPARATE_FAT_CACHE + FatCache m_fatCache; + cache_t* cacheFetchFat(uint32_t blockNumber, uint8_t options) { + return m_fatCache.read(blockNumber, + options | FatCache::CACHE_STATUS_MIRROR_FAT); + } + bool cacheSync() { + return m_cache.sync() && m_fatCache.sync(); + } +#else // + cache_t* cacheFetchFat(uint32_t blockNumber, uint8_t options) { + return cacheFetchData(blockNumber, + options | FatCache::CACHE_STATUS_MIRROR_FAT); + } + bool cacheSync() { + return m_cache.sync(); + } +#endif // USE_SEPARATE_FAT_CACHE + cache_t* cacheFetchData(uint32_t blockNumber, uint8_t options) { + return m_cache.read(blockNumber, options); + } + void cacheInvalidate() { + m_cache.invalidate(); + } + bool cacheSyncData() { + return m_cache.sync(); + } + cache_t *cacheAddress() { + return m_cache.block(); + } + uint32_t cacheBlockNumber() { + return m_cache.lbn(); + } + void cacheDirty() { + m_cache.dirty(); + } +//------------------------------------------------------------------------------ + bool allocateCluster(uint32_t current, uint32_t* next); + bool allocContiguous(uint32_t count, uint32_t* firstCluster); + uint8_t blockOfCluster(uint32_t position) const { + return (position >> 9) & m_clusterBlockMask; + } + uint32_t clusterStartBlock(uint32_t cluster) const; + int8_t fatGet(uint32_t cluster, uint32_t* value); + bool fatPut(uint32_t cluster, uint32_t value); + bool fatPutEOC(uint32_t cluster) { + return fatPut(cluster, 0x0FFFFFFF); + } + bool freeChain(uint32_t cluster); + bool isEOC(uint32_t cluster) const { + return cluster > m_lastCluster; + } + //---------------------------------------------------------------------------- + // Virtual block I/O functions. + virtual bool readBlock(uint32_t block, uint8_t* dst) = 0; + virtual bool writeBlock(uint32_t block, const uint8_t* src) = 0; +#if USE_MULTI_BLOCK_IO + virtual bool readBlocks(uint32_t block, uint8_t* dst, size_t nb) = 0; + virtual bool writeBlocks(uint32_t block, const uint8_t* src, size_t nb) = 0; +#endif // USE_MULTI_BLOCK_IO +}; +#endif // FatVolume diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/FmtNumber.cpp b/Firmware_V2/src/libraries/SdFat/FatLib/FmtNumber.cpp new file mode 100644 index 0000000..3aa5bec --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/FmtNumber.cpp @@ -0,0 +1,455 @@ +/* FatLib Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#include "FmtNumber.h" +// Use Stimmer div/mod 10 on avr +#ifdef __AVR__ +#include +#define USE_STIMMER +#endif // __AVR__ +//------------------------------------------------------------------------------ +// Stimmer div/mod 10 for AVR +// this code fragment works out i/10 and i%10 by calculating +// i*(51/256)*(256/255)/2 == i*51/510 == i/10 +// by "j.k" I mean 32.8 fixed point, j is integer part, k is fractional part +// j.k = ((j+1.0)*51.0)/256.0 +// (we add 1 because we will be using the floor of the result later) +// divmod10_asm16 and divmod10_asm32 are public domain code by Stimmer. +// http://forum.arduino.cc/index.php?topic=167414.msg1293679#msg1293679 +#define divmod10_asm16(in32, mod8, tmp8) \ +asm volatile( \ + " ldi %2,51 \n\t" \ + " mul %A0,%2 \n\t" \ + " clr %A0 \n\t" \ + " add r0,%2 \n\t" \ + " adc %A0,r1 \n\t" \ + " mov %1,r0 \n\t" \ + " mul %B0,%2 \n\t" \ + " clr %B0 \n\t" \ + " add %A0,r0 \n\t" \ + " adc %B0,r1 \n\t" \ + " clr r1 \n\t" \ + " add %1,%A0 \n\t" \ + " adc %A0,%B0 \n\t" \ + " adc %B0,r1 \n\t" \ + " add %1,%B0 \n\t" \ + " adc %A0,r1 \n\t" \ + " adc %B0,r1 \n\t" \ + " lsr %B0 \n\t" \ + " ror %A0 \n\t" \ + " ror %1 \n\t" \ + " ldi %2,10 \n\t" \ + " mul %1,%2 \n\t" \ + " mov %1,r1 \n\t" \ + " clr r1 \n\t" \ + :"+r"(in32), "=d"(mod8), "=d"(tmp8) : : "r0") + +#define divmod10_asm32(in32, mod8, tmp8) \ +asm volatile( \ + " ldi %2,51 \n\t" \ + " mul %A0,%2 \n\t" \ + " clr %A0 \n\t" \ + " add r0,%2 \n\t" \ + " adc %A0,r1 \n\t" \ + " mov %1,r0 \n\t" \ + " mul %B0,%2 \n\t" \ + " clr %B0 \n\t" \ + " add %A0,r0 \n\t" \ + " adc %B0,r1 \n\t" \ + " mul %C0,%2 \n\t" \ + " clr %C0 \n\t" \ + " add %B0,r0 \n\t" \ + " adc %C0,r1 \n\t" \ + " mul %D0,%2 \n\t" \ + " clr %D0 \n\t" \ + " add %C0,r0 \n\t" \ + " adc %D0,r1 \n\t" \ + " clr r1 \n\t" \ + " add %1,%A0 \n\t" \ + " adc %A0,%B0 \n\t" \ + " adc %B0,%C0 \n\t" \ + " adc %C0,%D0 \n\t" \ + " adc %D0,r1 \n\t" \ + " add %1,%B0 \n\t" \ + " adc %A0,%C0 \n\t" \ + " adc %B0,%D0 \n\t" \ + " adc %C0,r1 \n\t" \ + " adc %D0,r1 \n\t" \ + " add %1,%D0 \n\t" \ + " adc %A0,r1 \n\t" \ + " adc %B0,r1 \n\t" \ + " adc %C0,r1 \n\t" \ + " adc %D0,r1 \n\t" \ + " lsr %D0 \n\t" \ + " ror %C0 \n\t" \ + " ror %B0 \n\t" \ + " ror %A0 \n\t" \ + " ror %1 \n\t" \ + " ldi %2,10 \n\t" \ + " mul %1,%2 \n\t" \ + " mov %1,r1 \n\t" \ + " clr r1 \n\t" \ + :"+r"(in32), "=d"(mod8), "=d"(tmp8) : : "r0") +//------------------------------------------------------------------------------ +/* +// C++ code is based on this version of divmod10 by robtillaart. +// http://forum.arduino.cc/index.php?topic=167414.msg1246851#msg1246851 +// from robtillaart post: +// The code is based upon the divu10() code from the book Hackers Delight1. +// My insight was that the error formula in divu10() was in fact modulo 10 +// but not always. Sometimes it was 10 more. +void divmod10(uint32_t in, uint32_t &div, uint32_t &mod) +{ + // q = in * 0.8; + uint32_t q = (in >> 1) + (in >> 2); + q = q + (q >> 4); + q = q + (q >> 8); + q = q + (q >> 16); // not needed for 16 bit version + + // q = q / 8; ==> q = in *0.1; + q = q >> 3; + + // determine error + uint32_t r = in - ((q << 3) + (q << 1)); // r = in - q*10; + div = q + (r > 9); + if (r > 9) mod = r - 10; + else mod = r; +} +// Hackers delight function is here: +// http://www.hackersdelight.org/hdcodetxt/divuc.c.txt +// Code below uses 8/10 = 0.1100 1100 1100 1100 1100 1100 1100 1100. +// 15 ops including the multiply, or 17 elementary ops. +unsigned divu10(unsigned n) { + unsigned q, r; + + q = (n >> 1) + (n >> 2); + q = q + (q >> 4); + q = q + (q >> 8); + q = q + (q >> 16); + q = q >> 3; + r = n - q*10; + return q + ((r + 6) >> 4); +// return q + (r > 9); +} +*/ +//------------------------------------------------------------------------------ +#ifndef DOXYGEN_SHOULD_SKIP_THIS +#ifdef __AVR__ +static const float m[] PROGMEM = {1e-1, 1e-2, 1e-4, 1e-8, 1e-16, 1e-32}; +static const float p[] PROGMEM = {1e+1, 1e+2, 1e+4, 1e+8, 1e+16, 1e+32}; +#else // __AVR__ +static const float m[] = {1e-1, 1e-2, 1e-4, 1e-8, 1e-16, 1e-32}; +static const float p[] = {1e+1, 1e+2, 1e+4, 1e+8, 1e+16, 1e+32}; +#endif // __AVR__ +#endif // DOXYGEN_SHOULD_SKIP_THIS +// scale float v by power of ten. return v*10^n +float scale10(float v, int8_t n) { + const float *s; + if (n < 0) { + n = -n; + s = m; + } else { + s = p; + } + n &= 63; + for (uint8_t i = 0; n; n >>= 1, i++) { +#ifdef __AVR__ + if (n & 1) { + v *= pgm_read_float(&s[i]); + } +#else // __AVR__ + if (n & 1) { + v *= s[i]; + } +#endif // __AVR__ + } + return v; +} +//------------------------------------------------------------------------------ +// Format 16-bit unsigned +char* fmtDec(uint16_t n, char* p) { + while (n > 9) { +#ifdef USE_STIMMER + uint8_t tmp8, r; + divmod10_asm16(n, r, tmp8); +#else // USE_STIMMER + uint16_t t = n; + n = (n >> 1) + (n >> 2); + n = n + (n >> 4); + n = n + (n >> 8); + // n = n + (n >> 16); // no code for 16-bit n + n = n >> 3; + uint8_t r = t - (((n << 2) + n) << 1); + if (r > 9) { + n++; + r -= 10; + } +#endif // USE_STIMMER + *--p = r + '0'; + } + *--p = n + '0'; + return p; +} +//------------------------------------------------------------------------------ +// format 32-bit unsigned +char* fmtDec(uint32_t n, char* p) { + while (n >> 16) { +#ifdef USE_STIMMER + uint8_t tmp8, r; + divmod10_asm32(n, r, tmp8); +#else // USE_STIMMER + uint32_t t = n; + n = (n >> 1) + (n >> 2); + n = n + (n >> 4); + n = n + (n >> 8); + n = n + (n >> 16); + n = n >> 3; + uint8_t r = t - (((n << 2) + n) << 1); + if (r > 9) { + n++; + r -= 10; + } +#endif // USE_STIMMER + *--p = r + '0'; + } + return fmtDec((uint16_t)n, p); +} +//------------------------------------------------------------------------------ +char* fmtFloat(float value, char* p, uint8_t prec) { + char sign = value < 0 ? '-' : 0; + if (sign) { + value = -value; + } + + if (isnan(value)) { + *--p = 'n'; + *--p = 'a'; + *--p = 'n'; + return p; + } + if (isinf(value)) { + *--p = 'f'; + *--p = 'n'; + *--p = 'i'; + return p; + } + if (value > 4294967040.0) { + *--p = 'f'; + *--p = 'v'; + *--p = 'o'; + return p; + } + if (prec > 9) { + prec = 9; + } + value += scale10(0.5, -prec); + + uint32_t whole = value; + if (prec) { + char* tmp = p - prec; + uint32_t fraction = scale10(value - whole, prec); + p = fmtDec(fraction, p); + while (p > tmp) { + *--p = '0'; + } + *--p = '.'; + } + p = fmtDec(whole, p); + if (sign) { + *--p = sign; + } + return p; +} +//------------------------------------------------------------------------------ +/** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] ptr Pointer to last char in buffer. + * \param[in] prec Number of digits after decimal point. + * \param[in] expChar Use exp format if non zero. + * \return Pointer to first character of result. + */ +char* fmtFloat(float value, char* ptr, uint8_t prec, char expChar) { + bool neg = value < 0; + if (neg) { + value = -value; + } + + // check for nan inf ovf + if (isnan(value)) { + *--ptr = 'n'; + *--ptr = 'a'; + *--ptr = 'n'; + return ptr; + } + if (isinf(value)) { + *--ptr = 'f'; + *--ptr = 'n'; + *--ptr = 'i'; + return ptr; + } + if (!expChar && value > 4294967040.0) { + *--ptr = 'f'; + *--ptr = 'v'; + *--ptr = 'o'; + return ptr; + } + if (prec > 9) { + prec = 9; + } + float round = scale10(0.5, -prec); + if (expChar) { + int8_t exp = 0; + bool expNeg = false; + if (value) { + while (value > 10.0) { + value *= 0.1; + exp++; + } + while (value < 1.0) { + value *= 10.0; + exp--; + } + value += round; + if (value > 10.0) { + value *= 0.1; + exp++; + } + expNeg = exp < 0; + if (expNeg) { + exp = -exp; + } + } + ptr = fmtDec((uint16_t)exp, ptr); + if (exp < 10) { + *--ptr = '0'; + } + *--ptr = expNeg ? '-' : '+'; + *--ptr = expChar; + } else { + // round value + value += round; + } + uint32_t whole = value; + if (prec) { + char* tmp = ptr - prec; + uint32_t fraction = scale10(value - whole, prec); + ptr = fmtDec(fraction, ptr); + while (ptr > tmp) { + *--ptr = '0'; + } + *--ptr = '.'; + } + ptr = fmtDec(whole, ptr); + if (neg) { + *--ptr = '-'; + } + return ptr; +} +//------------------------------------------------------------------------------ +char* fmtHex(uint32_t n, char* p) { + do { + uint8_t h = n & 0XF; + *--p = h + (h < 10 ? '0' : 'A' - 10); + n >>= 4; + } while (n); + return p; +} +//------------------------------------------------------------------------------ +float scanFloat(const char* str, char** ptr) { + int16_t const EXP_LIMIT = 100; + bool digit = false; + bool dot = false; + uint32_t fract = 0; + int fracExp = 0; + uint8_t nd = 0; + bool neg; + int c; + float v; + const char* successPtr = str; + + if (ptr) { + *ptr = const_cast(str); + } + + while (isSpace((c = *str++))) {} + neg = c == '-'; + if (c == '-' || c == '+') { + c = *str++; + } + // Skip leading zeros + while (c == '0') { + c = *str++; + digit = true; + } + for (;;) { + if (isDigit(c)) { + digit = true; + if (nd < 9) { + fract = 10*fract + c - '0'; + nd++; + if (dot) { + fracExp--; + } + } else { + if (!dot) { + fracExp++; + } + } + } else if (c == '.') { + if (dot) { + goto fail; + } + dot = true; + } else { + if (!digit) { + goto fail; + } + break; + } + successPtr = str; + c = *str++; + } + if (c == 'e' || c == 'E') { + int exp = 0; + c = *str++; + bool expNeg = c == '-'; + if (c == '-' || c == '+') { + c = *str++; + } + while (isDigit(c)) { + if (exp > EXP_LIMIT) { + goto fail; + } + exp = 10*exp + c - '0'; + successPtr = str; + c = *str++; + } + fracExp += expNeg ? -exp : exp; + } + if (ptr) { + *ptr = const_cast(successPtr); + } + v = scale10(static_cast(fract), fracExp); + return neg ? -v : v; + +fail: + return 0; +} + + diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/FmtNumber.h b/Firmware_V2/src/libraries/SdFat/FatLib/FmtNumber.h new file mode 100644 index 0000000..2bb97dc --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/FmtNumber.h @@ -0,0 +1,38 @@ +/* FatLib Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#ifndef FmtNumber_h +#define FmtNumber_h +// #include +inline bool isDigit(char c) { + return '0' <= c && c <= '9'; +} +inline bool isSpace(char c) { + return c == ' ' || (0X9 <= c && c <= 0XD); +} +#include +#include +char* fmtDec(uint16_t n, char* p); +char* fmtDec(uint32_t n, char* p); +char* fmtFloat(float value, char* p, uint8_t prec); +char* fmtFloat(float value, char* ptr, uint8_t prec, char expChar); +char* fmtHex(uint32_t n, char* p); +float scale10(float v, int8_t n); +float scanFloat(const char* str, char** ptr); +#endif // FmtNumber_h diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/StdioStream.cpp b/Firmware_V2/src/libraries/SdFat/FatLib/StdioStream.cpp new file mode 100644 index 0000000..40ad093 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/StdioStream.cpp @@ -0,0 +1,522 @@ +/* Arduino RamDisk Library + * Copyright (C) 2014 by William Greiman + * + * This file is part of the Arduino RamDisk Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino RamDisk Library. If not, see + * . + */ +#include "StdioStream.h" +#include "FmtNumber.h" +//------------------------------------------------------------------------------ +int StdioStream::fclose() { + int rtn = 0; + if (!m_flags) { + return EOF; + } + if (m_flags & F_SWR) { + if (!flushBuf()) { + rtn = EOF; + } + } + if (!FatFile::close()) { + rtn = EOF; + } + m_r = 0; + m_w = 0; + m_flags = 0; + return rtn; +} +//------------------------------------------------------------------------------ +int StdioStream::fflush() { + if ((m_flags & (F_SWR | F_SRW)) && !(m_flags & F_SRD)) { + if (flushBuf() && FatFile::sync()) { + return 0; + } + } + return EOF; +} +//------------------------------------------------------------------------------ +char* StdioStream::fgets(char* str, size_t num, size_t* len) { + char* s = str; + size_t n; + if (num-- == 0) { + return 0; + } + while (num) { + if ((n = m_r) == 0) { + if (!fillBuf()) { + if (s == str) { + return 0; + } + break; + } + n = m_r; + } + if (n > num) { + n = num; + } + uint8_t* end = reinterpret_cast(memchr(m_p, '\n', n)); + if (end != 0) { + n = ++end - m_p; + memcpy(s, m_p, n); + m_r -= n; + m_p = end; + s += n; + break; + } + memcpy(s, m_p, n); + m_r -= n; + m_p += n; + s += n; + num -= n; + } + *s = 0; + if (len) { + *len = s - str; + } + return str; +} +//------------------------------------------------------------------------------ +bool StdioStream::fopen(const char* path, const char* mode) { + uint8_t oflag; + switch (*mode++) { + case 'a': + m_flags = F_SWR; + oflag = O_WRITE | O_CREAT | O_APPEND | O_AT_END; + break; + + case 'r': + m_flags = F_SRD; + oflag = O_READ; + break; + + case 'w': + m_flags = F_SWR; + oflag = O_WRITE | O_CREAT | O_TRUNC; + break; + + default: + goto fail; + } + while (*mode) { + switch (*mode++) { + case '+': + m_flags |= F_SRW; + oflag |= O_RDWR; + break; + + case 'b': + break; + + case 'x': + oflag |= O_EXCL; + break; + + default: + goto fail; + } + } + if ((oflag & O_EXCL) && !(oflag & O_WRITE)) { + goto fail; + } + if (!FatFile::open(path, oflag)) { + goto fail; + } + m_r = 0; + m_w = 0; + m_p = m_buf; + return true; + +fail: + m_flags = 0; + return false; +} +//------------------------------------------------------------------------------ +int StdioStream::fputs(const char* str) { + size_t len = strlen(str); + return fwrite(str, 1, len) == len ? len : EOF; +} +//------------------------------------------------------------------------------ +size_t StdioStream::fread(void* ptr, size_t size, size_t count) { + uint8_t* dst = reinterpret_cast(ptr); + size_t total = size*count; + if (total == 0) { + return 0; + } + size_t need = total; + while (need > m_r) { + memcpy(dst, m_p, m_r); + dst += m_r; + m_p += m_r; + need -= m_r; + if (!fillBuf()) { + return (total - need)/size; + } + } + memcpy(dst, m_p, need); + m_r -= need; + m_p += need; + return count; +} +//------------------------------------------------------------------------------ +int StdioStream::fseek(int32_t offset, int origin) { + int32_t pos; + if (m_flags & F_SWR) { + if (!flushBuf()) { + goto fail; + } + } + switch (origin) { + case SEEK_CUR: + pos = ftell(); + if (pos < 0) { + goto fail; + } + pos += offset; + if (!FatFile::seekCur(pos)) { + goto fail; + } + break; + + case SEEK_SET: + if (!FatFile::seekSet(offset)) { + goto fail; + } + break; + + case SEEK_END: + if (!FatFile::seekEnd(offset)) { + goto fail; + } + break; + + default: + goto fail; + } + m_r = 0; + m_p = m_buf; + return 0; + +fail: + return EOF; +} +//------------------------------------------------------------------------------ +int32_t StdioStream::ftell() { + uint32_t pos = FatFile::curPosition(); + if (m_flags & F_SRD) { + if (m_r > pos) { + return -1L; + } + pos -= m_r; + } else if (m_flags & F_SWR) { + pos += m_p - m_buf; + } + return pos; +} +//------------------------------------------------------------------------------ +size_t StdioStream::fwrite(const void* ptr, size_t size, size_t count) { + return write(ptr, count*size) < 0 ? EOF : count; +#if 0 //////////////////////////////////////////////////////////////////////////////////// + const uint8_t* src = static_cast(ptr); + size_t total = count*size; + if (total == 0) { + return 0; + } + size_t todo = total; + + while (todo > m_w) { + memcpy(m_p, src, m_w); + m_p += m_w; + src += m_w; + todo -= m_w; + if (!flushBuf()) { + return (total - todo)/size; + } + } + memcpy(m_p, src, todo); + m_p += todo; + m_w -= todo; + return count; +#endif ////////////////////////////////////////////////////////////////////////////////// +} +//------------------------------------------------------------------------------ +int StdioStream::write(const void* buf, size_t count) { + const uint8_t* src = static_cast(buf); + size_t todo = count; + + while (todo > m_w) { + memcpy(m_p, src, m_w); + m_p += m_w; + src += m_w; + todo -= m_w; + if (!flushBuf()) { + return EOF; + } + } + memcpy(m_p, src, todo); + m_p += todo; + m_w -= todo; + return count; +} +//------------------------------------------------------------------------------ +#if (defined(ARDUINO) && ENABLE_ARDUINO_FEATURES) || defined(DOXYGEN) +size_t StdioStream::print(const __FlashStringHelper *str) { + const char *p = (const char*)str; + uint8_t c; + while ((c = pgm_read_byte(p))) { + if (putc(c) < 0) { + return 0; + } + p++; + } + return p - (const char*)str; +} +#endif // (defined(ARDUINO) && ENABLE_ARDUINO_FEATURES) || defined(DOXYGEN) +//------------------------------------------------------------------------------ +int StdioStream::printDec(float value, uint8_t prec) { + char buf[24]; + char *ptr = fmtFloat(value, buf + sizeof(buf), prec); + return write(ptr, buf + sizeof(buf) - ptr); +} +//------------------------------------------------------------------------------ +int StdioStream::printDec(signed char n) { + if (n < 0) { + if (fputc('-') < 0) { + return -1; + } + n = -n; + } + return printDec((unsigned char)n); +} +//------------------------------------------------------------------------------ +int StdioStream::printDec(int16_t n) { + int s; + uint8_t rtn = 0; + if (n < 0) { + if (fputc('-') < 0) { + return -1; + } + n = -n; + rtn++; + } + if ((s = printDec((uint16_t)n)) < 0) { + return s; + } + return rtn; +} +//------------------------------------------------------------------------------ +int StdioStream::printDec(uint16_t n) { +#define NEW_WAY +#ifdef NEW_WAY + char buf[5]; + char *ptr = fmtDec(n, buf + sizeof(buf)); + uint8_t len = buf + sizeof(buf) - ptr; + return write(ptr, len); +#else + uint8_t len; + if (n < 100) { + len = n < 10 ? 1 : 2; + } else { + len = n < 1000 ? 3 : n < 10000 ? 4 : 5; + } + char* str = fmtSpace(len); + if (!str) { + return -1; + } + fmtDec(n, str); + return len; +#endif +} +//------------------------------------------------------------------------------ +int StdioStream::printDec(int32_t n) { + uint8_t s = 0; + if (n < 0) { + if (fputc('-') < 0) { + return -1; + } + n = -n; + s = 1; + } + int rtn = printDec((uint32_t)n); + return rtn > 0 ? rtn + s : -1; +} +//------------------------------------------------------------------------------ +int StdioStream::printDec(uint32_t n) { +#ifdef NEW_WAY + char buf[10]; + char *ptr = fmtDec(n, buf + sizeof(buf)); + uint8_t len = buf + sizeof(buf) - ptr; + return write(ptr, len); +#else + uint8_t len; + if (n < 0X10000) { + return printDec((uint16_t)n); + } + if (n < 10000000) { + len = n < 100000 ? 5 : n < 1000000 ? 6 : 7; + } else { + len = n < 100000000 ? 8 : n < 1000000000 ? 9 : 10; + } + + char* str = fmtSpace(len); + if (!str) { + return -1; + } + fmtDec(n, str); + return len; +#endif +} +//------------------------------------------------------------------------------ +int StdioStream::printHex(uint32_t n) { +#ifdef NEW_WAY + char buf[8]; + char *ptr = fmtHex(n, buf + sizeof(buf)); + uint8_t len = buf + sizeof(buf) - ptr; + return write(ptr, len); +#else + size_t len; + if (n < 0X10000) { + len = n < 0X10 ? 1 : n < 0X100 ? 2 : n < 0X1000 ? 3 : 4; + } else { + len = n < 0X100000 ? 5 : n < 0X1000000 ? 6 : n < 0X10000000 ? 7 : 8; + } + char* str = fmtSpace(len); + if (!str) { + return -1; + } + + do { + uint8_t h = n & 0XF; + *str-- = h + (h < 10 ? '0' : 'A' - 10); + n >>= 4; + } while (n); + return len; +#endif +} +//------------------------------------------------------------------------------ +bool StdioStream::rewind() { + if (m_flags & F_SWR) { + if (!flushBuf()) { + return false; + } + } + FatFile::seekSet(0); + m_r = 0; + return true; +} +//------------------------------------------------------------------------------ +int StdioStream::ungetc(int c) { + // error if EOF. + if (c == EOF) { + return EOF; + } + // error if not reading. + if ((m_flags & F_SRD) == 0) { + return EOF; + } + // error if no space. + if (m_p == m_buf) { + return EOF; + } + m_r++; + m_flags &= ~F_EOF; + return *--m_p = (uint8_t)c; +} +//============================================================================== +// private +//------------------------------------------------------------------------------ +int StdioStream::fillGet() { + if (!fillBuf()) { + return EOF; + } + m_r--; + return *m_p++; +} +//------------------------------------------------------------------------------ +// private +bool StdioStream::fillBuf() { + if (!(m_flags & + F_SRD)) { // check for F_ERR and F_EOF ??///////////////// + if (!(m_flags & F_SRW)) { + m_flags |= F_ERR; + return false; + } + if (m_flags & F_SWR) { + if (!flushBuf()) { + return false; + } + m_flags &= ~F_SWR; + m_flags |= F_SRD; + m_w = 0; + } + } + m_p = m_buf + UNGETC_BUF_SIZE; + int nr = FatFile::read(m_p, sizeof(m_buf) - UNGETC_BUF_SIZE); + if (nr <= 0) { + m_flags |= nr < 0 ? F_ERR : F_EOF; + m_r = 0; + return false; + } + m_r = nr; + return true; +} +//------------------------------------------------------------------------------ +// private +bool StdioStream::flushBuf() { + if (!(m_flags & + F_SWR)) { // check for F_ERR ??//////////////////////// + if (!(m_flags & F_SRW)) { + m_flags |= F_ERR; + return false; + } + m_flags &= ~F_SRD; + m_flags |= F_SWR; + m_r = 0; + m_w = sizeof(m_buf); + m_p = m_buf; + return true; + } + uint8_t n = m_p - m_buf; + m_p = m_buf; + m_w = sizeof(m_buf); + if (FatFile::write(m_buf, n) == n) { + return true; + } + m_flags |= F_ERR; + return false; +} +//------------------------------------------------------------------------------ +int StdioStream::flushPut(uint8_t c) { + if (!flushBuf()) { + return EOF; + } + m_w--; + return *m_p++ = c; +} +//------------------------------------------------------------------------------ +char* StdioStream::fmtSpace(uint8_t len) { + if (m_w < len) { + if (!flushBuf() || m_w < len) { + return 0; + } + } + if (len > m_w) { + return 0; + } + m_p += len; + m_w -= len; + return reinterpret_cast(m_p); +} + diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/StdioStream.h b/Firmware_V2/src/libraries/SdFat/FatLib/StdioStream.h new file mode 100644 index 0000000..218c23f --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/StdioStream.h @@ -0,0 +1,662 @@ +/* Arduino RamDisk Library + * Copyright (C) 2014 by William Greiman + * + * This file is part of the Arduino RamDisk Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino RamDisk Library. If not, see + * . + */ +#ifndef StdioStream_h +#define StdioStream_h +/** + * \file + * \brief StdioStream class + */ +#include +#include "FatFile.h" +//------------------------------------------------------------------------------ +/** Total size of stream buffer. The entire buffer is used for output. + * During input UNGETC_BUF_SIZE of this space is reserved for ungetc. + */ +const uint8_t STREAM_BUF_SIZE = 64; +/** Amount of buffer allocated for ungetc during input. */ +const uint8_t UNGETC_BUF_SIZE = 2; +//------------------------------------------------------------------------------ +// Get rid of any macros defined in . +#include +#undef clearerr +#undef fclose +#undef feof +#undef ferror +#undef fflush +#undef fgetc +#undef fgetpos +#undef fgets +#undef fopen +#undef fprintf +#undef fputc +#undef fputs +#undef fread +#undef freopen +#undef fscanf +#undef fseek +#undef fsetpos +#undef ftell +#undef fwrite +#undef getc +#undef getchar +#undef gets +#undef perror +#undef printf +#undef putc +#undef putchar +#undef puts +#undef remove +#undef rename +#undef rewind +#undef scanf +#undef setbuf +#undef setvbuf +//#undef sprintf // NOLINT +#undef sscanf +#undef tmpfile +#undef tmpnam +#undef ungetc +#undef vfprintf +#undef vprintf +#undef vsprintf + +// make sure needed macros are defined +#ifndef EOF +/** End-of-file return value. */ +#define EOF (-1) +#endif // EOF +#ifndef NULL +/** Null pointer */ +#define NULL 0 +#endif // NULL +#ifndef SEEK_CUR +/** Seek relative to current position. */ +#define SEEK_CUR 1 +#endif // SEEK_CUR +#ifndef SEEK_END +/** Seek relative to end-of-file. */ +#define SEEK_END 2 +#endif // SEEK_END +#ifndef SEEK_SET +/** Seek relative to start-of-file. */ +#define SEEK_SET 0 +#endif // SEEK_SET +//------------------------------------------------------------------------------ +/** \class StdioStream + * \brief StdioStream implements a minimal stdio stream. + * + * StdioStream does not support subdirectories or long file names. + */ +class StdioStream : private FatFile { + public: + /** Constructor + * + */ + StdioStream() { + m_w = m_r = 0; + m_p = m_buf; + m_flags = 0; + } + //---------------------------------------------------------------------------- + /** Clear the stream's end-of-file and error indicators. */ + void clearerr() { + m_flags &= ~(F_ERR | F_EOF); + } + //---------------------------------------------------------------------------- + /** Close a stream. + * + * A successful call to the fclose function causes the stream to be + * flushed and the associated file to be closed. Any unwritten buffered + * data is written to the file; any unread buffered data is discarded. + * Whether or not the call succeeds, the stream is disassociated from + * the file. + * + * \return zero if the stream was successfully closed, or EOF if any any + * errors are detected. + */ + int fclose(); + //---------------------------------------------------------------------------- + /** Test the stream's end-of-file indicator. + * \return non-zero if and only if the end-of-file indicator is set. + */ + int feof() { + return (m_flags & F_EOF) != 0; + } + //---------------------------------------------------------------------------- + /** Test the stream's error indicator. + * \return return non-zero if and only if the error indicator is set. + */ + int ferror() { + return (m_flags & F_ERR) != 0; + } + //---------------------------------------------------------------------------- + /** Flush the stream. + * + * If stream is an output stream or an update stream in which the most + * recent operation was not input, any unwritten data is written to the + * file; otherwise the call is an error since any buffered input data + * would be lost. + * + * \return sets the error indicator for the stream and returns EOF if an + * error occurs, otherwise it returns zero. + */ + int fflush(); + //---------------------------------------------------------------------------- + /** Get a byte from the stream. + * + * \return If the end-of-file indicator for the stream is set, or if the + * stream is at end-of-file, the end-of-file indicator for the stream is + * set and the fgetc function returns EOF. Otherwise, the fgetc function + * returns the next character from the input stream. + */ + int fgetc() { + return m_r-- == 0 ? fillGet() : *m_p++; + } + //---------------------------------------------------------------------------- + /** Get a string from a stream. + * + * The fgets function reads at most one less than the number of + * characters specified by num from the stream into the array pointed + * to by str. No additional characters are read after a new-line + * character (which is retained) or after end-of-file. A null character + * is written immediately after the last character read into the array. + * + * \param[out] str Pointer to an array of where the string is copied. + * + * \param[in] num Maximum number of characters including the null + * character. + * + * \param[out] len If len is not null and fgets is successful, the + * length of the string is returned. + * + * \return str if successful. If end-of-file is encountered and no + * characters have been read into the array, the contents of the array + * remain unchanged and a null pointer is returned. If a read error + * occurs during the operation, the array contents are indeterminate + * and a null pointer is returned. + */ + char* fgets(char* str, size_t num, size_t* len = 0); + //---------------------------------------------------------------------------- + /** Open a stream. + * + * Open a file and associates the stream with it. + * + * \param[in] path file to be opened. + * + * \param[in] mode a string that indicates the open mode. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
"r" or "rb"Open a file for reading. The file must exist.
"w" or "wb"Truncate an existing to zero length or create an empty file + * for writing.
"wx" or "wbx"Create a file for writing. Fails if the file already exists.
"a" or "ab"Append; open or create file for writing at end-of-file.
"r+" or "rb+" or "r+b"Open a file for update (reading and writing).
"w+" or "w+b" or "wb+"Truncate an existing to zero length or create a file for update.
"w+x" or "w+bx" or "wb+x"Create a file for update. Fails if the file already exists.
"a+" or "a+b" or "ab+"Append; open or create a file for update, writing at end-of-file.
+ * The character 'b' shall have no effect, but is allowed for ISO C + * standard conformance. + * + * Opening a file with append mode causes all subsequent writes to the + * file to be forced to the then current end-of-file, regardless of + * intervening calls to the fseek function. + * + * When a file is opened with update mode, both input and output may be + * performed on the associated stream. However, output shall not be + * directly followed by input without an intervening call to the fflush + * function or to a file positioning function (fseek, or rewind), and + * input shall not be directly followed by output without an intervening + * call to a file positioning function, unless the input operation + * encounters end-of-file. + * + * \return true for success or false for failure. + */ + bool fopen(const char* path, const char * mode); + //---------------------------------------------------------------------------- + /** Write a byte to a stream. + * + * \param[in] c the byte to be written (converted to an unsigned char). + * + * \return Upon successful completion, fputc() returns the value it + * has written. Otherwise, it returns EOF and sets the error indicator for + * the stream. + */ + int fputc(int c) { + return m_w-- == 0 ? flushPut(c) : *m_p++ = c; + } + //---------------------------------------------------------------------------- + /** Write a string to a stream. + * + * \param[in] str a pointer to the string to be written. + * + * \return for success, fputs() returns a non-negative + * number. Otherwise, it returns EOF and sets the error indicator for + * the stream. + */ + int fputs(const char* str); + //---------------------------------------------------------------------------- + /** Binary input. + * + * Reads an array of up to count elements, each one with a size of size + * bytes. + * \param[out] ptr pointer to area of at least (size*count) bytes where + * the data will be stored. + * + * \param[in] size the size, in bytes, of each element to be read. + * + * \param[in] count the number of elements to be read. + * + * \return number of elements successfully read, which may be less than + * count if a read error or end-of-file is encountered. If size or count + * is zero, fread returns zero and the contents of the array and the + * state of the stream remain unchanged. + */ + size_t fread(void* ptr, size_t size, size_t count); + //---------------------------------------------------------------------------- + /** Set the file position for the stream. + * + * \param[in] offset number of offset from the origin. + * + * \param[in] origin position used as reference for the offset. It is + * specified by one of the following constants. + * + * SEEK_SET - Beginning of file. + * + * SEEK_CUR - Current position of the file pointer. + * + * SEEK_END - End of file. + * + * \return zero for success. Otherwise, it returns non-zero and sets the + * error indicator for the stream. + */ + int fseek(int32_t offset, int origin); + //---------------------------------------------------------------------------- + /** Get the current position in a stream. + * + * \return If successful, ftell return the current value of the position + * indicator. On failure, ftell returns −1L. + */ + int32_t ftell(); + //---------------------------------------------------------------------------- + /** Binary output. + * + * Writes an array of up to count elements, each one with a size of size + * bytes. + * \param[in] ptr pointer to (size*count) bytes of data to be written. + * + * \param[in] size the size, in bytes, of each element to be written. + * + * \param[in] count the number of elements to be written. + * + * \return number of elements successfully written. if this number is + * less than count, an error has occurred. If size or count is zero, + * fwrite returns zero. + */ + size_t fwrite(const void * ptr, size_t size, size_t count); + //---------------------------------------------------------------------------- + /** Get a byte from the stream. + * + * getc and fgetc are equivalent but getc is in-line so it is faster but + * require more flash memory. + * + * \return If the end-of-file indicator for the stream is set, or if the + * stream is at end-of-file, the end-of-file indicator for the stream is + * set and the fgetc function returns EOF. Otherwise, the fgetc function + * returns the next character from the input stream. + */ + inline __attribute__((always_inline)) + int getc() { + return m_r-- == 0 ? fillGet() : *m_p++; + } + //---------------------------------------------------------------------------- + /** Write a byte to a stream. + * + * putc and fputc are equivalent but putc is in-line so it is faster but + * require more flash memory. + * + * \param[in] c the byte to be written (converted to an unsigned char). + * + * \return Upon successful completion, fputc() returns the value it + * has written. Otherwise, it returns EOF and sets the error indicator for + * the stream. + */ + inline __attribute__((always_inline)) + int putc(int c) { + return m_w-- == 0 ? flushPut(c) : *m_p++ = c; + } + //---------------------------------------------------------------------------- + /** Write a CR/LF. + * + * \return two, the number of bytes written, for success or -1 for failure. + */ + inline __attribute__((always_inline)) + int putCRLF() { + if (m_w < 2) { + if (!flushBuf()) { + return -1; + } + } + *m_p++ = '\r'; + *m_p++ = '\n'; + m_w -= 2; + return 2; + } + //---------------------------------------------------------------------------- + /** Write a character. + * \param[in] c the character to write. + * \return the number of bytes written. + */ + size_t print(char c) { + return putc(c) < 0 ? 0 : 1; + } + //---------------------------------------------------------------------------- + /** Write a string. + * + * \param[in] str the string to be written. + * + * \return the number of bytes written. + */ + size_t print(const char* str) { + int n = fputs(str); + return n < 0 ? 0 : n; + } + //---------------------------------------------------------------------------- +#if (defined(ARDUINO) && ENABLE_ARDUINO_FEATURES) || defined(DOXYGEN) + /** Print a string stored in flash memory. + * + * \param[in] str the string to print. + * + * \return the number of bytes written. + */ + size_t print(const __FlashStringHelper *str); +#endif // (defined(ARDUINO) && ENABLE_ARDUINO_FEATURES) || defined(DOXYGEN) + //---------------------------------------------------------------------------- + /** Print a floating point number. + * + * \param[in] prec Number of digits after decimal point. + * + * \param[in] val the number to be printed. + * + * \return the number of bytes written. + */ + size_t print(double val, uint8_t prec = 2) { + return print(static_cast(val), prec); + } + //---------------------------------------------------------------------------- + /** Print a floating point number. + * + * \param[in] prec Number of digits after decimal point. + * + * \param[in] val the number to be printed. + * + * \return the number of bytes written. + */ + size_t print(float val, uint8_t prec = 2) { + int n = printDec(val, prec); + return n > 0 ? n : 0; + } + //---------------------------------------------------------------------------- + /** Print a number. + * + * \param[in] val the number to be printed. + * + * \return the number of bytes written. + */ + template + size_t print(T val) { + int n = printDec(val); + return n > 0 ? n : 0; + } + //---------------------------------------------------------------------------- + /** Write a CR/LF. + * + * \return two, the number of bytes written, for success or zero for failure. + */ + size_t println() { + return putCRLF() > 0 ? 2 : 0; + } + //---------------------------------------------------------------------------- + /** Print a floating point number followed by CR/LF. + * + * \param[in] val the number to be printed. + * + * \param[in] prec Number of digits after decimal point. + * + * \return the number of bytes written. + */ + size_t println(double val, uint8_t prec = 2) { + return println(static_cast(val), prec); + } + //---------------------------------------------------------------------------- + /** Print a floating point number followed by CR/LF. + * + * \param[in] val the number to be printed. + * + * \param[in] prec Number of digits after decimal point. + * + * \return the number of bytes written. + */ + size_t println(float val, uint8_t prec = 2) { + int n = printDec(val, prec); + return n > 0 && putCRLF() > 0 ? n + 2 : 0; + } + //---------------------------------------------------------------------------- + /** Print an item followed by CR/LF + * + * \param[in] val the item to be printed. + * + * \return the number of bytes written. + */ + template + size_t println(T val) { + int n = print(val); + return putCRLF() > 0 ? n + 2 : 0; + } + //---------------------------------------------------------------------------- + /** Print a char as a number. + * \param[in] n number to be printed. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(char n) { + if (CHAR_MIN == 0) { + return printDec((unsigned char)n); + } else { + return printDec((signed char)n); + } + } + //---------------------------------------------------------------------------- + /** print a signed 8-bit integer + * \param[in] n number to be printed. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(signed char n); + //---------------------------------------------------------------------------- + /** Print an unsigned 8-bit number. + * \param[in] n number to be print. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(unsigned char n) { + return printDec((uint16_t)n); + } + //---------------------------------------------------------------------------- + /** Print a int16_t + * \param[in] n number to be printed. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(int16_t n); + //---------------------------------------------------------------------------- + /** print a uint16_t. + * \param[in] n number to be printed. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(uint16_t n); + //---------------------------------------------------------------------------- + /** Print a signed 32-bit integer. + * \param[in] n number to be printed. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(int32_t n); + //---------------------------------------------------------------------------- + /** Write an unsigned 32-bit number. + * \param[in] n number to be printed. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(uint32_t n); + //---------------------------------------------------------------------------- + /** Print a double. + * \param[in] value The number to be printed. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(double value, uint8_t prec) { + return printDec(static_cast(value), prec); + } + //---------------------------------------------------------------------------- + /** Print a float. + * \param[in] value The number to be printed. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(float value, uint8_t prec); + //---------------------------------------------------------------------------- + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + int printField(double value, char term, uint8_t prec = 2) { + return printField(static_cast(value), term, prec) > 0; + } + //---------------------------------------------------------------------------- + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + int printField(float value, char term, uint8_t prec = 2) { + int rtn = printDec(value, prec); + return rtn < 0 || putc(term) < 0 ? -1 : rtn + 1; + } + //---------------------------------------------------------------------------- + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. + * \return The number of bytes written or -1 if an error occurs. + */ + template + int printField(T value, char term) { + int rtn = printDec(value); + return rtn < 0 || putc(term) < 0 ? -1 : rtn + 1; + } + //---------------------------------------------------------------------------- + /** Print HEX + * \param[in] n number to be printed as HEX. + * + * \return The number of bytes written or -1 if an error occurs. + */ + int printHex(uint32_t n); + //---------------------------------------------------------------------------- + /** Print HEX with CRLF + * \param[in] n number to be printed as HEX. + * + * \return The number of bytes written or -1 if an error occurs. + */ + int printHexln(uint32_t n) { + int rtn = printHex(n); + return rtn < 0 || putCRLF() != 2 ? -1 : rtn + 2; + } + //---------------------------------------------------------------------------- + /** Set position of a stream to the beginning. + * + * The rewind function sets the file position to the beginning of the + * file. It is equivalent to fseek(0L, SEEK_SET) except that the error + * indicator for the stream is also cleared. + * + * \return true for success or false for failure. + */ + bool rewind(); + //---------------------------------------------------------------------------- + /** Push a byte back into an input stream. + * + * \param[in] c the byte (converted to an unsigned char) to be pushed back. + * + * One character of push-back is guaranteed. If the ungetc function is + * called too many times without an intervening read or file positioning + * operation on that stream, the operation may fail. + * + * A successful intervening call to a file positioning function (fseek, + * fsetpos, or rewind) discards any pushed-back characters for the stream. + * + * \return Upon successful completion, ungetc() returns the byte pushed + * back after conversion. Otherwise it returns EOF. + */ + int ungetc(int c); + //============================================================================ + private: + bool fillBuf(); + int fillGet(); + bool flushBuf(); + int flushPut(uint8_t c); + char* fmtSpace(uint8_t len); + int write(const void* buf, size_t count); + //---------------------------------------------------------------------------- + // F_SRD and F_WR are never simultaneously asserted + static const uint8_t F_SRD = 0x01; // OK to read + static const uint8_t F_SWR = 0x02; // OK to write + static const uint8_t F_SRW = 0x04; // open for reading & writing + static const uint8_t F_EOF = 0x10; // found EOF + static const uint8_t F_ERR = 0x20; // found error + //---------------------------------------------------------------------------- + uint8_t m_flags; + uint8_t* m_p; + uint8_t m_r; + uint8_t m_w; + uint8_t m_buf[STREAM_BUF_SIZE]; +}; +//------------------------------------------------------------------------------ +#endif // StdioStream_h diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/SysCall.h b/Firmware_V2/src/libraries/SdFat/FatLib/SysCall.h new file mode 100644 index 0000000..ad1c33d --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/SysCall.h @@ -0,0 +1,71 @@ +/* FatLib Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#ifndef SysCall_h +#define SysCall_h +/** + * \file + * \brief SysCall class + */ +#if defined(ARDUINO) +#include +#include "../../SPI/SPI.h" +#elif defined(PLATFORM_ID) // Only defined if a Particle device +#include "application.h" +#else // defined(ARDUINO) +#error "Unknown system" +#endif // defined(ARDUINO) +#ifndef F +/** Define macro for strings stored in flash. */ +#define F(str) (str) +#endif // F +/** + * \class SysCall + * \brief SysCall - Class to wrap system calls. + */ +class SysCall { + public: + /** Halt execution of this thread. */ + static void halt() { + while (1) { + yield(); + } + } + /** Yield to other threads. */ + static void yield(); +}; +#if defined(ESP8266) +inline void SysCall::yield() { + // Avoid ESP8266 bug + delay(0); +} +#elif defined(ARDUINO) +inline void SysCall::yield() { + // Use the external Arduino yield() function. + ::yield(); +} +#elif defined(PLATFORM_ID) // Only defined if a Particle device +inline void SysCall::yield() { + Particle.process(); +} +#else // defined(ARDUINO) +inline void SysCall::yield() {} +#endif // defined(ARDUINO) + +#endif // SysCall_h diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/bufstream.h b/Firmware_V2/src/libraries/SdFat/FatLib/bufstream.h new file mode 100644 index 0000000..8b49701 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/bufstream.h @@ -0,0 +1,167 @@ +/* FatLib Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#ifndef bufstream_h +#define bufstream_h +/** + * \file + * \brief \ref ibufstream and \ref obufstream classes + */ +#include +#include "iostream.h" +//============================================================================== +/** + * \class ibufstream + * \brief parse a char string + */ +class ibufstream : public istream { + public: + /** Constructor */ + ibufstream() : m_buf(0), m_len(0) {} + /** Constructor + * \param[in] str pointer to string to be parsed + * Warning: The string will not be copied so must stay in scope. + */ + explicit ibufstream(const char* str) { + init(str); + } + /** Initialize an ibufstream + * \param[in] str pointer to string to be parsed + * Warning: The string will not be copied so must stay in scope. + */ + void init(const char* str) { + m_buf = str; + m_len = strlen(m_buf); + m_pos = 0; + clear(); + } + + protected: + /// @cond SHOW_PROTECTED + int16_t getch() { + if (m_pos < m_len) { + return m_buf[m_pos++]; + } + setstate(eofbit); + return -1; + } + void getpos(FatPos_t *pos) { + pos->position = m_pos; + } + bool seekoff(off_type off, seekdir way) { + (void)off; + (void)way; + return false; + } + bool seekpos(pos_type pos) { + if (pos < m_len) { + m_pos = pos; + return true; + } + return false; + } + void setpos(FatPos_t *pos) { + m_pos = pos->position; + } + pos_type tellpos() { + return m_pos; + } + /// @endcond + private: + const char* m_buf; + size_t m_len; + size_t m_pos; +}; +//============================================================================== +/** + * \class obufstream + * \brief format a char string + */ +class obufstream : public ostream { + public: + /** constructor */ + obufstream() : m_in(0) {} + /** Constructor + * \param[in] buf buffer for formatted string + * \param[in] size buffer size + */ + obufstream(char *buf, size_t size) { + init(buf, size); + } + /** Initialize an obufstream + * \param[in] buf buffer for formatted string + * \param[in] size buffer size + */ + void init(char *buf, size_t size) { + m_buf = buf; + buf[0] = '\0'; + m_size = size; + m_in = 0; + } + /** \return a pointer to the buffer */ + char* buf() { + return m_buf; + } + /** \return the length of the formatted string */ + size_t length() { + return m_in; + } + + protected: + /// @cond SHOW_PROTECTED + void putch(char c) { + if (m_in >= (m_size - 1)) { + setstate(badbit); + return; + } + m_buf[m_in++] = c; + m_buf[m_in] = '\0'; + } + void putstr(const char *str) { + while (*str) { + putch(*str++); + } + } + bool seekoff(off_type off, seekdir way) { + (void)off; + (void)way; + return false; + } + bool seekpos(pos_type pos) { + if (pos > m_in) { + return false; + } + m_in = pos; + m_buf[m_in] = '\0'; + return true; + } + bool sync() { + return true; + } + + pos_type tellpos() { + return m_in; + } + /// @endcond + private: + char *m_buf; + size_t m_size; + size_t m_in; +}; +#endif // bufstream_h diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/fstream.cpp b/Firmware_V2/src/libraries/SdFat/FatLib/fstream.cpp new file mode 100644 index 0000000..505f083 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/fstream.cpp @@ -0,0 +1,167 @@ +/* FatLib Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#include "fstream.h" +//============================================================================== +/// @cond SHOW_PROTECTED +int16_t FatStreamBase::getch() { + uint8_t c; + int8_t s = read(&c, 1); + if (s != 1) { + if (s < 0) { + setstate(badbit); + } else { + setstate(eofbit); + } + return -1; + } + if (c != '\r' || (getmode() & ios::binary)) { + return c; + } + s = read(&c, 1); + if (s == 1 && c == '\n') { + return c; + } + if (s == 1) { + seekCur(-1); + } + return '\r'; +} +//------------------------------------------------------------------------------ +void FatStreamBase::open(const char* path, ios::openmode mode) { + uint8_t flags; + switch (mode & (app | in | out | trunc)) { + case app | in: + case app | in | out: + flags = O_RDWR | O_APPEND | O_CREAT; + break; + + case app: + case app | out: + flags = O_WRITE | O_APPEND | O_CREAT; + break; + + case in: + flags = O_READ; + break; + + case in | out: + flags = O_RDWR; + break; + + case in | out | trunc: + flags = O_RDWR | O_TRUNC | O_CREAT; + break; + + case out: + case out | trunc: + flags = O_WRITE | O_TRUNC | O_CREAT; + break; + + default: + goto fail; + } + if (mode & ios::ate) { + flags |= O_AT_END; + } + if (!FatFile::open(path, flags)) { + goto fail; + } + setmode(mode); + clear(); + return; + +fail: + FatFile::close(); + setstate(failbit); + return; +} +//------------------------------------------------------------------------------ +void FatStreamBase::putch(char c) { + if (c == '\n' && !(getmode() & ios::binary)) { + write('\r'); + } + write(c); + if (getWriteError()) { + setstate(badbit); + } +} +//------------------------------------------------------------------------------ +void FatStreamBase::putstr(const char* str) { + size_t n = 0; + while (1) { + char c = str[n]; + if (c == '\0' || (c == '\n' && !(getmode() & ios::binary))) { + if (n > 0) { + write(str, n); + } + if (c == '\0') { + break; + } + write('\r'); + str += n; + n = 0; + } + n++; + } + if (getWriteError()) { + setstate(badbit); + } +} +//------------------------------------------------------------------------------ +/** Internal do not use + * \param[in] off + * \param[in] way + */ +bool FatStreamBase::seekoff(off_type off, seekdir way) { + pos_type pos; + switch (way) { + case beg: + pos = off; + break; + + case cur: + pos = curPosition() + off; + break; + + case end: + pos = fileSize() + off; + break; + + default: + return false; + } + return seekpos(pos); +} +//------------------------------------------------------------------------------ +/** Internal do not use + * \param[in] pos + */ +bool FatStreamBase::seekpos(pos_type pos) { + return seekSet(pos); +} +//------------------------------------------------------------------------------ +int FatStreamBase::write(const void* buf, size_t n) { + return FatFile::write(buf, n); +} +//------------------------------------------------------------------------------ +void FatStreamBase::write(char c) { + write(&c, 1); +} +/// @endcond diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/fstream.h b/Firmware_V2/src/libraries/SdFat/FatLib/fstream.h new file mode 100644 index 0000000..5410c38 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/fstream.h @@ -0,0 +1,315 @@ +/* FatLib Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#ifndef fstream_h +#define fstream_h +/** + * \file + * \brief \ref fstream, \ref ifstream, and \ref ofstream classes + */ +#include "FatFile.h" +#include "iostream.h" +//============================================================================== +/** + * \class FatStreamBase + * \brief Base class for C++ style streams + */ +class FatStreamBase : protected FatFile, virtual public ios { + protected: + /// @cond SHOW_PROTECTED + int16_t getch(); + void putch(char c); + void putstr(const char *str); + void open(const char* path, ios::openmode mode); + /** Internal do not use + * \return mode + */ + ios::openmode getmode() { + return m_mode; + } + /** Internal do not use + * \param[in] mode + */ + void setmode(ios::openmode mode) { + m_mode = mode; + } + bool seekoff(off_type off, seekdir way); + bool seekpos(pos_type pos); + int write(const void* buf, size_t n); + void write(char c); + /// @endcond + private: + ios::openmode m_mode; +}; +//============================================================================== +/** + * \class fstream + * \brief file input/output stream. + */ +class fstream : public iostream, FatStreamBase { + public: + using iostream::peek; + fstream() {} + /** Constructor with open + * + * \param[in] path path to open + * \param[in] mode open mode + */ + explicit fstream(const char* path, openmode mode = in | out) { + open(path, mode); + } +#if DESTRUCTOR_CLOSES_FILE + ~fstream() {} +#endif // DESTRUCTOR_CLOSES_FILE + /** Clear state and writeError + * \param[in] state new state for stream + */ + void clear(iostate state = goodbit) { + ios::clear(state); + FatFile::clearWriteError(); + } + /** Close a file and force cached data and directory information + * to be written to the storage device. + */ + void close() { + FatFile::close(); + } + /** Open a fstream + * \param[in] path file to open + * \param[in] mode open mode + * + * Valid open modes are (at end, ios::ate, and/or ios::binary may be added): + * + * ios::in - Open file for reading. + * + * ios::out or ios::out | ios::trunc - Truncate to 0 length, if existent, + * or create a file for writing only. + * + * ios::app or ios::out | ios::app - Append; open or create file for + * writing at end-of-file. + * + * ios::in | ios::out - Open file for update (reading and writing). + * + * ios::in | ios::out | ios::trunc - Truncate to zero length, if existent, + * or create file for update. + * + * ios::in | ios::app or ios::in | ios::out | ios::app - Append; open or + * create text file for update, writing at end of file. + */ + void open(const char* path, openmode mode = in | out) { + FatStreamBase::open(path, mode); + } + /** \return True if stream is open else false. */ + bool is_open() { + return FatFile::isOpen(); + } + + protected: + /// @cond SHOW_PROTECTED + /** Internal - do not use + * \return + */ + int16_t getch() { + return FatStreamBase::getch(); + } + /** Internal - do not use + * \param[out] pos + */ + void getpos(FatPos_t* pos) { + FatFile::getpos(pos); + } + /** Internal - do not use + * \param[in] c + */ + void putch(char c) { + FatStreamBase::putch(c); + } + /** Internal - do not use + * \param[in] str + */ + void putstr(const char *str) { + FatStreamBase::putstr(str); + } + /** Internal - do not use + * \param[in] pos + */ + bool seekoff(off_type off, seekdir way) { + return FatStreamBase::seekoff(off, way); + } + bool seekpos(pos_type pos) { + return FatStreamBase::seekpos(pos); + } + void setpos(FatPos_t* pos) { + FatFile::setpos(pos); + } + bool sync() { + return FatStreamBase::sync(); + } + pos_type tellpos() { + return FatStreamBase::curPosition(); + } + /// @endcond +}; +//============================================================================== +/** + * \class ifstream + * \brief file input stream. + */ +class ifstream : public istream, FatStreamBase { + public: + using istream::peek; + ifstream() {} + /** Constructor with open + * \param[in] path file to open + * \param[in] mode open mode + */ + explicit ifstream(const char* path, openmode mode = in) { + open(path, mode); + } +#if DESTRUCTOR_CLOSES_FILE + ~ifstream() {} +#endif // DESTRUCTOR_CLOSES_FILE + /** Close a file and force cached data and directory information + * to be written to the storage device. + */ + void close() { + FatFile::close(); + } + /** \return True if stream is open else false. */ + bool is_open() { + return FatFile::isOpen(); + } + /** Open an ifstream + * \param[in] path file to open + * \param[in] mode open mode + * + * \a mode See fstream::open() for valid modes. + */ + void open(const char* path, openmode mode = in) { + FatStreamBase::open(path, mode | in); + } + + protected: + /// @cond SHOW_PROTECTED + /** Internal - do not use + * \return + */ + int16_t getch() { + return FatStreamBase::getch(); + } + /** Internal - do not use + * \param[out] pos + */ + void getpos(FatPos_t* pos) { + FatFile::getpos(pos); + } + /** Internal - do not use + * \param[in] pos + */ + bool seekoff(off_type off, seekdir way) { + return FatStreamBase::seekoff(off, way); + } + bool seekpos(pos_type pos) { + return FatStreamBase::seekpos(pos); + } + void setpos(FatPos_t* pos) { + FatFile::setpos(pos); + } + pos_type tellpos() { + return FatStreamBase::curPosition(); + } + /// @endcond +}; +//============================================================================== +/** + * \class ofstream + * \brief file output stream. + */ +class ofstream : public ostream, FatStreamBase { + public: + ofstream() {} + /** Constructor with open + * \param[in] path file to open + * \param[in] mode open mode + */ + explicit ofstream(const char* path, ios::openmode mode = out) { + open(path, mode); + } +#if DESTRUCTOR_CLOSES_FILE + ~ofstream() {} +#endif // DESTRUCTOR_CLOSES_FILE + /** Clear state and writeError + * \param[in] state new state for stream + */ + void clear(iostate state = goodbit) { + ios::clear(state); + FatFile::clearWriteError(); + } + /** Close a file and force cached data and directory information + * to be written to the storage device. + */ + void close() { + FatFile::close(); + } + /** Open an ofstream + * \param[in] path file to open + * \param[in] mode open mode + * + * \a mode See fstream::open() for valid modes. + */ + void open(const char* path, openmode mode = out) { + FatStreamBase::open(path, mode | out); + } + /** \return True if stream is open else false. */ + bool is_open() { + return FatFile::isOpen(); + } + + protected: + /// @cond SHOW_PROTECTED + /** + * Internal do not use + * \param[in] c + */ + void putch(char c) { + FatStreamBase::putch(c); + } + void putstr(const char* str) { + FatStreamBase::putstr(str); + } + bool seekoff(off_type off, seekdir way) { + return FatStreamBase::seekoff(off, way); + } + bool seekpos(pos_type pos) { + return FatStreamBase::seekpos(pos); + } + /** + * Internal do not use + * \param[in] b + */ + bool sync() { + return FatStreamBase::sync(); + } + pos_type tellpos() { + return FatStreamBase::curPosition(); + } + /// @endcond +}; +//------------------------------------------------------------------------------ +#endif // fstream_h diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/ios.h b/Firmware_V2/src/libraries/SdFat/FatLib/ios.h new file mode 100644 index 0000000..d89d96c --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/ios.h @@ -0,0 +1,418 @@ +/* FatLib Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#ifndef ios_h +#define ios_h +#include "FatFile.h" +/** + * \file + * \brief \ref ios_base and \ref ios classes + */ +//============================================================================== +/** + * \class ios_base + * \brief Base class for all streams + */ +class ios_base { + public: + /** typedef for iostate bitmask */ + typedef unsigned char iostate; + // State flags. + /** iostate for no flags */ + static const iostate goodbit = 0x00; + /** iostate bad bit for a nonrecoverable error. */ + static const iostate badbit = 0X01; + /** iostate bit for end of file reached */ + static const iostate eofbit = 0x02; + /** iostate fail bit for nonfatal error */ + static const iostate failbit = 0X04; + /** + * unsigned size that can represent maximum file size. + * (violates spec - should be signed) + */ + typedef uint32_t streamsize; + /** type for absolute seek position */ + typedef uint32_t pos_type; + /** type for relative seek offset */ + typedef int32_t off_type; + + /** enumerated type for the direction of relative seeks */ + enum seekdir { + /** seek relative to the beginning of the stream */ + beg, + /** seek relative to the current stream position */ + cur, + /** seek relative to the end of the stream */ + end + }; + /** type for format flags */ + typedef unsigned int fmtflags; + /** left adjust fields */ + static const fmtflags left = 0x0001; + /** right adjust fields */ + static const fmtflags right = 0x0002; + /** fill between sign/base prefix and number */ + static const fmtflags internal = 0x0004; + /** base 10 flag*/ + static const fmtflags dec = 0x0008; + /** base 16 flag */ + static const fmtflags hex = 0x0010; + /** base 8 flag */ + static const fmtflags oct = 0x0020; + // static const fmtflags fixed = 0x0040; + // static const fmtflags scientific = 0x0080; + /** use strings true/false for bool */ + static const fmtflags boolalpha = 0x0100; + /** use prefix 0X for hex and 0 for oct */ + static const fmtflags showbase = 0x0200; + /** always show '.' for floating numbers */ + static const fmtflags showpoint = 0x0400; + /** show + sign for nonnegative numbers */ + static const fmtflags showpos = 0x0800; + /** skip initial white space */ + static const fmtflags skipws = 0x1000; + // static const fmtflags unitbuf = 0x2000; + /** use uppercase letters in number representations */ + static const fmtflags uppercase = 0x4000; + /** mask for adjustfield */ + static const fmtflags adjustfield = left | right | internal; + /** mask for basefield */ + static const fmtflags basefield = dec | hex | oct; + // static const fmtflags floatfield = scientific | fixed; + //---------------------------------------------------------------------------- + /** typedef for iostream open mode */ + typedef uint8_t openmode; + + // Openmode flags. + /** seek to end before each write */ + static const openmode app = 0X4; + /** open and seek to end immediately after opening */ + static const openmode ate = 0X8; + /** perform input and output in binary mode (as opposed to text mode) */ + static const openmode binary = 0X10; + /** open for input */ + static const openmode in = 0X20; + /** open for output */ + static const openmode out = 0X40; + /** truncate an existing stream when opening */ + static const openmode trunc = 0X80; + //---------------------------------------------------------------------------- + ios_base() : m_fill(' '), m_fmtflags(dec | right | skipws) + , m_precision(2), m_width(0) {} + /** \return fill character */ + char fill() { + return m_fill; + } + /** Set fill character + * \param[in] c new fill character + * \return old fill character + */ + char fill(char c) { + char r = m_fill; + m_fill = c; + return r; + } + /** \return format flags */ + fmtflags flags() const { + return m_fmtflags; + } + /** set format flags + * \param[in] fl new flag + * \return old flags + */ + fmtflags flags(fmtflags fl) { + fmtflags tmp = m_fmtflags; + m_fmtflags = fl; + return tmp; + } + /** \return precision */ + int precision() const { + return m_precision; + } + /** set precision + * \param[in] n new precision + * \return old precision + */ + int precision(unsigned int n) { + int r = m_precision; + m_precision = n; + return r; + } + /** set format flags + * \param[in] fl new flags to be or'ed in + * \return old flags + */ + fmtflags setf(fmtflags fl) { + fmtflags r = m_fmtflags; + m_fmtflags |= fl; + return r; + } + /** modify format flags + * \param[in] mask flags to be removed + * \param[in] fl flags to be set after mask bits have been cleared + * \return old flags + */ + fmtflags setf(fmtflags fl, fmtflags mask) { + fmtflags r = m_fmtflags; + m_fmtflags &= ~mask; + m_fmtflags |= fl; + return r; + } + /** clear format flags + * \param[in] fl flags to be cleared + * \return old flags + */ + void unsetf(fmtflags fl) { + m_fmtflags &= ~fl; + } + /** \return width */ + unsigned width() { + return m_width; + } + /** set width + * \param[in] n new width + * \return old width + */ + unsigned width(unsigned n) { + unsigned r = m_width; + m_width = n; + return r; + } + + protected: + /** \return current number base */ + uint8_t flagsToBase() { + uint8_t f = flags() & basefield; + return f == oct ? 8 : f != hex ? 10 : 16; + } + + private: + char m_fill; + fmtflags m_fmtflags; + unsigned char m_precision; + unsigned int m_width; +}; +//------------------------------------------------------------------------------ +/** function for boolalpha manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& boolalpha(ios_base& str) { + str.setf(ios_base::boolalpha); + return str; +} +/** function for dec manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& dec(ios_base& str) { + str.setf(ios_base::dec, ios_base::basefield); + return str; +} +/** function for hex manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& hex(ios_base& str) { + str.setf(ios_base::hex, ios_base::basefield); + return str; +} +/** function for internal manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& internal(ios_base& str) { + str.setf(ios_base::internal, ios_base::adjustfield); + return str; +} +/** function for left manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& left(ios_base& str) { + str.setf(ios_base::left, ios_base::adjustfield); + return str; +} +/** function for noboolalpha manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& noboolalpha(ios_base& str) { + str.unsetf(ios_base::boolalpha); + return str; +} +/** function for noshowbase manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& noshowbase(ios_base& str) { + str.unsetf(ios_base::showbase); + return str; +} +/** function for noshowpoint manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& noshowpoint(ios_base& str) { + str.unsetf(ios_base::showpoint); + return str; +} +/** function for noshowpos manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& noshowpos(ios_base& str) { + str.unsetf(ios_base::showpos); + return str; +} +/** function for noskipws manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& noskipws(ios_base& str) { + str.unsetf(ios_base::skipws); + return str; +} +/** function for nouppercase manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& nouppercase(ios_base& str) { + str.unsetf(ios_base::uppercase); + return str; +} +/** function for oct manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& oct(ios_base& str) { + str.setf(ios_base::oct, ios_base::basefield); + return str; +} +/** function for right manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& right(ios_base& str) { + str.setf(ios_base::right, ios_base::adjustfield); + return str; +} +/** function for showbase manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& showbase(ios_base& str) { + str.setf(ios_base::showbase); + return str; +} +/** function for showpos manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& showpos(ios_base& str) { + str.setf(ios_base::showpos); + return str; +} +/** function for showpoint manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& showpoint(ios_base& str) { + str.setf(ios_base::showpoint); + return str; +} +/** function for skipws manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& skipws(ios_base& str) { + str.setf(ios_base::skipws); + return str; +} +/** function for uppercase manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& uppercase(ios_base& str) { + str.setf(ios_base::uppercase); + return str; +} +//============================================================================== +/** + * \class ios + * \brief Error and state information for all streams + */ +class ios : public ios_base { + public: + /** Create ios with no error flags set */ + ios() : m_iostate(0) {} + + /** \return null pointer if fail() is true. */ + operator const void*() const { + return !fail() ? reinterpret_cast(this) : 0; + } + /** \return true if fail() else false. */ + bool operator!() const { + return fail(); + } + /** \return The iostate flags for this file. */ + iostate rdstate() const { + return m_iostate; + } + /** \return True if no iostate flags are set else false. */ + bool good() const { + return m_iostate == goodbit; + } + /** \return true if end of file has been reached else false. + * + * Warning: An empty file returns false before the first read. + * + * Moral: eof() is only useful in combination with fail(), to find out + * whether EOF was the cause for failure + */ + bool eof() const { + return m_iostate & eofbit; + } + /** \return true if any iostate bit other than eof are set else false. */ + bool fail() const { + return m_iostate & (failbit | badbit); + } + /** \return true if bad bit is set else false. */ + bool bad() const { + return m_iostate & badbit; + } + /** Clear iostate bits. + * + * \param[in] state The flags you want to set after clearing all flags. + **/ + void clear(iostate state = goodbit) { + m_iostate = state; + } + /** Set iostate bits. + * + * \param[in] state Bitts to set. + **/ + void setstate(iostate state) { + m_iostate |= state; + } + + private: + iostate m_iostate; +}; +#endif // ios_h diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/iostream.h b/Firmware_V2/src/libraries/SdFat/FatLib/iostream.h new file mode 100644 index 0000000..deeae6e --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/iostream.h @@ -0,0 +1,153 @@ +/* FatLib Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#ifndef iostream_h +#define iostream_h +/** + * \file + * \brief \ref iostream class + */ +#include "istream.h" +#include "ostream.h" +/** Skip white space + * \param[in] is the Stream + * \return The stream + */ +inline istream& ws(istream& is) { + is.skipWhite(); + return is; +} +/** insert endline + * \param[in] os The Stream + * \return The stream + */ +inline ostream& endl(ostream& os) { + os.put('\n'); +#if ENDL_CALLS_FLUSH + os.flush(); +#endif // ENDL_CALLS_FLUSH + return os; +} +/** flush manipulator + * \param[in] os The stream + * \return The stream + */ +inline ostream& flush(ostream& os) { + os.flush(); + return os; +} +/** + * \struct setfill + * \brief type for setfill manipulator + */ +struct setfill { + /** fill character */ + char c; + /** constructor + * + * \param[in] arg new fill character + */ + explicit setfill(char arg) : c(arg) {} +}; +/** setfill manipulator + * \param[in] os the stream + * \param[in] arg set setfill object + * \return the stream + */ +inline ostream &operator<< (ostream &os, const setfill &arg) { + os.fill(arg.c); + return os; +} +/** setfill manipulator + * \param[in] obj the stream + * \param[in] arg set setfill object + * \return the stream + */ +inline istream &operator>>(istream &obj, const setfill &arg) { + obj.fill(arg.c); + return obj; +} +//------------------------------------------------------------------------------ +/** \struct setprecision + * \brief type for setprecision manipulator + */ +struct setprecision { + /** precision */ + unsigned int p; + /** constructor + * \param[in] arg new precision + */ + explicit setprecision(unsigned int arg) : p(arg) {} +}; +/** setprecision manipulator + * \param[in] os the stream + * \param[in] arg set setprecision object + * \return the stream + */ +inline ostream &operator<< (ostream &os, const setprecision &arg) { + os.precision(arg.p); + return os; +} +/** setprecision manipulator + * \param[in] is the stream + * \param[in] arg set setprecision object + * \return the stream + */ +inline istream &operator>>(istream &is, const setprecision &arg) { + is.precision(arg.p); + return is; +} +//------------------------------------------------------------------------------ +/** \struct setw + * \brief type for setw manipulator + */ +struct setw { + /** width */ + unsigned w; + /** constructor + * \param[in] arg new width + */ + explicit setw(unsigned arg) : w(arg) {} +}; +/** setw manipulator + * \param[in] os the stream + * \param[in] arg set setw object + * \return the stream + */ +inline ostream &operator<< (ostream &os, const setw &arg) { + os.width(arg.w); + return os; +} +/** setw manipulator + * \param[in] is the stream + * \param[in] arg set setw object + * \return the stream + */ +inline istream &operator>>(istream &is, const setw &arg) { + is.width(arg.w); + return is; +} +//============================================================================== +/** + * \class iostream + * \brief Input/Output stream + */ +class iostream : public istream, public ostream { +}; +#endif // iostream_h diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/istream.cpp b/Firmware_V2/src/libraries/SdFat/FatLib/istream.cpp new file mode 100644 index 0000000..9485099 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/istream.cpp @@ -0,0 +1,391 @@ +/* FatLib Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +// #include +#include +#include +#include "istream.h" +//------------------------------------------------------------------------------ +int istream::get() { + int c; + m_gcount = 0; + c = getch(); + if (c < 0) { + setstate(failbit); + } else { + m_gcount = 1; + } + return c; +} +//------------------------------------------------------------------------------ +istream& istream::get(char& c) { + int tmp = get(); + if (tmp >= 0) { + c = tmp; + } + return *this; +} +//------------------------------------------------------------------------------ +istream& istream::get(char *str, streamsize n, char delim) { + int c; + FatPos_t pos; + m_gcount = 0; + while ((m_gcount + 1) < n) { + c = getch(&pos); + if (c < 0) { + break; + } + if (c == delim) { + setpos(&pos); + break; + } + str[m_gcount++] = c; + } + if (n > 0) { + str[m_gcount] = '\0'; + } + if (m_gcount == 0) { + setstate(failbit); + } + return *this; +} +//------------------------------------------------------------------------------ +void istream::getBool(bool *b) { + if ((flags() & boolalpha) == 0) { + getNumber(b); + return; + } +#ifdef __AVR__ + PGM_P truePtr = PSTR("true"); + PGM_P falsePtr = PSTR("false"); +#else // __AVR__ + const char* truePtr = "true"; + const char* falsePtr = "false"; +#endif // __AVR + const uint8_t true_len = 4; + const uint8_t false_len = 5; + bool trueOk = true; + bool falseOk = true; + uint8_t i = 0; + int c = readSkip(); + while (1) { +#ifdef __AVR__ + falseOk = falseOk && c == pgm_read_byte(falsePtr + i); + trueOk = trueOk && c == pgm_read_byte(truePtr + i); +#else // __AVR__ + falseOk = falseOk && c == falsePtr[i]; + trueOk = trueOk && c == truePtr[i]; +#endif // __AVR__ + if (trueOk == false && falseOk == false) { + break; + } + i++; + if (trueOk && i == true_len) { + *b = true; + return; + } + if (falseOk && i == false_len) { + *b = false; + return; + } + c = getch(); + } + setstate(failbit); +} +//------------------------------------------------------------------------------ +void istream::getChar(char* ch) { + int16_t c = readSkip(); + if (c < 0) { + setstate(failbit); + } else { + *ch = c; + } +} +//------------------------------------------------------------------------------ +// +// http://www.exploringbinary.com/category/numbers-in-computers/ +// +int16_t const EXP_LIMIT = 100; +static const uint32_t uint32_max = (uint32_t)-1; +bool istream::getDouble(double* value) { + bool got_digit = false; + bool got_dot = false; + bool neg; + int16_t c; + bool expNeg = false; + int16_t exp = 0; + int16_t fracExp = 0; + uint32_t frac = 0; + FatPos_t endPos; + double pow10; + double v; + + getpos(&endPos); + c = readSkip(); + neg = c == '-'; + if (c == '-' || c == '+') { + c = getch(); + } + while (1) { + if (isdigit(c)) { + got_digit = true; + if (frac < uint32_max/10) { + frac = frac * 10 + (c - '0'); + if (got_dot) { + fracExp--; + } + } else { + if (!got_dot) { + fracExp++; + } + } + } else if (!got_dot && c == '.') { + got_dot = true; + } else { + break; + } + if (fracExp < -EXP_LIMIT || fracExp > EXP_LIMIT) { + goto fail; + } + c = getch(&endPos); + } + if (!got_digit) { + goto fail; + } + if (c == 'e' || c == 'E') { + c = getch(); + expNeg = c == '-'; + if (c == '-' || c == '+') { + c = getch(); + } + while (isdigit(c)) { + if (exp > EXP_LIMIT) { + goto fail; + } + exp = exp * 10 + (c - '0'); + c = getch(&endPos); + } + } + v = static_cast(frac); + exp = expNeg ? fracExp - exp : fracExp + exp; + expNeg = exp < 0; + if (expNeg) { + exp = -exp; + } + pow10 = 10.0; + while (exp) { + if (exp & 1) { + if (expNeg) { + // check for underflow + if (v < FLT_MIN * pow10 && frac != 0) { + goto fail; + } + v /= pow10; + } else { + // check for overflow + if (v > FLT_MAX / pow10) { + goto fail; + } + v *= pow10; + } + } + pow10 *= pow10; + exp >>= 1; + } + setpos(&endPos); + *value = neg ? -v : v; + return true; + +fail: + // error restore position to last good place + setpos(&endPos); + setstate(failbit); + return false; +} +//------------------------------------------------------------------------------ + +istream& istream::getline(char *str, streamsize n, char delim) { + FatPos_t pos; + int c; + m_gcount = 0; + if (n > 0) { + str[0] = '\0'; + } + while (1) { + c = getch(&pos); + if (c < 0) { + break; + } + if (c == delim) { + m_gcount++; + break; + } + if ((m_gcount + 1) >= n) { + setpos(&pos); + setstate(failbit); + break; + } + str[m_gcount++] = c; + str[m_gcount] = '\0'; + } + if (m_gcount == 0) { + setstate(failbit); + } + return *this; +} +//------------------------------------------------------------------------------ +bool istream::getNumber(uint32_t posMax, uint32_t negMax, uint32_t* num) { + int16_t c; + int8_t any = 0; + int8_t have_zero = 0; + uint8_t neg; + uint32_t val = 0; + uint32_t cutoff; + uint8_t cutlim; + FatPos_t endPos; + uint8_t f = flags() & basefield; + uint8_t base = f == oct ? 8 : f != hex ? 10 : 16; + getpos(&endPos); + c = readSkip(); + + neg = c == '-' ? 1 : 0; + if (c == '-' || c == '+') { + c = getch(); + } + + if (base == 16 && c == '0') { // TESTSUITE + c = getch(&endPos); + if (c == 'X' || c == 'x') { + c = getch(); + // remember zero in case no hex digits follow x/X + have_zero = 1; + } else { + any = 1; + } + } + // set values for overflow test + cutoff = neg ? negMax : posMax; + cutlim = cutoff % base; + cutoff /= base; + + while (1) { + if (isdigit(c)) { + c -= '0'; + } else if (isalpha(c)) { + c -= isupper(c) ? 'A' - 10 : 'a' - 10; + } else { + break; + } + if (c >= base) { + break; + } + if (val > cutoff || (val == cutoff && c > cutlim)) { + // indicate overflow error + any = -1; + break; + } + val = val * base + c; + c = getch(&endPos); + any = 1; + } + setpos(&endPos); + if (any > 0 || (have_zero && any >= 0)) { + *num = neg ? -val : val; + return true; + } + setstate(failbit); + return false; +} +//------------------------------------------------------------------------------ +void istream::getStr(char *str) { + FatPos_t pos; + uint16_t i = 0; + uint16_t m = width() ? width() - 1 : 0XFFFE; + if (m != 0) { + getpos(&pos); + int c = readSkip(); + + while (i < m) { + if (c < 0) { + break; + } + if (isspace(c)) { + setpos(&pos); + break; + } + str[i++] = c; + c = getch(&pos); + } + } + str[i] = '\0'; + if (i == 0) { + setstate(failbit); + } + width(0); +} +//------------------------------------------------------------------------------ +istream& istream::ignore(streamsize n, int delim) { + int c; + m_gcount = 0; + while (m_gcount < n) { + c = getch(); + if (c < 0) { + break; + } + m_gcount++; + if (c == delim) { + break; + } + } + return *this; +} +//------------------------------------------------------------------------------ +int istream::peek() { + int16_t c; + FatPos_t pos; + m_gcount = 0; + getpos(&pos); + c = getch(); + if (c < 0) { + if (!bad()) { + setstate(eofbit); + } + } else { + setpos(&pos); + } + return c; +} +//------------------------------------------------------------------------------ +int16_t istream::readSkip() { + int16_t c; + do { + c = getch(); + } while (isspace(c) && (flags() & skipws)); + return c; +} +//------------------------------------------------------------------------------ +/** used to implement ws() */ +void istream::skipWhite() { + int c; + FatPos_t pos; + do { + c = getch(&pos); + } while (isspace(c)); + setpos(&pos); +} diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/istream.h b/Firmware_V2/src/libraries/SdFat/FatLib/istream.h new file mode 100644 index 0000000..4075507 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/istream.h @@ -0,0 +1,379 @@ +/* FatLib Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#ifndef istream_h +#define istream_h +/** + * \file + * \brief \ref istream class + */ +#include "ios.h" + +/** + * \class istream + * \brief Input Stream + */ +class istream : public virtual ios { + public: + istream() {} + /** call manipulator + * \param[in] pf function to call + * \return the stream + */ + istream& operator>>(istream& (*pf)(istream& str)) { + return pf(*this); + } + /** call manipulator + * \param[in] pf function to call + * \return the stream + */ + istream& operator>>(ios_base& (*pf)(ios_base& str)) { + pf(*this); + return *this; + } + /** call manipulator + * \param[in] pf function to call + * \return the stream + */ + istream& operator>>(ios& (*pf)(ios& str)) { + pf(*this); + return *this; + } + /** + * Extract a character string + * \param[out] str location to store the string. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(char *str) { + getStr(str); + return *this; + } + /** + * Extract a character + * \param[out] ch location to store the character. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(char& ch) { + getChar(&ch); + return *this; + } + /** + * Extract a character string + * \param[out] str location to store the string. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(signed char *str) { + getStr(reinterpret_cast(str)); + return *this; + } + /** + * Extract a character + * \param[out] ch location to store the character. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(signed char& ch) { + getChar(reinterpret_cast(&ch)); + return *this; + } + /** + * Extract a character string + * \param[out] str location to store the string. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(unsigned char *str) { + getStr(reinterpret_cast(str)); + return *this; + } + /** + * Extract a character + * \param[out] ch location to store the character. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(unsigned char& ch) { + getChar(reinterpret_cast(&ch)); + return *this; + } + /** + * Extract a value of type bool. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(bool& arg) { + getBool(&arg); + return *this; + } + /** + * Extract a value of type short. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream &operator>>(short& arg) { // NOLINT + getNumber(&arg); + return *this; + } + /** + * Extract a value of type unsigned short. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream &operator>>(unsigned short& arg) { // NOLINT + getNumber(&arg); + return *this; + } + /** + * Extract a value of type int. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream &operator>>(int& arg) { + getNumber(&arg); + return *this; + } + /** + * Extract a value of type unsigned int. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream &operator>>(unsigned int& arg) { + getNumber(&arg); + return *this; + } + /** + * Extract a value of type long. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream &operator>>(long& arg) { // NOLINT + getNumber(&arg); + return *this; + } + /** + * Extract a value of type unsigned long. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream &operator>>(unsigned long& arg) { // NOLINT + getNumber(&arg); + return *this; + } + /** + * Extract a value of type double. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream &operator>> (double& arg) { + getDouble(&arg); + return *this; + } + /** + * Extract a value of type float. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream &operator>> (float& arg) { + double v; + getDouble(&v); + arg = v; + return *this; + } + /** + * Extract a value of type void*. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>> (void*& arg) { + uint32_t val; + getNumber(&val); + arg = reinterpret_cast(val); + return *this; + } + /** + * \return The number of characters extracted by the last unformatted + * input function. + */ + streamsize gcount() const { + return m_gcount; + } + /** + * Extract a character if one is available. + * + * \return The character or -1 if a failure occurs. A failure is indicated + * by the stream state. + */ + int get(); + /** + * Extract a character if one is available. + * + * \param[out] ch location to receive the extracted character. + * + * \return always returns *this. A failure is indicated by the stream state. + */ + istream& get(char& ch); + /** + * Extract characters. + * + * \param[out] str Location to receive extracted characters. + * \param[in] n Size of str. + * \param[in] delim Delimiter + * + * Characters are extracted until extraction fails, n is less than 1, + * n-1 characters are extracted, or the next character equals + * \a delim (delim is not extracted). If no characters are extracted + * failbit is set. If end-of-file occurs the eofbit is set. + * + * \return always returns *this. A failure is indicated by the stream state. + */ + istream& get(char *str, streamsize n, char delim = '\n'); + /** + * Extract characters + * + * \param[out] str Location to receive extracted characters. + * \param[in] n Size of str. + * \param[in] delim Delimiter + * + * Characters are extracted until extraction fails, + * the next character equals \a delim (delim is extracted), or n-1 + * characters are extracted. + * + * The failbit is set if no characters are extracted or n-1 characters + * are extracted. If end-of-file occurs the eofbit is set. + * + * \return always returns *this. A failure is indicated by the stream state. + */ + istream& getline(char *str, streamsize n, char delim = '\n'); + /** + * Extract characters and discard them. + * + * \param[in] n maximum number of characters to ignore. + * \param[in] delim Delimiter. + * + * Characters are extracted until extraction fails, \a n characters + * are extracted, or the next input character equals \a delim + * (the delimiter is extracted). If end-of-file occurs the eofbit is set. + * + * Failures are indicated by the state of the stream. + * + * \return *this + * + */ + istream& ignore(streamsize n = 1, int delim = -1); + /** + * Return the next available character without consuming it. + * + * \return The character if the stream state is good else -1; + * + */ + int peek(); +// istream& read(char *str, streamsize count); +// streamsize readsome(char *str, streamsize count); + /** + * \return the stream position + */ + pos_type tellg() { + return tellpos(); + } + /** + * Set the stream position + * \param[in] pos The absolute position in which to move the read pointer. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& seekg(pos_type pos) { + if (!seekpos(pos)) { + setstate(failbit); + } + return *this; + } + /** + * Set the stream position. + * + * \param[in] off An offset to move the read pointer relative to way. + * \a off is a signed 32-bit int so the offset is limited to +- 2GB. + * \param[in] way One of ios::beg, ios::cur, or ios::end. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& seekg(off_type off, seekdir way) { + if (!seekoff(off, way)) { + setstate(failbit); + } + return *this; + } + void skipWhite(); + + protected: + /// @cond SHOW_PROTECTED + /** + * Internal - do not use + * \return + */ + virtual int16_t getch() = 0; + /** + * Internal - do not use + * \param[out] pos + * \return + */ + int16_t getch(FatPos_t* pos) { + getpos(pos); + return getch(); + } + /** + * Internal - do not use + * \param[out] pos + */ + virtual void getpos(FatPos_t* pos) = 0; + /** + * Internal - do not use + * \param[in] pos + */ + virtual bool seekoff(off_type off, seekdir way) = 0; + virtual bool seekpos(pos_type pos) = 0; + virtual void setpos(FatPos_t* pos) = 0; + virtual pos_type tellpos() = 0; + + /// @endcond + private: + void getBool(bool *b); + void getChar(char* ch); + bool getDouble(double* value); + template void getNumber(T* value); + bool getNumber(uint32_t posMax, uint32_t negMax, uint32_t* num); + void getStr(char *str); + int16_t readSkip(); + + size_t m_gcount; +}; +//------------------------------------------------------------------------------ +template +void istream::getNumber(T* value) { + uint32_t tmp; + if ((T)-1 < 0) { + // number is signed, max positive value + uint32_t const m = ((uint32_t)-1) >> (33 - sizeof(T) * 8); + // max absolute value of negative number is m + 1. + if (getNumber(m, m + 1, &tmp)) { + *value = (T)tmp; + } + } else { + // max unsigned value for T + uint32_t const m = (T)-1; + if (getNumber(m, m, &tmp)) { + *value = (T)tmp; + } + } +} +#endif // istream_h diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/ostream.cpp b/Firmware_V2/src/libraries/SdFat/FatLib/ostream.cpp new file mode 100644 index 0000000..230cda4 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/ostream.cpp @@ -0,0 +1,191 @@ +/* FatLib Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#include +#include "ostream.h" +#ifndef PSTR +#define PSTR(x) x +#endif +//------------------------------------------------------------------------------ +void ostream::do_fill(unsigned len) { + for (; len < width(); len++) { + putch(fill()); + } + width(0); +} +//------------------------------------------------------------------------------ +void ostream::fill_not_left(unsigned len) { + if ((flags() & adjustfield) != left) { + do_fill(len); + } +} +//------------------------------------------------------------------------------ +char* ostream::fmtNum(uint32_t n, char *ptr, uint8_t base) { + char a = flags() & uppercase ? 'A' - 10 : 'a' - 10; + do { + uint32_t m = n; + n /= base; + char c = m - base * n; + *--ptr = c < 10 ? c + '0' : c + a; + } while (n); + return ptr; +} +//------------------------------------------------------------------------------ +void ostream::putBool(bool b) { + if (flags() & boolalpha) { + if (b) { + putPgm(PSTR("true")); + } else { + putPgm(PSTR("false")); + } + } else { + putChar(b ? '1' : '0'); + } +} +//------------------------------------------------------------------------------ +void ostream::putChar(char c) { + fill_not_left(1); + putch(c); + do_fill(1); +} +//------------------------------------------------------------------------------ +void ostream::putDouble(double n) { + uint8_t nd = precision(); + double round = 0.5; + char sign; + char buf[13]; // room for sign, 10 digits, '.', and zero byte + char *end = buf + sizeof(buf) - 1; + char *str = end; + // terminate string + *end = '\0'; + + // get sign and make nonnegative + if (n < 0.0) { + sign = '-'; + n = -n; + } else { + sign = flags() & showpos ? '+' : '\0'; + } + // check for larger than uint32_t + if (n > 4.0E9) { + putPgm(PSTR("BIG FLT")); + return; + } + // round up and separate int and fraction parts + for (uint8_t i = 0; i < nd; ++i) { + round *= 0.1; + } + n += round; + uint32_t intPart = n; + double fractionPart = n - intPart; + + // format intPart and decimal point + if (nd || (flags() & showpoint)) { + *--str = '.'; + } + str = fmtNum(intPart, str, 10); + + // calculate length for fill + uint8_t len = sign ? 1 : 0; + len += nd + end - str; + + // extract adjust field + fmtflags adj = flags() & adjustfield; + if (adj == internal) { + if (sign) { + putch(sign); + } + do_fill(len); + } else { + // do fill for internal or right + fill_not_left(len); + if (sign) { + *--str = sign; + } + } + putstr(str); + // output fraction + while (nd-- > 0) { + fractionPart *= 10.0; + int digit = static_cast(fractionPart); + putch(digit + '0'); + fractionPart -= digit; + } + // do fill if not done above + do_fill(len); +} +//------------------------------------------------------------------------------ +void ostream::putNum(int32_t n) { + bool neg = n < 0 && flagsToBase() == 10; + if (neg) { + n = -n; + } + putNum(n, neg); +} +//------------------------------------------------------------------------------ +void ostream::putNum(uint32_t n, bool neg) { + char buf[13]; + char* end = buf + sizeof(buf) - 1; + char* num; + char* str; + uint8_t base = flagsToBase(); + *end = '\0'; + str = num = fmtNum(n, end, base); + if (base == 10) { + if (neg) { + *--str = '-'; + } else if (flags() & showpos) { + *--str = '+'; + } + } else if (flags() & showbase) { + if (flags() & hex) { + *--str = flags() & uppercase ? 'X' : 'x'; + } + *--str = '0'; + } + uint8_t len = end - str; + fmtflags adj = flags() & adjustfield; + if (adj == internal) { + while (str < num) { + putch(*str++); + } + } + if (adj != left) { + do_fill(len); + } + putstr(str); + do_fill(len); +} +//------------------------------------------------------------------------------ +void ostream::putPgm(const char* str) { + int n; + for (n = 0; pgm_read_byte(&str[n]); n++) {} + fill_not_left(n); + for (uint8_t c; (c = pgm_read_byte(str)); str++) { + putch(c); + } + do_fill(n); +} +//------------------------------------------------------------------------------ +void ostream::putStr(const char *str) { + unsigned n = strlen(str); + fill_not_left(n); + putstr(str); + do_fill(n); +} diff --git a/Firmware_V2/src/libraries/SdFat/FatLib/ostream.h b/Firmware_V2/src/libraries/SdFat/FatLib/ostream.h new file mode 100644 index 0000000..e7c9163 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FatLib/ostream.h @@ -0,0 +1,271 @@ +/* FatLib Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the FatLib Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the FatLib Library. If not, see + * . + */ +#ifndef ostream_h +#define ostream_h +/** + * \file + * \brief \ref ostream class + */ +#include "ios.h" +//============================================================================== +/** + * \class ostream + * \brief Output Stream + */ +class ostream : public virtual ios { + public: + ostream() {} + + /** call manipulator + * \param[in] pf function to call + * \return the stream + */ + ostream& operator<< (ostream& (*pf)(ostream& str)) { + return pf(*this); + } + /** call manipulator + * \param[in] pf function to call + * \return the stream + */ + ostream& operator<< (ios_base& (*pf)(ios_base& str)) { + pf(*this); + return *this; + } + /** Output bool + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<< (bool arg) { + putBool(arg); + return *this; + } + /** Output string + * \param[in] arg string to output + * \return the stream + */ + ostream &operator<< (const char *arg) { + putStr(arg); + return *this; + } + /** Output string + * \param[in] arg string to output + * \return the stream + */ + ostream &operator<< (const signed char *arg) { + putStr((const char*)arg); + return *this; + } + /** Output string + * \param[in] arg string to output + * \return the stream + */ + ostream &operator<< (const unsigned char *arg) { + putStr((const char*)arg); + return *this; + } + /** Output character + * \param[in] arg character to output + * \return the stream + */ + ostream &operator<< (char arg) { + putChar(arg); + return *this; + } + /** Output character + * \param[in] arg character to output + * \return the stream + */ + ostream &operator<< (signed char arg) { + putChar(static_cast(arg)); + return *this; + } + /** Output character + * \param[in] arg character to output + * \return the stream + */ + ostream &operator<< (unsigned char arg) { + putChar(static_cast(arg)); + return *this; + } + /** Output double + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<< (double arg) { + putDouble(arg); + return *this; + } + /** Output float + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<< (float arg) { + putDouble(arg); + return *this; + } + /** Output signed short + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<< (short arg) { // NOLINT + putNum((int32_t)arg); + return *this; + } + /** Output unsigned short + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<< (unsigned short arg) { // NOLINT + putNum((uint32_t)arg); + return *this; + } + /** Output signed int + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<< (int arg) { + putNum((int32_t)arg); + return *this; + } + /** Output unsigned int + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<< (unsigned int arg) { + putNum((uint32_t)arg); + return *this; + } + /** Output signed long + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<< (long arg) { // NOLINT + putNum((int32_t)arg); + return *this; + } + /** Output unsigned long + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<< (unsigned long arg) { // NOLINT + putNum((uint32_t)arg); + return *this; + } + /** Output pointer + * \param[in] arg value to output + * \return the stream + */ + ostream& operator<< (const void* arg) { + putNum(reinterpret_cast(arg)); + return *this; + } +#if (defined(ARDUINO) && ENABLE_ARDUINO_FEATURES) || defined(DOXYGEN) + /** Output a string from flash using the Arduino F() macro. + * \param[in] arg pointing to flash string + * \return the stream + */ + ostream &operator<< (const __FlashStringHelper *arg) { + putPgm(reinterpret_cast(arg)); + return *this; + } +#endif // (defined(ARDUINO) && ENABLE_ARDUINO_FEATURES) || defined(DOXYGEN) + /** + * Puts a character in a stream. + * + * The unformatted output function inserts the element \a ch. + * It returns *this. + * + * \param[in] ch The character + * \return A reference to the ostream object. + */ + ostream& put(char ch) { + putch(ch); + return *this; + } +// ostream& write(char *str, streamsize count); + /** + * Flushes the buffer associated with this stream. The flush function + * calls the sync function of the associated file. + * \return A reference to the ostream object. + */ + ostream& flush() { + if (!sync()) { + setstate(badbit); + } + return *this; + } + /** + * \return the stream position + */ + pos_type tellp() { + return tellpos(); + } + /** + * Set the stream position + * \param[in] pos The absolute position in which to move the write pointer. + * \return Is always *this. Failure is indicated by the state of *this. + */ + ostream& seekp(pos_type pos) { + if (!seekpos(pos)) { + setstate(failbit); + } + return *this; + } + /** + * Set the stream position. + * + * \param[in] off An offset to move the write pointer relative to way. + * \a off is a signed 32-bit int so the offset is limited to +- 2GB. + * \param[in] way One of ios::beg, ios::cur, or ios::end. + * \return Is always *this. Failure is indicated by the state of *this. + */ + ostream& seekp(off_type off, seekdir way) { + if (!seekoff(off, way)) { + setstate(failbit); + } + return *this; + } + + protected: + /// @cond SHOW_PROTECTED + /** Put character with binary/text conversion + * \param[in] ch character to write + */ + virtual void putch(char ch) = 0; + virtual void putstr(const char *str) = 0; + virtual bool seekoff(off_type pos, seekdir way) = 0; + virtual bool seekpos(pos_type pos) = 0; + virtual bool sync() = 0; + + virtual pos_type tellpos() = 0; + /// @endcond + private: + void do_fill(unsigned len); + void fill_not_left(unsigned len); + char* fmtNum(uint32_t n, char *ptr, uint8_t base); + void putBool(bool b); + void putChar(char c); + void putDouble(double n); + void putNum(uint32_t n, bool neg = false); + void putNum(int32_t n); + void putPgm(const char* str); + void putStr(const char* str); +}; +#endif // ostream_h diff --git a/Firmware_V2/src/libraries/SdFat/FreeStack.h b/Firmware_V2/src/libraries/SdFat/FreeStack.h new file mode 100644 index 0000000..fd8e4cd --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/FreeStack.h @@ -0,0 +1,54 @@ +/* Arduino SdFat Library + * Copyright (C) 2015 by William Greiman + * + * This file is part of the Arduino SdFat Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with the Arduino SdFat Library. If not, see + * . + */ +#ifndef FreeStack_h +#define FreeStack_h +/** + * \file + * \brief FreeStack() function. + */ +#if defined(__AVR__) || defined(DOXYGEN) +/** boundary between stack and heap. */ +extern char *__brkval; +/** End of bss section.*/ +extern char __bss_end; +/** Amount of free stack space. + * \return The number of free bytes. + */ +static int FreeStack() { + char top; + return __brkval ? &top - __brkval : &top - &__bss_end; +} +#elif defined(PLATFORM_ID) // Particle board +static int FreeStack() { + return System.freeMemory(); +} +#elif defined(__arm__) +extern "C" char* sbrk(int incr); +static int FreeStack() { + char top; + return &top - reinterpret_cast(sbrk(0)); +} +#else +#warning FreeStack is not defined for this system. +static int FreeStack() { + return 0; +} +#endif +#endif // FreeStack_h diff --git a/Firmware_V2/src/libraries/SdFat/MinimumSerial.cpp b/Firmware_V2/src/libraries/SdFat/MinimumSerial.cpp new file mode 100644 index 0000000..909f143 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/MinimumSerial.cpp @@ -0,0 +1,58 @@ +/* Arduino SdFat Library + * Copyright (C) 2012 by William Greiman + * + * This file is part of the Arduino SdFat Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino SdFat Library. If not, see + * . + */ +#include "SystemInclude.h" +#if defined(UDR0) || defined(DOXYGEN) +#include "MinimumSerial.h" +const uint16_t MIN_2X_BAUD = F_CPU/(4*(2*0XFFF + 1)) + 1; +//------------------------------------------------------------------------------ +void MinimumSerial::begin(uint32_t baud) { + uint16_t baud_setting; + // don't worry, the compiler will squeeze out F_CPU != 16000000UL + if ((F_CPU != 16000000UL || baud != 57600) && baud > MIN_2X_BAUD) { + // Double the USART Transmission Speed + UCSR0A = 1 << U2X0; + baud_setting = (F_CPU / 4 / baud - 1) / 2; + } else { + // hardcoded exception for compatibility with the bootloader shipped + // with the Duemilanove and previous boards and the firmware on the 8U2 + // on the Uno and Mega 2560. + UCSR0A = 0; + baud_setting = (F_CPU / 8 / baud - 1) / 2; + } + // assign the baud_setting + UBRR0H = baud_setting >> 8; + UBRR0L = baud_setting; + // enable transmit and receive + UCSR0B |= (1 << TXEN0) | (1 << RXEN0); +} +//------------------------------------------------------------------------------ +int MinimumSerial::read() { + if (UCSR0A & (1 << RXC0)) { + return UDR0; + } + return -1; +} +//------------------------------------------------------------------------------ +size_t MinimumSerial::write(uint8_t b) { + while (((1 << UDRIE0) & UCSR0B) || !(UCSR0A & (1 << UDRE0))) {} + UDR0 = b; + return 1; +} +#endif // defined(UDR0) || defined(DOXYGEN) diff --git a/Firmware_V2/src/libraries/SdFat/MinimumSerial.h b/Firmware_V2/src/libraries/SdFat/MinimumSerial.h new file mode 100644 index 0000000..86b2c46 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/MinimumSerial.h @@ -0,0 +1,50 @@ +/* Arduino SdFat Library + * Copyright (C) 2012 by William Greiman + * + * This file is part of the Arduino SdFat Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino SdFat Library. If not, see + * . + */ +#ifndef MinimumSerial_h +#define MinimumSerial_h +#include "SystemInclude.h" +//============================================================================== +/** + * \class MinimumSerial + * \brief mini serial class for the %SdFat library. + */ +class MinimumSerial : public Print { + public: + /** + * Set baud rate for serial port zero and enable in non interrupt mode. + * Do not call this function if you use another serial library. + * \param[in] baud rate + */ + void begin(uint32_t baud); + /** + * Unbuffered read + * \return -1 if no character is available or an available character. + */ + int read(); + /** + * Unbuffered write + * + * \param[in] b byte to write. + * \return 1 + */ + size_t write(uint8_t b); + using Print::write; +}; +#endif // MinimumSerial_h diff --git a/Firmware_V2/src/libraries/SdFat/SdFat.cpp b/Firmware_V2/src/libraries/SdFat/SdFat.cpp new file mode 100644 index 0000000..7b962ad --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdFat.cpp @@ -0,0 +1,100 @@ +/* Arduino SdFat Library + * Copyright (C) 2012 by William Greiman + * + * This file is part of the Arduino SdFat Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino SdFat Library. If not, see + * . + */ +#include "SdFat.h" +//------------------------------------------------------------------------------ +void SdFatBase::errorHalt(Print* pr) { + errorPrint(pr); + SysCall::halt(); +} +//------------------------------------------------------------------------------ +void SdFatBase::errorHalt(Print* pr, char const* msg) { + errorPrint(pr, msg); + SysCall::halt(); +} +//------------------------------------------------------------------------------ +void SdFatBase::errorPrint(Print* pr) { + if (!cardErrorCode()) { + return; + } + pr->print(F("SD errorCode: 0X")); + pr->print(cardErrorCode(), HEX); + pr->print(F(",0X")); + pr->println(cardErrorData(), HEX); +} +//------------------------------------------------------------------------------ +void SdFatBase::errorPrint(Print* pr, char const* msg) { + pr->print(F("error: ")); + pr->println(msg); + errorPrint(pr); +} +//------------------------------------------------------------------------------ +void SdFatBase::initErrorHalt(Print* pr) { + initErrorPrint(pr); + SysCall::halt(); +} +//------------------------------------------------------------------------------ +void SdFatBase::initErrorHalt(Print* pr, char const *msg) { + pr->println(msg); + initErrorHalt(pr); +} +//------------------------------------------------------------------------------ +void SdFatBase::initErrorPrint(Print* pr) { + if (cardErrorCode()) { + pr->println(F("Can't access SD card. Do not reformat.")); + if (cardErrorCode() == SD_CARD_ERROR_CMD0) { + pr->println(F("No card, wrong chip select pin, or SPI problem?")); + } + errorPrint(pr); + } else if (vol()->fatType() == 0) { + pr->println(F("Invalid format, reformat SD.")); + } else if (!vwd()->isOpen()) { + pr->println(F("Can't open root directory.")); + } else { + pr->println(F("No error found.")); + } +} +//------------------------------------------------------------------------------ +void SdFatBase::initErrorPrint(Print* pr, char const *msg) { + pr->println(msg); + initErrorPrint(pr); +} +#if defined(ARDUINO) || defined(DOXYGEN) +//------------------------------------------------------------------------------ +void SdFatBase::errorPrint(Print* pr, const __FlashStringHelper* msg) { + pr->print(F("error: ")); + pr->println(msg); + errorPrint(pr); +} +//------------------------------------------------------------------------------ +void SdFatBase::errorHalt(Print* pr, const __FlashStringHelper* msg) { + errorPrint(pr, msg); + SysCall::halt(); +} +//------------------------------------------------------------------------------ +void SdFatBase::initErrorHalt(Print* pr, const __FlashStringHelper* msg) { + pr->println(msg); + initErrorHalt(pr); +} +//------------------------------------------------------------------------------ +void SdFatBase::initErrorPrint(Print* pr, const __FlashStringHelper* msg) { + pr->println(msg); + initErrorPrint(pr); +} +#endif // defined(ARDUINO) || defined(DOXYGEN) diff --git a/Firmware_V2/src/libraries/SdFat/SdFat.h b/Firmware_V2/src/libraries/SdFat/SdFat.h new file mode 100644 index 0000000..40a9b25 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdFat.h @@ -0,0 +1,360 @@ +/* Arduino SdFat Library + * Copyright (C) 2012 by William Greiman + * + * This file is part of the Arduino SdFat Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino SdFat Library. If not, see + * . + */ +#ifndef SdFat_h +#define SdFat_h +/** + * \file + * \brief SdFat class + */ +#ifdef ARDUINO +#include "SdSpiCard/SdSpiCard.h" +#include "FatLib/FatLib.h" +#else // ARDUINO +#include "SdSpiCard.h" +#include "FatLib.h" +#endif // ARDUINO +//------------------------------------------------------------------------------ +/** SdFat version YYYYMMDD */ +#define SD_FAT_VERSION 20160719 +//============================================================================== +/** + * \class SdBaseFile + * \brief Class for backward compatibility. + */ +class SdBaseFile : public FatFile { + public: + SdBaseFile() {} + /** Create a file object and open it in the current working directory. + * + * \param[in] path A path for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a + * bitwise-inclusive OR of open flags. see + * FatFile::open(FatFile*, const char*, uint8_t). + */ + SdBaseFile(const char* path, uint8_t oflag) : FatFile(path, oflag) {} +}; +#if ENABLE_ARDUINO_FEATURES +/** + * \class SdFile + * \brief Class for backward compatibility. + */ +class SdFile : public PrintFile { + public: + SdFile() {} + /** Create a file object and open it in the current working directory. + * + * \param[in] path A path for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a + * bitwise-inclusive OR of open flags. see + * FatFile::open(FatFile*, const char*, uint8_t). + */ + SdFile(const char* path, uint8_t oflag) : PrintFile(path, oflag) {} +}; +#endif // #if ENABLE_ARDUINO_FEATURES +/** + * \class SdFatBase + * \brief Virtual base class for %SdFat library. + */ +class SdFatBase : public FatFileSystem { + public: + /** Initialize SD card and file system. + * \param[in] spi SPI object for the card. + * \param[in] csPin SD card chip select pin. + * \param[in] divisor SPI divisor. + * \return true for success else false. + */ + bool begin(SdSpiCard::m_spi_t* spi, uint8_t csPin = SS, uint8_t divisor = 2) { + return m_sdCard.begin(spi, csPin, divisor) && + FatFileSystem::begin(); + } + /** \return Pointer to SD card object */ + SdSpiCard *card() { + return &m_sdCard; + } + /** %Print any SD error code to Serial and halt. */ + void errorHalt() { + errorHalt(&Serial); + } + /** %Print any SD error code and halt. + * + * \param[in] pr Print destination. + */ + void errorHalt(Print* pr); + /** %Print msg, any SD error code and halt. + * + * \param[in] msg Message to print. + */ + void errorHalt(char const* msg) { + errorHalt(&Serial, msg); + } + /** %Print msg, any SD error code, and halt. + * + * \param[in] pr Print destination. + * \param[in] msg Message to print. + */ + void errorHalt(Print* pr, char const* msg); + + /** %Print any SD error code to Serial */ + void errorPrint() { + errorPrint(&Serial); + } + /** %Print any SD error code. + * \param[in] pr Print device. + */ + void errorPrint(Print* pr); + /** %Print msg, any SD error code. + * + * \param[in] msg Message to print. + */ + void errorPrint(const char* msg) { + errorPrint(&Serial, msg); + } + /** %Print msg, any SD error code. + * + * \param[in] pr Print destination. + * \param[in] msg Message to print. + */ + void errorPrint(Print* pr, char const* msg); + + /** Diagnostic call to initialize FatFileSystem - use for + * diagnostic purposes only. + * \return true for success else false. + */ + bool fsBegin() { + return FatFileSystem::begin(); + } + /** %Print any SD error code and halt. */ + void initErrorHalt() { + initErrorHalt(&Serial); + } + /** %Print error details and halt after begin fails. + * + * \param[in] pr Print destination. + */ + void initErrorHalt(Print* pr); + /**Print message, error details, and halt after SdFat::init() fails. + * + * \param[in] msg Message to print. + */ + void initErrorHalt(char const *msg) { + initErrorHalt(&Serial, msg); + } + /**Print message, error details, and halt after SdFatBase::init() fails. + * \param[in] pr Print device. + * \param[in] msg Message to print. + */ + void initErrorHalt(Print* pr, char const *msg); + + /** Print error details after SdFat::init() fails. */ + void initErrorPrint() { + initErrorPrint(&Serial); + } + /** Print error details after SdFatBase::init() fails. + * + * \param[in] pr Print destination. + */ + void initErrorPrint(Print* pr); + /**Print message and error details and halt after SdFat::init() fails. + * + * \param[in] msg Message to print. + */ + void initErrorPrint(char const *msg) { + initErrorPrint(&Serial, msg); + } + /**Print message and error details and halt after SdFatBase::init() fails. + * + * \param[in] pr Print destination. + * \param[in] msg Message to print. + */ + void initErrorPrint(Print* pr, char const *msg); +#if defined(ARDUINO) || defined(DOXYGEN) + /** %Print msg, any SD error code, and halt. + * + * \param[in] msg Message to print. + */ + void errorHalt(const __FlashStringHelper* msg) { + errorHalt(&Serial, msg); + } + /** %Print msg, any SD error code, and halt. + * + * \param[in] pr Print destination. + * \param[in] msg Message to print. + */ + void errorHalt(Print* pr, const __FlashStringHelper* msg); + /** %Print msg, any SD error code. + * + * \param[in] msg Message to print. + */ + void errorPrint(const __FlashStringHelper* msg) { + errorPrint(&Serial, msg); + } + /** %Print msg, any SD error code. + * + * \param[in] pr Print destination. + * \param[in] msg Message to print. + */ + void errorPrint(Print* pr, const __FlashStringHelper* msg); + /**Print message, error details, and halt after SdFat::init() fails. + * + * \param[in] msg Message to print. + */ + void initErrorHalt(const __FlashStringHelper* msg) { + initErrorHalt(&Serial, msg); + } + /**Print message, error details, and halt after SdFatBase::init() fails. + * \param[in] pr Print device for message. + * \param[in] msg Message to print. + */ + void initErrorHalt(Print* pr, const __FlashStringHelper* msg); + /**Print message and error details and halt after SdFat::init() fails. + * + * \param[in] msg Message to print. + */ + void initErrorPrint(const __FlashStringHelper* msg) { + initErrorPrint(&Serial, msg); + } + /**Print message and error details and halt after SdFatBase::init() fails. + * + * \param[in] pr Print destination. + * \param[in] msg Message to print. + */ + void initErrorPrint(Print* pr, const __FlashStringHelper* msg); +#endif // defined(ARDUINO) || defined(DOXYGEN) + + private: + uint8_t cardErrorCode() { + return m_sdCard.errorCode(); + } + uint8_t cardErrorData() { + return m_sdCard.errorData(); + } + bool readBlock(uint32_t block, uint8_t* dst) { + return m_sdCard.readBlock(block, dst); + } + bool writeBlock(uint32_t block, const uint8_t* src) { + return m_sdCard.writeBlock(block, src); + } + bool readBlocks(uint32_t block, uint8_t* dst, size_t n) { + return m_sdCard.readBlocks(block, dst, n); + } + bool writeBlocks(uint32_t block, const uint8_t* src, size_t n) { + return m_sdCard.writeBlocks(block, src, n); + } + SdSpiCard m_sdCard; +}; +//============================================================================== +/** + * \class SdFat + * \brief Main file system class for %SdFat library. + */ +class SdFat : public SdFatBase { + public: +#if IMPLEMENT_SPI_INTERFACE_SELECTION + SdFat() { + m_spi.setSpiIf(0); + } + explicit SdFat(uint8_t spiIf) { + m_spi.setSpiIf(spiIf < SPI_INTERFACE_COUNT ? spiIf : 0); + } +#endif // IMPLEMENT_SPI_INTERFACE_SELECTION + + /** Initialize SD card and file system. + * + * \param[in] csPin SD card chip select pin. + * \param[in] divisor SPI divisor. + * \return true for success else false. + */ + bool begin(uint8_t csPin = SS, uint8_t divisor = 2) { + return SdFatBase::begin(&m_spi, csPin, divisor); + } + /** Diagnostic call to initialize SD card - use for diagnostic purposes only. + * \param[in] csPin SD card chip select pin. + * \param[in] divisor SPI divisor. + * \return true for success else false. + */ + bool cardBegin(uint8_t csPin = SS, uint8_t divisor = 2) { + return card()->begin(&m_spi, csPin, divisor); + } + + private: + SpiDefault_t m_spi; +}; +//============================================================================== +#if SD_SPI_CONFIGURATION >= 3 || defined(DOXYGEN) +/** + * \class SdFatLibSpi + * \brief SdFat class using the standard Arduino SPI library. + */ +class SdFatLibSpi: public SdFatBase { + public: + /** Initialize SD card and file system. + * + * \param[in] csPin SD card chip select pin. + * \param[in] divisor SPI divisor. + * \return true for success else false. + */ + bool begin(uint8_t csPin = SS, uint8_t divisor = 2) { + return SdFatBase::begin(&m_spi, csPin, divisor); + } + /** Diagnostic call to initialize SD card - use for diagnostic purposes only. + * \param[in] csPin SD card chip select pin. + * \param[in] divisor SPI divisor. + * \return true for success else false. + */ + bool cardBegin(uint8_t csPin = SS, uint8_t divisor = 2) { + return card()->begin(&m_spi, csPin, divisor); + } + + private: + SdSpiLib m_spi; +}; +//============================================================================== +/** + * \class SdFatSoftSpi + * \brief SdFat class using software SPI. + */ +template +class SdFatSoftSpi : public SdFatBase { + public: + /** Initialize SD card and file system. + * + * \param[in] csPin SD card chip select pin. + * \param[in] divisor SPI divisor. + * \return true for success else false. + */ + bool begin(uint8_t csPin = SS, uint8_t divisor = 2) { + return SdFatBase::begin(&m_spi, csPin, divisor); + } + /** Diagnostic call to initialize SD card - use for diagnostic purposes only. + * \param[in] csPin SD card chip select pin. + * \param[in] divisor SPI divisor. + * \return true for success else false. + */ + bool cardBegin(uint8_t csPin = SS, uint8_t divisor = 2) { + return card()->begin(&m_spi, csPin, divisor); + } + + private: + SdSpiSoft m_spi; +}; +#endif /// SD_SPI_CONFIGURATION >= 3 || defined(DOXYGEN) +#endif // SdFat_h diff --git a/Firmware_V2/src/libraries/SdFat/SdFatConfig.h b/Firmware_V2/src/libraries/SdFat/SdFatConfig.h new file mode 100644 index 0000000..ef85091 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdFatConfig.h @@ -0,0 +1,240 @@ +/* Arduino SdFat Library + * Copyright (C) 2012 by William Greiman + * + * This file is part of the Arduino SdFat Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino SdFat Library. If not, see + * . + */ +/** + * \file + * \brief configuration definitions + */ +#ifndef SdFatConfig_h +#define SdFatConfig_h +#include +#ifdef __AVR__ +#include +#endif // __AVR__ +//------------------------------------------------------------------------------ +/** + * Set USE_LONG_FILE_NAMES nonzero to use long file names (LFN). + * Long File Name are limited to a maximum length of 255 characters. + * + * This implementation allows 7-bit characters in the range + * 0X20 to 0X7E except the following characters are not allowed: + * + * < (less than) + * > (greater than) + * : (colon) + * " (double quote) + * / (forward slash) + * \ (backslash) + * | (vertical bar or pipe) + * ? (question mark) + * * (asterisk) + * + */ +#define USE_LONG_FILE_NAMES 1 +//------------------------------------------------------------------------------ +/** + * Set ARDUINO_FILE_USES_STREAM nonzero to use Stream as the base class + * for the Arduino File class. If ARDUINO_FILE_USES_STREAM is zero, Print + * will be used as the base class for the Arduino File class. + * + * You can save some flash if you do not use Stream input functions such as + * find(), findUntil(), readBytesUntil(), readString(), readStringUntil(), + * parseInt(), and parseFloat(). + */ +#define ARDUINO_FILE_USES_STREAM 0 +//------------------------------------------------------------------------------ +/** + * The symbol SD_SPI_CONFIGURATION defines SPI access to the SD card. + * + * IF SD_SPI_CONFIGUTATION is define to be zero, only the SdFat class + * is define and SdFat uses a fast custom SPI implementation if avaiable. + * If SD_HAS_CUSTOM_SPI is zero, the standard SPI library is used. + * + * If SD_SPI_CONFIGURATION is define to be one, only the SdFat class is + * define and SdFat uses the standard Arduino SPI.h library. + * + * If SD_SPI_CONFIGURATION is define to be two, only the SdFat class is + * define and SdFat uses software SPI on the pins defined below. + * + * If SD_SPI_CONFIGURATION is define to be three, the three classes, SdFat, + * SdFatLibSpi, and SdFatSoftSpi are defined. SdFat uses the fast + * custom SPI implementation. SdFatLibSpi uses the standard Arduino SPI + * library. SdFatSoftSpi is a template class that uses Software SPI. The + * template parameters define the software SPI pins. See the ThreeCard + * example for simultaneous use of all three classes. + */ +#define SD_SPI_CONFIGURATION 1 +//------------------------------------------------------------------------------ +/** + * If SD_SPI_CONFIGURATION is defined to be two, these definitions + * will define the pins used for software SPI. + * + * The default definition allows Uno shields to be used on other boards. + */ +/** Software SPI Master Out Slave In pin */ +uint8_t const SOFT_SPI_MOSI_PIN = 11; +/** Software SPI Master In Slave Out pin */ +uint8_t const SOFT_SPI_MISO_PIN = 12; +/** Software SPI Clock pin */ +uint8_t const SOFT_SPI_SCK_PIN = 13; +//------------------------------------------------------------------------------ +/** + * Set MAINTAIN_FREE_CLUSTER_COUNT nonzero to keep the count of free clusters + * updated. This will increase the speed of the freeClusterCount() call + * after the first call. Extra flash will be required. + */ +#define MAINTAIN_FREE_CLUSTER_COUNT 0 +//------------------------------------------------------------------------------ +/** + * To enable SD card CRC checking set USE_SD_CRC nonzero. + * + * Set USE_SD_CRC to 1 to use a smaller CRC-CCITT function. This function + * is slower for AVR but may be fast for ARM and other processors. + * + * Set USE_SD_CRC to 2 to used a larger table driven CRC-CCITT function. This + * function is faster for AVR but may be slower for ARM and other processors. + */ +#define USE_SD_CRC 0 +//------------------------------------------------------------------------------ +/** + * Set ENABLE_SPI_TRANSACTIONS nonzero to enable the SPI transaction feature + * of the standard Arduino SPI library. You must include SPI.h in your + * programs when ENABLE_SPI_TRANSACTIONS is nonzero. + */ +#define ENABLE_SPI_TRANSACTIONS 1 +//------------------------------------------------------------------------------ +/** + * Handle Watchdog Timer for WiFi modules. + * + * Yield will be called before accessing the SPI bus if it has been more + * than WDT_YIELD_TIME_MICROS microseconds since the last yield call by SdFat. + */ +#if defined(PLATFORM_ID) || defined(ESP8266) +// If Particle device or ESP8266 call yield. +#define WDT_YIELD_TIME_MICROS 100000 +#else +#define WDT_YIELD_TIME_MICROS 0 +#endif +//------------------------------------------------------------------------------ +/** + * Set FAT12_SUPPORT nonzero to enable use if FAT12 volumes. + * FAT12 has not been well tested and requires additional flash. + */ +#define FAT12_SUPPORT 0 +//------------------------------------------------------------------------------ +/** + * Set DESTRUCTOR_CLOSES_FILE nonzero to close a file in its destructor. + * + * Causes use of lots of heap in ARM. + */ +#define DESTRUCTOR_CLOSES_FILE 0 +//------------------------------------------------------------------------------ +/** + * Call flush for endl if ENDL_CALLS_FLUSH is nonzero + * + * The standard for iostreams is to call flush. This is very costly for + * SdFat. Each call to flush causes 2048 bytes of I/O to the SD. + * + * SdFat has a single 512 byte buffer for SD I/O so it must write the current + * data block to the SD, read the directory block from the SD, update the + * directory entry, write the directory block to the SD and read the data + * block back into the buffer. + * + * The SD flash memory controller is not designed for this many rewrites + * so performance may be reduced by more than a factor of 100. + * + * If ENDL_CALLS_FLUSH is zero, you must call flush and/or close to force + * all data to be written to the SD. + */ +#define ENDL_CALLS_FLUSH 0 +//------------------------------------------------------------------------------ +/** + * SPI SCK divisor for SD initialization commands. + * or greater + */ +#ifdef __AVR__ +const uint8_t SPI_SCK_INIT_DIVISOR = 64; +#else +const uint8_t SPI_SCK_INIT_DIVISOR = 128; +#endif +//------------------------------------------------------------------------------ +/** + * Set USE_SEPARATE_FAT_CACHE nonzero to use a second 512 byte cache + * for FAT table entries. This improves performance for large writes + * that are not a multiple of 512 bytes. + */ +#ifdef __arm__ +#define USE_SEPARATE_FAT_CACHE 1 +#else // __arm__ +#define USE_SEPARATE_FAT_CACHE 0 +#endif // __arm__ +//------------------------------------------------------------------------------ +/** + * Set USE_MULTI_BLOCK_IO nonzero to use multi-block SD read/write. + * + * Don't use mult-block read/write on small AVR boards. + */ +#if defined(RAMEND) && RAMEND < 3000 +#define USE_MULTI_BLOCK_IO 0 +#else // RAMEND +#define USE_MULTI_BLOCK_IO 1 +#endif // RAMEND +//------------------------------------------------------------------------------ +/** + * Determine the default SPI configuration. + */ +#if defined(__AVR__)\ + || defined(__SAM3X8E__) || defined(__SAM3X8H__)\ + || (defined(__arm__) && defined(CORE_TEENSY))\ + || defined(__STM32F1__)\ + || defined(PLATFORM_ID)\ + || defined(ESP8266)\ + || defined(DOXYGEN) +// Use custom fast implementation. +#define SD_HAS_CUSTOM_SPI 1 +#else // SD_HAS_CUSTOM_SPI +// Use standard SPI library. +#define SD_HAS_CUSTOM_SPI 0 +#endif // SD_HAS_CUSTOM_SPI +//----------------------------------------------------------------------------- +/** + * Number of hardware interfaces. + */ +#if defined(PLATFORM_ID) +#if Wiring_SPI1 && Wiring_SPI2 +#define SPI_INTERFACE_COUNT 3 +#elif Wiring_SPI1 +#define SPI_INTERFACE_COUNT 2 +#endif // Wiring_SPI1 && Wiring_SPI2 +#endif // defined(PLATFORM_ID) +// default is one +#ifndef SPI_INTERFACE_COUNT +#define SPI_INTERFACE_COUNT 1 +#endif // SPI_INTERFACE_COUNT +//------------------------------------------------------------------------------ +/** + * Check if API to select HW SPI interface is needed. + */ +#if SPI_INTERFACE_COUNT > 1 && SD_HAS_CUSTOM_SPI\ + && SD_SPI_CONFIGURATION != 1 && SD_SPI_CONFIGURATION != 2 +#define IMPLEMENT_SPI_INTERFACE_SELECTION 1 +#else // SPI_INTERFACE_COUNT > 1 +#define IMPLEMENT_SPI_INTERFACE_SELECTION 0 +#endif // SPI_INTERFACE_COUNT > 1 +#endif // SdFatConfig_h diff --git a/Firmware_V2/src/libraries/SdFat/SdFatUtil.cpp b/Firmware_V2/src/libraries/SdFat/SdFatUtil.cpp new file mode 100644 index 0000000..83cf0f7 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdFatUtil.cpp @@ -0,0 +1,41 @@ +/* Arduino SdFat Library + * Copyright (C) 2015 by William Greiman + * + * This file is part of the Arduino SdFat Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with the Arduino SdFat Library. If not, see + * . + */ +#include +#include "SdFat.h" +#include "SdFatUtil.h" +//------------------------------------------------------------------------------ +#ifdef __arm__ +extern "C" char* sbrk(int incr); +int SdFatUtil::FreeRam() { + char top; + return &top - reinterpret_cast(sbrk(0)); +} +#else // __arm__ +extern char *__brkval; +extern char __bss_end; +/** Amount of free RAM + * \return The number of free bytes. + */ +int SdFatUtil::FreeRam() { + char top; + return __brkval ? &top - __brkval : &top - &__bss_end; +} +#endif // __arm + diff --git a/Firmware_V2/src/libraries/SdFat/SdFatUtil.h b/Firmware_V2/src/libraries/SdFat/SdFatUtil.h new file mode 100644 index 0000000..ee36bfd --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdFatUtil.h @@ -0,0 +1,35 @@ +/* Arduino SdFat Library + * Copyright (C) 2012 by William Greiman + * + * This file is part of the Arduino SdFat Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino SdFat Library. If not, see + * . + */ +#ifndef SdFatUtil_h +#define SdFatUtil_h +/** + * \file + * \brief Useful utility functions. + */ +#include "SdFat.h" + +namespace SdFatUtil { + /** Amount of free RAM + * \return The number of free bytes. + */ + int FreeRam(); +} // namespace SdFatUtil +using namespace SdFatUtil; // NOLINT +#endif // #define SdFatUtil_h diff --git a/Firmware_V2/src/libraries/SdFat/SdSpiCard/DigitalPin.h b/Firmware_V2/src/libraries/SdFat/SdSpiCard/DigitalPin.h new file mode 100644 index 0000000..993dc7a --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdSpiCard/DigitalPin.h @@ -0,0 +1,378 @@ +/* Arduino DigitalIO Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the Arduino DigitalIO Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino DigitalIO Library. If not, see + * . + */ +/** + * @file + * @brief Fast Digital Pin functions + * + * @defgroup digitalPin Fast Pin I/O + * @details Fast Digital I/O functions and template class. + * @{ + */ +#ifndef DigitalPin_h +#define DigitalPin_h +#include "SystemInclude.h" +#if defined(__AVR__) || defined(DOXYGEN) +#include +/** GpioPinMap type */ +struct GpioPinMap_t { + volatile uint8_t* pin; /**< address of PIN for this pin */ + volatile uint8_t* ddr; /**< address of DDR for this pin */ + volatile uint8_t* port; /**< address of PORT for this pin */ + uint8_t mask; /**< bit mask for this pin */ +}; + +/** Initializer macro. */ +#define GPIO_PIN(reg, bit) {&PIN##reg, &DDR##reg, &PORT##reg, 1 << bit} + +// Include pin map for current board. +#include "boards/GpioPinMap.h" +//------------------------------------------------------------------------------ +/** generate bad pin number error */ +void badPinNumber(void) + __attribute__((error("Pin number is too large or not a constant"))); +//------------------------------------------------------------------------------ +/** Check for valid pin number + * @param[in] pin Number of pin to be checked. + */ +static inline __attribute__((always_inline)) +void badPinCheck(uint8_t pin) { + if (!__builtin_constant_p(pin) || pin >= NUM_DIGITAL_PINS) { + badPinNumber(); + } +} +//------------------------------------------------------------------------------ +/** DDR register address + * @param[in] pin Arduino pin number + * @return register address + */ +static inline __attribute__((always_inline)) +volatile uint8_t* ddrReg(uint8_t pin) { + badPinCheck(pin); + return GpioPinMap[pin].ddr; +} +//------------------------------------------------------------------------------ +/** Bit mask for pin + * @param[in] pin Arduino pin number + * @return mask + */ +static inline __attribute__((always_inline)) +uint8_t pinMask(uint8_t pin) { + badPinCheck(pin); + return GpioPinMap[pin].mask; +} +//------------------------------------------------------------------------------ +/** PIN register address + * @param[in] pin Arduino pin number + * @return register address + */ +static inline __attribute__((always_inline)) +volatile uint8_t* pinReg(uint8_t pin) { + badPinCheck(pin); + return GpioPinMap[pin].pin; +} +//------------------------------------------------------------------------------ +/** PORT register address + * @param[in] pin Arduino pin number + * @return register address + */ +static inline __attribute__((always_inline)) +volatile uint8_t* portReg(uint8_t pin) { + badPinCheck(pin); + return GpioPinMap[pin].port; +} +//------------------------------------------------------------------------------ +/** Fast write helper. + * @param[in] address I/O register address + * @param[in] mask bit mask for pin + * @param[in] level value for bit + */ +static inline __attribute__((always_inline)) +void fastBitWriteSafe(volatile uint8_t* address, uint8_t mask, bool level) { + uint8_t s; + if (address > reinterpret_cast(0X3F)) { + s = SREG; + cli(); + } + if (level) { + *address |= mask; + } else { + *address &= ~mask; + } + if (address > reinterpret_cast(0X3F)) { + SREG = s; + } +} +//------------------------------------------------------------------------------ +/** Read pin value. + * @param[in] pin Arduino pin number + * @return value read + */ +static inline __attribute__((always_inline)) +bool fastDigitalRead(uint8_t pin) { + return *pinReg(pin) & pinMask(pin); +} +//------------------------------------------------------------------------------ +/** Toggle a pin. + * @param[in] pin Arduino pin number + * + * If the pin is in output mode toggle the pin level. + * If the pin is in input mode toggle the state of the 20K pullup. + */ +static inline __attribute__((always_inline)) +void fastDigitalToggle(uint8_t pin) { + if (pinReg(pin) > reinterpret_cast(0X3F)) { + // must write bit to high address port + *pinReg(pin) = pinMask(pin); + } else { + // will compile to sbi and PIN register will not be read. + *pinReg(pin) |= pinMask(pin); + } +} +//------------------------------------------------------------------------------ +/** Set pin value. + * @param[in] pin Arduino pin number + * @param[in] level value to write + */ +static inline __attribute__((always_inline)) +void fastDigitalWrite(uint8_t pin, bool level) { + fastBitWriteSafe(portReg(pin), pinMask(pin), level); +} +//------------------------------------------------------------------------------ +/** Write the DDR register. + * @param[in] pin Arduino pin number + * @param[in] level value to write + */ +static inline __attribute__((always_inline)) +void fastDdrWrite(uint8_t pin, bool level) { + fastBitWriteSafe(ddrReg(pin), pinMask(pin), level); +} +//------------------------------------------------------------------------------ +/** Set pin mode. + * @param[in] pin Arduino pin number + * @param[in] mode INPUT, OUTPUT, or INPUT_PULLUP. + * + * The internal pullup resistors will be enabled if mode is INPUT_PULLUP + * and disabled if the mode is INPUT. + */ +static inline __attribute__((always_inline)) +void fastPinMode(uint8_t pin, uint8_t mode) { + fastDdrWrite(pin, mode == OUTPUT); + if (mode != OUTPUT) { + fastDigitalWrite(pin, mode == INPUT_PULLUP); + } +} +#else // defined(__AVR__) +#if defined(CORE_TEENSY) +//------------------------------------------------------------------------------ +/** read pin value + * @param[in] pin Arduino pin number + * @return value read + */ +static inline __attribute__((always_inline)) +bool fastDigitalRead(uint8_t pin) { + return *portInputRegister(pin); +} +//------------------------------------------------------------------------------ +/** Set pin value + * @param[in] pin Arduino pin number + * @param[in] level value to write + */ +static inline __attribute__((always_inline)) +void fastDigitalWrite(uint8_t pin, bool value) { + if (value) { + *portSetRegister(pin) = 1; + } else { + *portClearRegister(pin) = 1; + } +} +#elif defined(__SAM3X8E__) || defined(__SAM3X8H__) +//------------------------------------------------------------------------------ +/** read pin value + * @param[in] pin Arduino pin number + * @return value read + */ +static inline __attribute__((always_inline)) +bool fastDigitalRead(uint8_t pin) { + return g_APinDescription[pin].pPort->PIO_PDSR & g_APinDescription[pin].ulPin; +} +//------------------------------------------------------------------------------ +/** Set pin value + * @param[in] pin Arduino pin number + * @param[in] level value to write + */ +static inline __attribute__((always_inline)) +void fastDigitalWrite(uint8_t pin, bool value) { + if (value) { + g_APinDescription[pin].pPort->PIO_SODR = g_APinDescription[pin].ulPin; + } else { + g_APinDescription[pin].pPort->PIO_CODR = g_APinDescription[pin].ulPin; + } +} +#elif defined(ESP8266) +//------------------------------------------------------------------------------ +/** Set pin value + * @param[in] pin Arduino pin number + * @param[in] val value to write + */ +static inline __attribute__((always_inline)) +void fastDigitalWrite(uint8_t pin, uint8_t val) { + if (pin < 16) { + if (val) { + GPOS = (1 << pin); + } else { + GPOC = (1 << pin); + } + } else if (pin == 16) { + if (val) { + GP16O |= 1; + } else { + GP16O &= ~1; + } + } +} +//------------------------------------------------------------------------------ +/** Read pin value + * @param[in] pin Arduino pin number + * @return value read + */ +static inline __attribute__((always_inline)) +bool fastDigitalRead(uint8_t pin) { + if (pin < 16) { + return GPIP(pin); + } else if (pin == 16) { + return GP16I & 0x01; + } + return 0; +} +#else // CORE_TEENSY +//------------------------------------------------------------------------------ +inline void fastDigitalWrite(uint8_t pin, bool value) { + digitalWrite(pin, value); +} +//------------------------------------------------------------------------------ +inline bool fastDigitalRead(uint8_t pin) {return digitalRead(pin);} +#endif // CORE_TEENSY +//------------------------------------------------------------------------------ +inline void fastDigitalToggle(uint8_t pin) { + fastDigitalWrite(pin, !fastDigitalRead(pin)); +} +//------------------------------------------------------------------------------ +#define fastPinMode(pin, mode) pinMode(pin, mode) +#endif // __AVR__ +//------------------------------------------------------------------------------ +/** set pin configuration + * @param[in] pin Arduino pin number + * @param[in] mode mode INPUT or OUTPUT. + * @param[in] level If mode is output, set level high/low. + * If mode is input, enable or disable the pin's 20K pullup. + */ +#define fastPinConfig(pin, mode, level)\ + {fastPinMode(pin, mode); fastDigitalWrite(pin, level);} +//============================================================================== +/** + * @class DigitalPin + * @brief Fast digital port I/O + */ +template +class DigitalPin { + public: + //---------------------------------------------------------------------------- + /** Constructor */ + DigitalPin() {} + //---------------------------------------------------------------------------- + /** Asignment operator. + * @param[in] value If true set the pin's level high else set the + * pin's level low. + * + * @return This DigitalPin instance. + */ + inline DigitalPin & operator = (bool value) __attribute__((always_inline)) { + write(value); + return *this; + } + //---------------------------------------------------------------------------- + /** Parenthesis operator. + * @return Pin's level + */ + inline operator bool () const __attribute__((always_inline)) { + return read(); + } + //---------------------------------------------------------------------------- + /** Set pin configuration. + * @param[in] mode: INPUT or OUTPUT. + * @param[in] level If mode is OUTPUT, set level high/low. + * If mode is INPUT, enable or disable the pin's 20K pullup. + */ + inline __attribute__((always_inline)) + void config(uint8_t mode, bool level) { + fastPinConfig(PinNumber, mode, level); + } + //---------------------------------------------------------------------------- + /** + * Set pin level high if output mode or enable 20K pullup if input mode. + */ + inline __attribute__((always_inline)) + void high() {write(true);} + //---------------------------------------------------------------------------- + /** + * Set pin level low if output mode or disable 20K pullup if input mode. + */ + inline __attribute__((always_inline)) + void low() {write(false);} + //---------------------------------------------------------------------------- + /** + * Set pin mode. + * @param[in] mode: INPUT, OUTPUT, or INPUT_PULLUP. + * + * The internal pullup resistors will be enabled if mode is INPUT_PULLUP + * and disabled if the mode is INPUT. + */ + inline __attribute__((always_inline)) + void mode(uint8_t mode) { + fastPinMode(PinNumber, mode); + } + //---------------------------------------------------------------------------- + /** @return Pin's level. */ + inline __attribute__((always_inline)) + bool read() const { + return fastDigitalRead(PinNumber); + } + //---------------------------------------------------------------------------- + /** Toggle a pin. + * + * If the pin is in output mode toggle the pin's level. + * If the pin is in input mode toggle the state of the 20K pullup. + */ + inline __attribute__((always_inline)) + void toggle() { + fastDigitalToggle(PinNumber); + } + //---------------------------------------------------------------------------- + /** Write the pin's level. + * @param[in] value If true set the pin's level high else set the + * pin's level low. + */ + inline __attribute__((always_inline)) + void write(bool value) { + fastDigitalWrite(PinNumber, value); + } +}; +#endif // DigitalPin_h +/** @} */ diff --git a/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdInfo.h b/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdInfo.h new file mode 100644 index 0000000..968283b --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdInfo.h @@ -0,0 +1,385 @@ +/* Arduino SdSpiCard Library + * Copyright (C) 2012 by William Greiman + * + * This file is part of the Arduino SdSpiCard Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino SdSpiCard Library. If not, see + * . + */ +#ifndef SdInfo_h +#define SdInfo_h +#include +// Based on the document: +// +// SD Specifications +// Part 1 +// Physical Layer +// Simplified Specification +// Version 3.01 +// May 18, 2010 +// +// http://www.sdcard.org/developers/tech/sdcard/pls/simplified_specs +//------------------------------------------------------------------------------ +// SD card errors +/** timeout error for command CMD0 (initialize card in SPI mode) */ +uint8_t const SD_CARD_ERROR_CMD0 = 0X1; +/** CMD8 was not accepted - not a valid SD card*/ +uint8_t const SD_CARD_ERROR_CMD8 = 0X2; +/** card returned an error response for CMD12 (stop multiblock read) */ +uint8_t const SD_CARD_ERROR_CMD12 = 0X3; +/** card returned an error response for CMD17 (read block) */ +uint8_t const SD_CARD_ERROR_CMD17 = 0X4; +/** card returned an error response for CMD18 (read multiple block) */ +uint8_t const SD_CARD_ERROR_CMD18 = 0X5; +/** card returned an error response for CMD24 (write block) */ +uint8_t const SD_CARD_ERROR_CMD24 = 0X6; +/** WRITE_MULTIPLE_BLOCKS command failed */ +uint8_t const SD_CARD_ERROR_CMD25 = 0X7; +/** card returned an error response for CMD58 (read OCR) */ +uint8_t const SD_CARD_ERROR_CMD58 = 0X8; +/** SET_WR_BLK_ERASE_COUNT failed */ +uint8_t const SD_CARD_ERROR_ACMD23 = 0X9; +/** ACMD41 initialization process timeout */ +uint8_t const SD_CARD_ERROR_ACMD41 = 0XA; +/** card returned a bad CSR version field */ +uint8_t const SD_CARD_ERROR_BAD_CSD = 0XB; +/** erase block group command failed */ +uint8_t const SD_CARD_ERROR_ERASE = 0XC; +/** card not capable of single block erase */ +uint8_t const SD_CARD_ERROR_ERASE_SINGLE_BLOCK = 0XD; +/** Erase sequence timed out */ +uint8_t const SD_CARD_ERROR_ERASE_TIMEOUT = 0XE; +/** card returned an error token instead of read data */ +uint8_t const SD_CARD_ERROR_READ = 0XF; +/** read CID or CSD failed */ +uint8_t const SD_CARD_ERROR_READ_REG = 0X10; +/** timeout while waiting for start of read data */ +uint8_t const SD_CARD_ERROR_READ_TIMEOUT = 0X11; +/** card did not accept STOP_TRAN_TOKEN */ +uint8_t const SD_CARD_ERROR_STOP_TRAN = 0X12; +/** card returned an error token as a response to a write operation */ +uint8_t const SD_CARD_ERROR_WRITE = 0X13; +/** attempt to write protected block zero */ +uint8_t const SD_CARD_ERROR_WRITE_BLOCK_ZERO = 0X14; // REMOVE - not used +/** card did not go ready for a multiple block write */ +uint8_t const SD_CARD_ERROR_WRITE_MULTIPLE = 0X15; // Not used +/** card returned an error to a CMD13 status check after a write */ +uint8_t const SD_CARD_ERROR_WRITE_PROGRAMMING = 0X16; +/** timeout occurred during write programming */ +uint8_t const SD_CARD_ERROR_WRITE_TIMEOUT = 0X17; +/** incorrect rate selected */ +uint8_t const SD_CARD_ERROR_SCK_RATE = 0X18; +/** init() not called */ +uint8_t const SD_CARD_ERROR_INIT_NOT_CALLED = 0X19; +/** card returned an error for CMD59 (CRC_ON_OFF) */ +uint8_t const SD_CARD_ERROR_CMD59 = 0X1A; +/** invalid read CRC */ +uint8_t const SD_CARD_ERROR_READ_CRC = 0X1B; +/** SPI DMA error */ +uint8_t const SD_CARD_ERROR_SPI_DMA = 0X1C; +/** CMD6 not accepted */ +uint8_t const SD_CARD_ERROR_CMD6 = 0X1D; +//------------------------------------------------------------------------------ +// card types +/** Standard capacity V1 SD card */ +uint8_t const SD_CARD_TYPE_SD1 = 1; +/** Standard capacity V2 SD card */ +uint8_t const SD_CARD_TYPE_SD2 = 2; +/** High Capacity SD card */ +uint8_t const SD_CARD_TYPE_SDHC = 3; +//------------------------------------------------------------------------------ +// SPI divisor constants +/** Set SCK to max rate of F_CPU/2. */ +uint8_t const SPI_FULL_SPEED = 2; +/** Set SCK rate to F_CPU/3 for Due */ +uint8_t const SPI_DIV3_SPEED = 3; +/** Set SCK rate to F_CPU/4. */ +uint8_t const SPI_HALF_SPEED = 4; +/** Set SCK rate to F_CPU/6 for Due */ +uint8_t const SPI_DIV6_SPEED = 6; +/** Set SCK rate to F_CPU/8. */ +uint8_t const SPI_QUARTER_SPEED = 8; +/** Set SCK rate to F_CPU/16. */ +uint8_t const SPI_EIGHTH_SPEED = 16; +/** Set SCK rate to F_CPU/32. */ +uint8_t const SPI_SIXTEENTH_SPEED = 32; +//------------------------------------------------------------------------------ +// SD operation timeouts +/** init timeout ms */ +unsigned const SD_INIT_TIMEOUT = 2000; +/** erase timeout ms */ +unsigned const SD_ERASE_TIMEOUT = 10000; +/** read timeout ms */ +unsigned const SD_READ_TIMEOUT = 300; +/** write time out ms */ +unsigned const SD_WRITE_TIMEOUT = 600; +//------------------------------------------------------------------------------ +// SD card commands +/** GO_IDLE_STATE - init card in spi mode if CS low */ +uint8_t const CMD0 = 0X00; +/** SWITCH_FUNC - Switch Function Command */ +uint8_t const CMD6 = 0X06; +/** SEND_IF_COND - verify SD Memory Card interface operating condition.*/ +uint8_t const CMD8 = 0X08; +/** SEND_CSD - read the Card Specific Data (CSD register) */ +uint8_t const CMD9 = 0X09; +/** SEND_CID - read the card identification information (CID register) */ +uint8_t const CMD10 = 0X0A; +/** STOP_TRANSMISSION - end multiple block read sequence */ +uint8_t const CMD12 = 0X0C; +/** SEND_STATUS - read the card status register */ +uint8_t const CMD13 = 0X0D; +/** READ_SINGLE_BLOCK - read a single data block from the card */ +uint8_t const CMD17 = 0X11; +/** READ_MULTIPLE_BLOCK - read a multiple data blocks from the card */ +uint8_t const CMD18 = 0X12; +/** WRITE_BLOCK - write a single data block to the card */ +uint8_t const CMD24 = 0X18; +/** WRITE_MULTIPLE_BLOCK - write blocks of data until a STOP_TRANSMISSION */ +uint8_t const CMD25 = 0X19; +/** ERASE_WR_BLK_START - sets the address of the first block to be erased */ +uint8_t const CMD32 = 0X20; +/** ERASE_WR_BLK_END - sets the address of the last block of the continuous + range to be erased*/ +uint8_t const CMD33 = 0X21; +/** ERASE - erase all previously selected blocks */ +uint8_t const CMD38 = 0X26; +/** APP_CMD - escape for application specific command */ +uint8_t const CMD55 = 0X37; +/** READ_OCR - read the OCR register of a card */ +uint8_t const CMD58 = 0X3A; +/** CRC_ON_OFF - enable or disable CRC checking */ +uint8_t const CMD59 = 0X3B; +/** SET_WR_BLK_ERASE_COUNT - Set the number of write blocks to be + pre-erased before writing */ +uint8_t const ACMD23 = 0X17; +/** SD_SEND_OP_COMD - Sends host capacity support information and + activates the card's initialization process */ +uint8_t const ACMD41 = 0X29; +//============================================================================== +/** status for card in the ready state */ +uint8_t const R1_READY_STATE = 0X00; +/** status for card in the idle state */ +uint8_t const R1_IDLE_STATE = 0X01; +/** status bit for illegal command */ +uint8_t const R1_ILLEGAL_COMMAND = 0X04; +/** start data token for read or write single block*/ +uint8_t const DATA_START_BLOCK = 0XFE; +/** stop token for write multiple blocks*/ +uint8_t const STOP_TRAN_TOKEN = 0XFD; +/** start data token for write multiple blocks*/ +uint8_t const WRITE_MULTIPLE_TOKEN = 0XFC; +/** mask for data response tokens after a write block operation */ +uint8_t const DATA_RES_MASK = 0X1F; +/** write data accepted token */ +uint8_t const DATA_RES_ACCEPTED = 0X05; +//============================================================================== +/** + * \class CID + * \brief Card IDentification (CID) register. + */ +typedef struct CID { + // byte 0 + /** Manufacturer ID */ + unsigned char mid; + // byte 1-2 + /** OEM/Application ID */ + char oid[2]; + // byte 3-7 + /** Product name */ + char pnm[5]; + // byte 8 + /** Product revision least significant digit */ + unsigned char prv_m : 4; + /** Product revision most significant digit */ + unsigned char prv_n : 4; + // byte 9-12 + /** Product serial number */ + uint32_t psn; + // byte 13 + /** Manufacturing date year low digit */ + unsigned char mdt_year_high : 4; + /** not used */ + unsigned char reserved : 4; + // byte 14 + /** Manufacturing date month */ + unsigned char mdt_month : 4; + /** Manufacturing date year low digit */ + unsigned char mdt_year_low : 4; + // byte 15 + /** not used always 1 */ + unsigned char always1 : 1; + /** CRC7 checksum */ + unsigned char crc : 7; +} __attribute__((packed)) cid_t; +//============================================================================== +/** + * \class CSDV1 + * \brief CSD register for version 1.00 cards . + */ +typedef struct CSDV1 { + // byte 0 + unsigned char reserved1 : 6; + unsigned char csd_ver : 2; + // byte 1 + unsigned char taac; + // byte 2 + unsigned char nsac; + // byte 3 + unsigned char tran_speed; + // byte 4 + unsigned char ccc_high; + // byte 5 + unsigned char read_bl_len : 4; + unsigned char ccc_low : 4; + // byte 6 + unsigned char c_size_high : 2; + unsigned char reserved2 : 2; + unsigned char dsr_imp : 1; + unsigned char read_blk_misalign : 1; + unsigned char write_blk_misalign : 1; + unsigned char read_bl_partial : 1; + // byte 7 + unsigned char c_size_mid; + // byte 8 + unsigned char vdd_r_curr_max : 3; + unsigned char vdd_r_curr_min : 3; + unsigned char c_size_low : 2; + // byte 9 + unsigned char c_size_mult_high : 2; + unsigned char vdd_w_cur_max : 3; + unsigned char vdd_w_curr_min : 3; + // byte 10 + unsigned char sector_size_high : 6; + unsigned char erase_blk_en : 1; + unsigned char c_size_mult_low : 1; + // byte 11 + unsigned char wp_grp_size : 7; + unsigned char sector_size_low : 1; + // byte 12 + unsigned char write_bl_len_high : 2; + unsigned char r2w_factor : 3; + unsigned char reserved3 : 2; + unsigned char wp_grp_enable : 1; + // byte 13 + unsigned char reserved4 : 5; + unsigned char write_partial : 1; + unsigned char write_bl_len_low : 2; + // byte 14 + unsigned char reserved5: 2; + unsigned char file_format : 2; + unsigned char tmp_write_protect : 1; + unsigned char perm_write_protect : 1; + unsigned char copy : 1; + /** Indicates the file format on the card */ + unsigned char file_format_grp : 1; + // byte 15 + unsigned char always1 : 1; + unsigned char crc : 7; +} __attribute__((packed)) csd1_t; +//============================================================================== +/** + * \class CSDV2 + * \brief CSD register for version 2.00 cards. + */ +typedef struct CSDV2 { + // byte 0 + unsigned char reserved1 : 6; + unsigned char csd_ver : 2; + // byte 1 + /** fixed to 0X0E */ + unsigned char taac; + // byte 2 + /** fixed to 0 */ + unsigned char nsac; + // byte 3 + unsigned char tran_speed; + // byte 4 + unsigned char ccc_high; + // byte 5 + /** This field is fixed to 9h, which indicates READ_BL_LEN=512 Byte */ + unsigned char read_bl_len : 4; + unsigned char ccc_low : 4; + // byte 6 + /** not used */ + unsigned char reserved2 : 4; + unsigned char dsr_imp : 1; + /** fixed to 0 */ + unsigned char read_blk_misalign : 1; + /** fixed to 0 */ + unsigned char write_blk_misalign : 1; + /** fixed to 0 - no partial read */ + unsigned char read_bl_partial : 1; + // byte 7 + /** high part of card size */ + unsigned char c_size_high : 6; + /** not used */ + unsigned char reserved3 : 2; + // byte 8 + /** middle part of card size */ + unsigned char c_size_mid; + // byte 9 + /** low part of card size */ + unsigned char c_size_low; + // byte 10 + /** sector size is fixed at 64 KB */ + unsigned char sector_size_high : 6; + /** fixed to 1 - erase single is supported */ + unsigned char erase_blk_en : 1; + /** not used */ + unsigned char reserved4 : 1; + // byte 11 + unsigned char wp_grp_size : 7; + /** sector size is fixed at 64 KB */ + unsigned char sector_size_low : 1; + // byte 12 + /** write_bl_len fixed for 512 byte blocks */ + unsigned char write_bl_len_high : 2; + /** fixed value of 2 */ + unsigned char r2w_factor : 3; + /** not used */ + unsigned char reserved5 : 2; + /** fixed value of 0 - no write protect groups */ + unsigned char wp_grp_enable : 1; + // byte 13 + unsigned char reserved6 : 5; + /** always zero - no partial block read*/ + unsigned char write_partial : 1; + /** write_bl_len fixed for 512 byte blocks */ + unsigned char write_bl_len_low : 2; + // byte 14 + unsigned char reserved7: 2; + /** Do not use always 0 */ + unsigned char file_format : 2; + unsigned char tmp_write_protect : 1; + unsigned char perm_write_protect : 1; + unsigned char copy : 1; + /** Do not use always 0 */ + unsigned char file_format_grp : 1; + // byte 15 + /** not used always 1 */ + unsigned char always1 : 1; + /** checksum */ + unsigned char crc : 7; +} __attribute__((packed)) csd2_t; +//============================================================================== +/** + * \class csd_t + * \brief Union of old and new style CSD register. + */ +union csd_t { + csd1_t v1; + csd2_t v2; +}; +#endif // SdInfo_h diff --git a/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpi.h b/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpi.h new file mode 100644 index 0000000..059e0bc --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpi.h @@ -0,0 +1,411 @@ +/* Arduino SdSpi Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the Arduino SdSpi Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino SdSpi Library. If not, see + * . + */ +/** +* \file +* \brief SdSpi class for V2 SD/SDHC cards +*/ +#ifndef SdSpi_h +#define SdSpi_h +#include "../SystemInclude.h" +#include "../SdFatConfig.h" +//------------------------------------------------------------------------------ +/** + * \class SdSpiBase + * \brief Virtual SPI class for access to SD and SDHC flash memory cards. + */ +class SdSpiBase { + public: + /** Initialize the SPI bus. + * + * \param[in] chipSelectPin SD card chip select pin. + */ + virtual void begin(uint8_t chipSelectPin) = 0; + /** Set SPI options for access to SD/SDHC cards. + * + * \param[in] divisor SCK clock divider relative to the system clock. + */ + virtual void beginTransaction(uint8_t divisor); + /** + * End SPI transaction. + */ + virtual void endTransaction(); + /** Receive a byte. + * + * \return The byte. + */ + virtual uint8_t receive() = 0; + /** Receive multiple bytes. + * + * \param[out] buf Buffer to receive the data. + * \param[in] n Number of bytes to receive. + * + * \return Zero for no error or nonzero error code. + */ + virtual uint8_t receive(uint8_t* buf, size_t n) = 0; + /** Send a byte. + * + * \param[in] data Byte to send + */ + virtual void send(uint8_t data) = 0; + /** Send multiple bytes. + * + * \param[in] buf Buffer for data to be sent. + * \param[in] n Number of bytes to send. + */ + virtual void send(const uint8_t* buf, size_t n) = 0; +}; +//------------------------------------------------------------------------------ +/** + * \class SdSpi + * \brief SPI class for access to SD and SDHC flash memory cards. + */ +#if SD_SPI_CONFIGURATION >= 3 +class SdSpi : public SdSpiBase { +#else // SD_SPI_CONFIGURATION >= 3 +class SdSpi { +#endif // SD_SPI_CONFIGURATION >= 3 + public: + /** Initialize the SPI bus. + * + * \param[in] chipSelectPin SD card chip select pin. + */ + void begin(uint8_t chipSelectPin); + /** Set SPI options for access to SD/SDHC cards. + * + * \param[in] divisor SCK clock divider relative to the system clock. + */ + void beginTransaction(uint8_t divisor); + /** + * End SPI transaction + */ + void endTransaction(); + /** Receive a byte. + * + * \return The byte. + */ + uint8_t receive(); + /** Receive multiple bytes. + * + * \param[out] buf Buffer to receive the data. + * \param[in] n Number of bytes to receive. + * + * \return Zero for no error or nonzero error code. + */ + uint8_t receive(uint8_t* buf, size_t n); + /** Send a byte. + * + * \param[in] data Byte to send + */ + void send(uint8_t data); + /** Send multiple bytes. + * + * \param[in] buf Buffer for data to be sent. + * \param[in] n Number of bytes to send. + */ + void send(const uint8_t* buf, size_t n); + /** \return true - uses SPI transactions */ +#if IMPLEMENT_SPI_INTERFACE_SELECTION + void setSpiIf(uint8_t spiIf) { + m_spiIf = spiIf; + } + private: + uint8_t m_spiIf; +#endif // IMPLEMENT_SPI_INTERFACE_SELECTION +}; +//------------------------------------------------------------------------------ +/** + * \class SdSpiLib + * \brief Arduino SPI library class for access to SD and SDHC flash + * memory cards. + */ +#if SD_SPI_CONFIGURATION >= 3 +class SdSpiLib : public SdSpiBase { +#else // SD_SPI_CONFIGURATION >= 3 +class SdSpiLib { +#endif // SD_SPI_CONFIGURATION >= 3 + public: + /** Initialize the SPI bus. + * + * \param[in] chipSelectPin SD card chip select pin. + */ + void begin(uint8_t chipSelectPin) { + pinMode(chipSelectPin, OUTPUT); + digitalWrite(chipSelectPin, HIGH); + SPI.begin(); + } + /** Set SPI options for access to SD/SDHC cards. + * + * \param[in] divisor SCK clock divider relative to the system clock. + */ + void beginTransaction(uint8_t divisor) { +#if ENABLE_SPI_TRANSACTIONS + SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0)); +#else // ENABLE_SPI_TRANSACTIONS + SPI.setBitOrder(MSBFIRST); + SPI.setDataMode(SPI_MODE0); +#endif // ENABLE_SPI_TRANSACTIONS +#ifndef SPI_CLOCK_DIV128 + SPI.setClockDivider(divisor); +#else // SPI_CLOCK_DIV128 + int v; + if (divisor <= 2) { + v = SPI_CLOCK_DIV2; + } else if (divisor <= 4) { + v = SPI_CLOCK_DIV4; + } else if (divisor <= 8) { + v = SPI_CLOCK_DIV8; + } else if (divisor <= 16) { + v = SPI_CLOCK_DIV16; + } else if (divisor <= 32) { + v = SPI_CLOCK_DIV32; + } else if (divisor <= 64) { + v = SPI_CLOCK_DIV64; + } else { + v = SPI_CLOCK_DIV128; + } + SPI.setClockDivider(v); +#endif // SPI_CLOCK_DIV128 + } + /** + * End SPI transaction. + */ + void endTransaction() { +#if ENABLE_SPI_TRANSACTIONS + SPI.endTransaction(); +#endif // ENABLE_SPI_TRANSACTIONS + } + /** Receive a byte. + * + * \return The byte. + */ + uint8_t receive() { + return SPI.transfer(0XFF); + } + /** Receive multiple bytes. + * + * \param[out] buf Buffer to receive the data. + * \param[in] n Number of bytes to receive. + * + * \return Zero for no error or nonzero error code. + */ + uint8_t receive(uint8_t* buf, size_t n) { + for (size_t i = 0; i < n; i++) { + buf[i] = SPI.transfer(0XFF); + } + return 0; + } + /** Send a byte. + * + * \param[in] b Byte to send + */ + void send(uint8_t b) { + SPI.transfer(b); + } + /** Send multiple bytes. + * + * \param[in] buf Buffer for data to be sent. + * \param[in] n Number of bytes to send. + */ + void send(const uint8_t* buf , size_t n) { + for (size_t i = 0; i < n; i++) { + SPI.transfer(buf[i]); + } + } +}; +//------------------------------------------------------------------------------ +#if SD_SPI_CONFIGURATION > 1 || defined(DOXYGEN) +#ifdef ARDUINO +#include "SoftSPI.h" +#elif defined(PLATFORM_ID) // Only defined if a Particle device +#include "SoftSPIParticle.h" +#endif // ARDUINO +/** + * \class SdSpiSoft + * \brief Software SPI class for access to SD and SDHC flash memory cards. + */ +template +class SdSpiSoft : public SdSpiBase { + public: + /** Initialize the SPI bus. + * + * \param[in] chipSelectPin SD card chip select pin. + */ + void begin(uint8_t chipSelectPin) { + pinMode(chipSelectPin, OUTPUT); + digitalWrite(chipSelectPin, HIGH); + m_spi.begin(); + } + /** + * Initialize hardware SPI - dummy for soft SPI + * \param[in] divisor SCK divisor - ignored. + */ + void beginTransaction(uint8_t divisor) { + (void)divisor; + } + /** + * End SPI transaction - dummy for soft SPI + */ + void endTransaction() {} + /** Receive a byte. + * + * \return The byte. + */ + uint8_t receive() { + return m_spi.receive(); + } + /** Receive multiple bytes. + * + * \param[out] buf Buffer to receive the data. + * \param[in] n Number of bytes to receive. + * + * \return Zero for no error or nonzero error code. + */ + uint8_t receive(uint8_t* buf, size_t n) { + for (size_t i = 0; i < n; i++) { + buf[i] = receive(); + } + return 0; + } + /** Send a byte. + * + * \param[in] data Byte to send + */ + void send(uint8_t data) { + m_spi.send(data); + } + /** Send multiple bytes. + * + * \param[in] buf Buffer for data to be sent. + * \param[in] n Number of bytes to send. + */ + void send(const uint8_t* buf , size_t n) { + for (size_t i = 0; i < n; i++) { + send(buf[i]); + } + } + + private: + SoftSPI m_spi; +}; +#endif // SD_SPI_CONFIGURATION > 1 || defined(DOXYGEN) +//------------------------------------------------------------------------------ +#if SD_SPI_CONFIGURATION == 2 +/** Default is software SPI. */ +typedef SdSpiSoft +SpiDefault_t; +#elif SD_SPI_CONFIGURATION == 1 || !SD_HAS_CUSTOM_SPI +/** Default is Arduino library SPI. */ +typedef SdSpiLib SpiDefault_t; +#else // SpiDefault_t +/** Default is custom fast SPI. */ +typedef SdSpi SpiDefault_t; +#endif // SpiDefault_t +//------------------------------------------------------------------------------ +// Use of in-line for AVR to save flash. +#ifdef __AVR__ +//------------------------------------------------------------------------------ +inline void SdSpi::begin(uint8_t chipSelectPin) { +#ifdef __AVR_ATmega328P__ + // Save a few bytes for 328 CPU - gcc optimizes single bit '|' to sbi. + PORTB |= 1 << 2; // SS high + DDRB |= 1 << 2; // SS output mode + DDRB |= 1 << 3; // MOSI output mode + DDRB |= 1 << 5; // SCK output mode +#else // __AVR_ATmega328P__ + + // set SS high - may be chip select for another SPI device + digitalWrite(SS, HIGH); + + // SS must be in output mode even it is not chip select + pinMode(SS, OUTPUT); + pinMode(MOSI, OUTPUT); + pinMode(SCK, OUTPUT); +#endif // __AVR_ATmega328P__ + pinMode(chipSelectPin, OUTPUT); + digitalWrite(chipSelectPin, HIGH); +} +//------------------------------------------------------------------------------ +inline void SdSpi::beginTransaction(uint8_t divisor) { +#if ENABLE_SPI_TRANSACTIONS + SPI.beginTransaction(SPISettings()); +#endif // ENABLE_SPI_TRANSACTIONS + uint8_t b = 2; + uint8_t r = 0; + + // See AVR processor documentation. + for (; divisor > b && r < 7; b <<= 1, r += r < 5 ? 1 : 2) {} + SPCR = (1 << SPE) | (1 << MSTR) | (r >> 1); + SPSR = r & 1 ? 0 : 1 << SPI2X; +} +//------------------------------------------------------------------------------ +inline void SdSpi::endTransaction() { +#if ENABLE_SPI_TRANSACTIONS + SPI.endTransaction(); +#endif // ENABLE_SPI_TRANSACTIONS +} +//------------------------------------------------------------------------------ +inline uint8_t SdSpi::receive() { + SPDR = 0XFF; + while (!(SPSR & (1 << SPIF))) {} + return SPDR; +} +//------------------------------------------------------------------------------ +inline uint8_t SdSpi::receive(uint8_t* buf, size_t n) { + if (n-- == 0) { + return 0; + } + SPDR = 0XFF; + for (size_t i = 0; i < n; i++) { + while (!(SPSR & (1 << SPIF))) {} + uint8_t b = SPDR; + SPDR = 0XFF; + buf[i] = b; + } + while (!(SPSR & (1 << SPIF))) {} + buf[n] = SPDR; + return 0; +} +//------------------------------------------------------------------------------ +inline void SdSpi::send(uint8_t data) { + SPDR = data; + while (!(SPSR & (1 << SPIF))) {} +} +//------------------------------------------------------------------------------ +inline void SdSpi::send(const uint8_t* buf , size_t n) { + if (n == 0) { + return; + } + SPDR = buf[0]; + if (n > 1) { + uint8_t b = buf[1]; + size_t i = 2; + while (1) { + while (!(SPSR & (1 << SPIF))) {} + SPDR = b; + if (i == n) { + break; + } + b = buf[i++]; + } + } + while (!(SPSR & (1 << SPIF))) {} +} +#endif // __AVR__ +#endif // SdSpi_h diff --git a/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpiCard.cpp b/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpiCard.cpp new file mode 100644 index 0000000..4debd2c --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpiCard.cpp @@ -0,0 +1,652 @@ +/* Arduino SdSpiCard Library + * Copyright (C) 2012 by William Greiman + * + * This file is part of the Arduino SdSpiCard Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino SdSpiCard Library. If not, see + * . + */ +#include "SdSpiCard.h" +#include "SdSpi.h" +// debug trace macro +#define SD_TRACE(m, b) +// #define SD_TRACE(m, b) Serial.print(m);Serial.println(b); +#define SD_CS_DBG(m) +// #define SD_CS_DBG(m) Serial.println(F(m)); +//============================================================================== +#if USE_SD_CRC +// CRC functions +//------------------------------------------------------------------------------ +static uint8_t CRC7(const uint8_t* data, uint8_t n) { + uint8_t crc = 0; + for (uint8_t i = 0; i < n; i++) { + uint8_t d = data[i]; + for (uint8_t j = 0; j < 8; j++) { + crc <<= 1; + if ((d & 0x80) ^ (crc & 0x80)) { + crc ^= 0x09; + } + d <<= 1; + } + } + return (crc << 1) | 1; +} +//------------------------------------------------------------------------------ +#if USE_SD_CRC == 1 +// slower CRC-CCITT +// uses the x^16,x^12,x^5,x^1 polynomial. +static uint16_t CRC_CCITT(const uint8_t *data, size_t n) { + uint16_t crc = 0; + for (size_t i = 0; i < n; i++) { + crc = (uint8_t)(crc >> 8) | (crc << 8); + crc ^= data[i]; + crc ^= (uint8_t)(crc & 0xff) >> 4; + crc ^= crc << 12; + crc ^= (crc & 0xff) << 5; + } + return crc; +} +#elif USE_SD_CRC > 1 // CRC_CCITT +//------------------------------------------------------------------------------ +// faster CRC-CCITT +// uses the x^16,x^12,x^5,x^1 polynomial. +#ifdef __AVR__ +static const uint16_t crctab[] PROGMEM = { +#else // __AVR__ +static const uint16_t crctab[] = { +#endif // __AVR__ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, + 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, + 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, + 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, + 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, + 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, + 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, + 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, + 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, + 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, + 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, + 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, + 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, + 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 +}; +static uint16_t CRC_CCITT(const uint8_t* data, size_t n) { + uint16_t crc = 0; + for (size_t i = 0; i < n; i++) { +#ifdef __AVR__ + crc = pgm_read_word(&crctab[(crc >> 8 ^ data[i]) & 0XFF]) ^ (crc << 8); +#else // __AVR__ + crc = crctab[(crc >> 8 ^ data[i]) & 0XFF] ^ (crc << 8); +#endif // __AVR__ + } + return crc; +} +#endif // CRC_CCITT +#endif // USE_SD_CRC +//============================================================================== +// SdSpiCard member functions +//------------------------------------------------------------------------------ +bool SdSpiCard::begin(m_spi_t* spi, uint8_t chipSelectPin, uint8_t sckDivisor) { + m_errorCode = m_type = 0; + m_spi = spi; + m_chipSelectPin = chipSelectPin; + // 16-bit init start time allows over a minute + unsigned t0 = (unsigned)millis(); + uint32_t arg; + + // initialize SPI bus and chip select pin. + spiBegin(m_chipSelectPin); + + // set SCK rate for initialization commands. + m_sckDivisor = SPI_SCK_INIT_DIVISOR; + + // toggle chip select and set slow SPI clock. + chipSelectLow(); + chipSelectHigh(); + + // must supply min of 74 clock cycles with CS high. + for (uint8_t i = 0; i < 10; i++) { + spiSend(0XFF); + } + // command to go idle in SPI mode + while (cardCommand(CMD0, 0) != R1_IDLE_STATE) { + if (((unsigned)millis() - t0) > SD_INIT_TIMEOUT) { + error(SD_CARD_ERROR_CMD0); + goto fail; + } + } +#if USE_SD_CRC + if (cardCommand(CMD59, 1) != R1_IDLE_STATE) { + error(SD_CARD_ERROR_CMD59); + goto fail; + } +#endif // USE_SD_CRC + // check SD version + while (1) { + if (cardCommand(CMD8, 0x1AA) == (R1_ILLEGAL_COMMAND | R1_IDLE_STATE)) { + type(SD_CARD_TYPE_SD1); + break; + } + for (uint8_t i = 0; i < 4; i++) { + m_status = spiReceive(); + } + if (m_status == 0XAA) { + type(SD_CARD_TYPE_SD2); + break; + } + if (((unsigned)millis() - t0) > SD_INIT_TIMEOUT) { + error(SD_CARD_ERROR_CMD8); + goto fail; + } + } + // initialize card and send host supports SDHC if SD2 + arg = type() == SD_CARD_TYPE_SD2 ? 0X40000000 : 0; + + while (cardAcmd(ACMD41, arg) != R1_READY_STATE) { + // check for timeout + if (((unsigned)millis() - t0) > SD_INIT_TIMEOUT) { + error(SD_CARD_ERROR_ACMD41); + goto fail; + } + } + // if SD2 read OCR register to check for SDHC card + if (type() == SD_CARD_TYPE_SD2) { + if (cardCommand(CMD58, 0)) { + error(SD_CARD_ERROR_CMD58); + goto fail; + } + if ((spiReceive() & 0XC0) == 0XC0) { + type(SD_CARD_TYPE_SDHC); + } + // Discard rest of ocr - contains allowed voltage range. + for (uint8_t i = 0; i < 3; i++) { + spiReceive(); + } + } + chipSelectHigh(); + m_sckDivisor = sckDivisor; + return true; + +fail: + chipSelectHigh(); + return false; +} +//------------------------------------------------------------------------------ +// send command and return error code. Return zero for OK +uint8_t SdSpiCard::cardCommand(uint8_t cmd, uint32_t arg) { + // select card + if (!m_selected) { + chipSelectLow(); + } + // wait if busy + waitNotBusy(SD_WRITE_TIMEOUT); + +#if USE_SD_CRC + // form message + uint8_t buf[6]; + buf[0] = (uint8_t)0x40U | cmd; + buf[1] = (uint8_t)(arg >> 24U); + buf[2] = (uint8_t)(arg >> 16U); + buf[3] = (uint8_t)(arg >> 8U); + buf[4] = (uint8_t)arg; + + // add CRC + buf[5] = CRC7(buf, 5); + + // send message + spiSend(buf, 6); +#else // USE_SD_CRC + // send command + spiSend(cmd | 0x40); + + // send argument + uint8_t *pa = reinterpret_cast(&arg); + for (int8_t i = 3; i >= 0; i--) { + spiSend(pa[i]); + } + + // send CRC - correct for CMD0 with arg zero or CMD8 with arg 0X1AA + spiSend(cmd == CMD0 ? 0X95 : 0X87); +#endif // USE_SD_CRC + + // skip stuff byte for stop read + if (cmd == CMD12) { + spiReceive(); + } + + // wait for response + for (uint8_t i = 0; ((m_status = spiReceive()) & 0X80) && i != 0XFF; i++) { + } + return m_status; +} +//------------------------------------------------------------------------------ +uint32_t SdSpiCard::cardSize() { + csd_t csd; + if (!readCSD(&csd)) { + return 0; + } + if (csd.v1.csd_ver == 0) { + uint8_t read_bl_len = csd.v1.read_bl_len; + uint16_t c_size = (csd.v1.c_size_high << 10) + | (csd.v1.c_size_mid << 2) | csd.v1.c_size_low; + uint8_t c_size_mult = (csd.v1.c_size_mult_high << 1) + | csd.v1.c_size_mult_low; + return (uint32_t)(c_size + 1) << (c_size_mult + read_bl_len - 7); + } else if (csd.v2.csd_ver == 1) { + uint32_t c_size = 0X10000L * csd.v2.c_size_high + 0X100L + * (uint32_t)csd.v2.c_size_mid + csd.v2.c_size_low; + return (c_size + 1) << 10; + } else { + error(SD_CARD_ERROR_BAD_CSD); + return 0; + } +} +//------------------------------------------------------------------------------ +void SdSpiCard::chipSelectHigh() { + if (!m_selected) { + SD_CS_DBG("chipSelectHigh error"); + return; + } + digitalWrite(m_chipSelectPin, HIGH); + // insure MISO goes high impedance + spiSend(0XFF); + spiEndTransaction(); + m_selected = false; +} +//------------------------------------------------------------------------------ +void SdSpiCard::chipSelectLow() { +#if WDT_YIELD_TIME_MICROS + static uint32_t last; + if ((micros() - last) > WDT_YIELD_TIME_MICROS) { + SysCall::yield(); + last = micros(); + } +#endif // WDT_YIELD_TIME_MICROS + if (m_selected) { + SD_CS_DBG("chipSelectLow error"); + return; + } + spiBeginTransaction(m_sckDivisor); + digitalWrite(m_chipSelectPin, LOW); + m_selected = true; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::erase(uint32_t firstBlock, uint32_t lastBlock) { + csd_t csd; + if (!readCSD(&csd)) { + goto fail; + } + // check for single block erase + if (!csd.v1.erase_blk_en) { + // erase size mask + uint8_t m = (csd.v1.sector_size_high << 1) | csd.v1.sector_size_low; + if ((firstBlock & m) != 0 || ((lastBlock + 1) & m) != 0) { + // error card can't erase specified area + error(SD_CARD_ERROR_ERASE_SINGLE_BLOCK); + goto fail; + } + } + if (m_type != SD_CARD_TYPE_SDHC) { + firstBlock <<= 9; + lastBlock <<= 9; + } + if (cardCommand(CMD32, firstBlock) + || cardCommand(CMD33, lastBlock) + || cardCommand(CMD38, 0)) { + error(SD_CARD_ERROR_ERASE); + goto fail; + } + if (!waitNotBusy(SD_ERASE_TIMEOUT)) { + error(SD_CARD_ERROR_ERASE_TIMEOUT); + goto fail; + } + chipSelectHigh(); + return true; + +fail: + chipSelectHigh(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::eraseSingleBlockEnable() { + csd_t csd; + return readCSD(&csd) ? csd.v1.erase_blk_en : false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::isBusy() { + bool rtn = true; + bool selected = m_selected; + if (!selected) { + chipSelectLow(); + } + for (uint8_t i = 0; i < 8; i++) { + if (0XFF == spiReceive()) { + rtn = false; + break; + } + } + if (!selected) { + chipSelectHigh(); + } + return rtn; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readBlock(uint32_t blockNumber, uint8_t* dst) { + SD_TRACE("RB", blockNumber); + // use address if not SDHC card + if (type() != SD_CARD_TYPE_SDHC) { + blockNumber <<= 9; + } + if (cardCommand(CMD17, blockNumber)) { + error(SD_CARD_ERROR_CMD17); + goto fail; + } + if (!readData(dst, 512)) { + goto fail; + } + chipSelectHigh(); + return true; + +fail: + chipSelectHigh(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readBlocks(uint32_t block, uint8_t* dst, size_t count) { + if (!readStart(block)) { + return false; + } + for (uint16_t b = 0; b < count; b++, dst += 512) { + if (!readData(dst, 512)) { + return false; + } + } + return readStop(); +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readData(uint8_t *dst) { + if (!readData(dst, 512)) { + return false; + } + return true; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readData(uint8_t* dst, size_t count) { +#if USE_SD_CRC + uint16_t crc; +#endif // USE_SD_CRC + // wait for start block token + unsigned t0 = millis(); + while ((m_status = spiReceive()) == 0XFF) { + if (((unsigned)millis() - t0) > SD_READ_TIMEOUT) { + error(SD_CARD_ERROR_READ_TIMEOUT); + goto fail; + } + } + if (m_status != DATA_START_BLOCK) { + error(SD_CARD_ERROR_READ); + goto fail; + } + // transfer data + if ((m_status = spiReceive(dst, count))) { + error(SD_CARD_ERROR_SPI_DMA); + goto fail; + } + +#if USE_SD_CRC + // get crc + crc = (spiReceive() << 8) | spiReceive(); + if (crc != CRC_CCITT(dst, count)) { + error(SD_CARD_ERROR_READ_CRC); + goto fail; + } +#else + // discard crc + spiReceive(); + spiReceive(); +#endif // USE_SD_CRC + return true; + +fail: + chipSelectHigh(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readOCR(uint32_t* ocr) { + uint8_t *p = reinterpret_cast(ocr); + if (cardCommand(CMD58, 0)) { + error(SD_CARD_ERROR_CMD58); + goto fail; + } + for (uint8_t i = 0; i < 4; i++) { + p[3 - i] = spiReceive(); + } + + chipSelectHigh(); + return true; + +fail: + chipSelectHigh(); + return false; +} +//------------------------------------------------------------------------------ +/** read CID or CSR register */ +bool SdSpiCard::readRegister(uint8_t cmd, void* buf) { + uint8_t* dst = reinterpret_cast(buf); + if (cardCommand(cmd, 0)) { + error(SD_CARD_ERROR_READ_REG); + goto fail; + } + if (!readData(dst, 16)) { + goto fail; + } + chipSelectHigh(); + return true; + +fail: + chipSelectHigh(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readStart(uint32_t blockNumber) { + SD_TRACE("RS", blockNumber); + if (type() != SD_CARD_TYPE_SDHC) { + blockNumber <<= 9; + } + if (cardCommand(CMD18, blockNumber)) { + error(SD_CARD_ERROR_CMD18); + goto fail; + } + return true; + +fail: + chipSelectHigh(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readStop() { + if (cardCommand(CMD12, 0)) { + error(SD_CARD_ERROR_CMD12); + goto fail; + } + chipSelectHigh(); + return true; + +fail: + chipSelectHigh(); + return false; +} +//------------------------------------------------------------------------------ +// wait for card to go not busy +bool SdSpiCard::waitNotBusy(uint16_t timeoutMillis) { + unsigned t0 = millis(); + while (spiReceive() != 0XFF) { + if (((unsigned)millis() - t0) >= timeoutMillis) { + goto fail; + } + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::writeBlock(uint32_t blockNumber, const uint8_t* src) { + SD_TRACE("WB", blockNumber); + // use address if not SDHC card + if (type() != SD_CARD_TYPE_SDHC) { + blockNumber <<= 9; + } + if (cardCommand(CMD24, blockNumber)) { + error(SD_CARD_ERROR_CMD24); + goto fail; + } + if (!writeData(DATA_START_BLOCK, src)) { + goto fail; + } + +#define CHECK_PROGRAMMING 0 +#if CHECK_PROGRAMMING + // wait for flash programming to complete + if (!waitNotBusy(SD_WRITE_TIMEOUT)) { + error(SD_CARD_ERROR_WRITE_TIMEOUT); + goto fail; + } + // response is r2 so get and check two bytes for nonzero + if (cardCommand(CMD13, 0) || spiReceive()) { + error(SD_CARD_ERROR_WRITE_PROGRAMMING); + goto fail; + } +#endif // CHECK_PROGRAMMING + + chipSelectHigh(); + return true; + +fail: + chipSelectHigh(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::writeBlocks(uint32_t block, const uint8_t* src, size_t count) { + if (!writeStart(block, count)) { + goto fail; + } + for (size_t b = 0; b < count; b++, src += 512) { + if (!writeData(src)) { + goto fail; + } + } + return writeStop(); + + fail: + chipSelectHigh(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::writeData(const uint8_t* src) { + // wait for previous write to finish + if (!waitNotBusy(SD_WRITE_TIMEOUT)) { + error(SD_CARD_ERROR_WRITE_TIMEOUT); + goto fail; + } + if (!writeData(WRITE_MULTIPLE_TOKEN, src)) { + goto fail; + } + return true; + +fail: + chipSelectHigh(); + return false; +} +//------------------------------------------------------------------------------ +// send one block of data for write block or write multiple blocks +bool SdSpiCard::writeData(uint8_t token, const uint8_t* src) { +#if USE_SD_CRC + uint16_t crc = CRC_CCITT(src, 512); +#else // USE_SD_CRC + uint16_t crc = 0XFFFF; +#endif // USE_SD_CRC + spiSend(token); + spiSend(src, 512); + spiSend(crc >> 8); + spiSend(crc & 0XFF); + + m_status = spiReceive(); + if ((m_status & DATA_RES_MASK) != DATA_RES_ACCEPTED) { + error(SD_CARD_ERROR_WRITE); + goto fail; + } + return true; + +fail: + chipSelectHigh(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::writeStart(uint32_t blockNumber, uint32_t eraseCount) { + SD_TRACE("WS", blockNumber); + // send pre-erase count + if (cardAcmd(ACMD23, eraseCount)) { + error(SD_CARD_ERROR_ACMD23); + goto fail; + } + // use address if not SDHC card + if (type() != SD_CARD_TYPE_SDHC) { + blockNumber <<= 9; + } + if (cardCommand(CMD25, blockNumber)) { + error(SD_CARD_ERROR_CMD25); + goto fail; + } + return true; + +fail: + chipSelectHigh(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::writeStop() { + if (!waitNotBusy(SD_WRITE_TIMEOUT)) { + goto fail; + } + spiSend(STOP_TRAN_TOKEN); + if (!waitNotBusy(SD_WRITE_TIMEOUT)) { + goto fail; + } + chipSelectHigh(); + return true; + +fail: + error(SD_CARD_ERROR_STOP_TRAN); + chipSelectHigh(); + return false; +} diff --git a/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpiCard.h b/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpiCard.h new file mode 100644 index 0000000..15df6e1 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpiCard.h @@ -0,0 +1,329 @@ +/* Arduino SdSpiCard Library + * Copyright (C) 2012 by William Greiman + * + * This file is part of the Arduino SdSpiCard Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino SdSpiCard Library. If not, see + * . + */ +#ifndef SpiCard_h +#define SpiCard_h +/** + * \file + * \brief SdSpiCard class for V2 SD/SDHC cards + */ +#include "../SystemInclude.h" +#include "../SdFatConfig.h" +#include "SdInfo.h" +#include "SdSpi.h" +//============================================================================== +/** + * \class SdSpiCard + * \brief Raw access to SD and SDHC flash memory cards via SPI protocol. + */ +class SdSpiCard { + public: + /** typedef for SPI class. */ +#if SD_SPI_CONFIGURATION < 3 + typedef SpiDefault_t m_spi_t; +#else // SD_SPI_CONFIGURATION < 3 + typedef SdSpiBase m_spi_t; +#endif // SD_SPI_CONFIGURATION < 3 + /** Construct an instance of SdSpiCard. */ + SdSpiCard() : m_selected(false), + m_errorCode(SD_CARD_ERROR_INIT_NOT_CALLED), m_type(0) {} + /** Initialize the SD card. + * \param[in] spi SPI object. + * \param[in] chipSelectPin SD chip select pin. + * \param[in] sckDivisor SPI clock divisor. + * \return true for success else false. + */ + bool begin(m_spi_t* spi, uint8_t chipSelectPin = SS, + uint8_t sckDivisor = SPI_FULL_SPEED); + /** + * Determine the size of an SD flash memory card. + * + * \return The number of 512 byte data blocks in the card + * or zero if an error occurs. + */ + uint32_t cardSize(); + /** Set the SD chip select pin high, send a dummy byte, and call SPI endTransaction. + * + * This function should only be called by programs doing raw I/O to the SD. + */ + void chipSelectHigh(); + /** Set the SD chip select pin low and call SPI beginTransaction. + * + * This function should only be called by programs doing raw I/O to the SD. + */ + void chipSelectLow(); + /** Erase a range of blocks. + * + * \param[in] firstBlock The address of the first block in the range. + * \param[in] lastBlock The address of the last block in the range. + * + * \note This function requests the SD card to do a flash erase for a + * range of blocks. The data on the card after an erase operation is + * either 0 or 1, depends on the card vendor. The card must support + * single block erase. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool erase(uint32_t firstBlock, uint32_t lastBlock); + /** Determine if card supports single block erase. + * + * \return true is returned if single block erase is supported. + * false is returned if single block erase is not supported. + */ + bool eraseSingleBlockEnable(); + /** + * Set SD error code. + * \param[in] code value for error code. + */ + void error(uint8_t code) { + m_errorCode = code; + } + /** + * \return code for the last error. See SdSpiCard.h for a list of error codes. + */ + int errorCode() const { + return m_errorCode; + } + /** \return error data for last error. */ + int errorData() const { + return m_status; + } + /** + * Check for busy. MISO low indicates the card is busy. + * + * \return true if busy else false. + */ + bool isBusy(); + /** + * Read a 512 byte block from an SD card. + * + * \param[in] block Logical block to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool readBlock(uint32_t block, uint8_t* dst); + /** + * Read multiple 512 byte blocks from an SD card. + * + * \param[in] block Logical block to be read. + * \param[in] count Number of blocks to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool readBlocks(uint32_t block, uint8_t* dst, size_t count); + /** + * Read a card's CID register. The CID contains card identification + * information such as Manufacturer ID, Product name, Product serial + * number and Manufacturing date. + * + * \param[out] cid pointer to area for returned data. + * + * \return true for success or false for failure. + */ + bool readCID(cid_t* cid) { + return readRegister(CMD10, cid); + } + /** + * Read a card's CSD register. The CSD contains Card-Specific Data that + * provides information regarding access to the card's contents. + * + * \param[out] csd pointer to area for returned data. + * + * \return true for success or false for failure. + */ + bool readCSD(csd_t* csd) { + return readRegister(CMD9, csd); + } + /** Read one data block in a multiple block read sequence + * + * \param[out] dst Pointer to the location for the data to be read. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool readData(uint8_t *dst); + /** Read OCR register. + * + * \param[out] ocr Value of OCR register. + * \return true for success else false. + */ + bool readOCR(uint32_t* ocr); + /** Start a read multiple blocks sequence. + * + * \param[in] blockNumber Address of first block in sequence. + * + * \note This function is used with readData() and readStop() for optimized + * multiple block reads. SPI chipSelect must be low for the entire sequence. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool readStart(uint32_t blockNumber); + /** End a read multiple blocks sequence. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool readStop(); + /** Return SCK divisor. + * + * \return Requested SCK divisor. + */ + uint8_t sckDivisor() { + return m_sckDivisor; + } + /** \return the SD chip select status, true if slected else false. */ + bool selected() {return m_selected;} + /** Set SCK divisor. + * \param[in] sckDivisor value for divisor. + */ + void setSckDivisor(uint8_t sckDivisor) { + m_sckDivisor = sckDivisor; + } + /** Return the card type: SD V1, SD V2 or SDHC + * \return 0 - SD V1, 1 - SD V2, or 3 - SDHC. + */ + int type() const { + return m_type; + } + /** + * Writes a 512 byte block to an SD card. + * + * \param[in] blockNumber Logical block to be written. + * \param[in] src Pointer to the location of the data to be written. + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool writeBlock(uint32_t blockNumber, const uint8_t* src); + /** + * Write multiple 512 byte blocks to an SD card. + * + * \param[in] block Logical block to be written. + * \param[in] count Number of blocks to be written. + * \param[in] src Pointer to the location of the data to be written. + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool writeBlocks(uint32_t block, const uint8_t* src, size_t count); + /** Write one data block in a multiple block write sequence. + * \param[in] src Pointer to the location of the data to be written. + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool writeData(const uint8_t* src); + /** Start a write multiple blocks sequence. + * + * \param[in] blockNumber Address of first block in sequence. + * \param[in] eraseCount The number of blocks to be pre-erased. + * + * \note This function is used with writeData() and writeStop() + * for optimized multiple block writes. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool writeStart(uint32_t blockNumber, uint32_t eraseCount); + /** End a write multiple blocks sequence. + * + * \return The value true is returned for success and + * the value false is returned for failure. + */ + bool writeStop(); + + private: + // private functions + uint8_t cardAcmd(uint8_t cmd, uint32_t arg) { + cardCommand(CMD55, 0); + return cardCommand(cmd, arg); + } + uint8_t cardCommand(uint8_t cmd, uint32_t arg); + bool readData(uint8_t* dst, size_t count); + bool readRegister(uint8_t cmd, void* buf); + void type(uint8_t value) { + m_type = value; + } + bool waitNotBusy(uint16_t timeoutMillis); + bool writeData(uint8_t token, const uint8_t* src); + void spiBegin(uint8_t chipSelectPin) { + m_spi->begin(chipSelectPin); + } + void spiBeginTransaction(uint8_t spiDivisor) { + m_spi->beginTransaction(spiDivisor); + } + void spiEndTransaction() { + m_spi->endTransaction(); + } + uint8_t spiReceive() { + return m_spi->receive(); + } + uint8_t spiReceive(uint8_t* buf, size_t n) { + return m_spi->receive(buf, n); + } + void spiSend(uint8_t data) { + m_spi->send(data); + } + void spiSend(const uint8_t* buf, size_t n) { + m_spi->send(buf, n); + } + m_spi_t* m_spi; + bool m_selected; + uint8_t m_chipSelectPin; + uint8_t m_errorCode; + uint8_t m_sckDivisor; + uint8_t m_status; + uint8_t m_type; +}; +//============================================================================== +/** + * \class Sd2Card + * \brief Raw access to SD and SDHC card using default SPI library. + */ +class Sd2Card : public SdSpiCard { + public: + /** Initialize the SD card. + * \param[in] chipSelectPin SD chip select pin. + * \param[in] sckDivisor SPI clock divisor. + * \return true for success else false. + */ + bool begin(uint8_t chipSelectPin = SS, uint8_t sckDivisor = 2) { + return SdSpiCard::begin(&m_spi, chipSelectPin, sckDivisor); + } + /** Initialize the SD card. Obsolete form. + * \param[in] chipSelectPin SD chip select pin. + * \param[in] sckDivisor SPI clock divisor. + * \return true for success else false. + */ + bool init(uint8_t sckDivisor = 2, uint8_t chipSelectPin = SS) { + return begin(chipSelectPin, sckDivisor); + } + + private: + bool begin(m_spi_t* spi, uint8_t chipSelectPin = SS, + uint8_t sckDivisor = SPI_FULL_SPEED) { + (void)spi; + (void)chipSelectPin; + (void)sckDivisor; + return false; + } + SpiDefault_t m_spi; +}; +#endif // SpiCard_h diff --git a/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpiESP8266.cpp b/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpiESP8266.cpp new file mode 100644 index 0000000..251ca8d --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpiESP8266.cpp @@ -0,0 +1,102 @@ +/* Arduino SdSpi Library + * Copyright (C) 2016 by William Greiman + * + * STM32F1 code for Maple and Maple Mini support, 2015 by Victor Perez + * + * This file is part of the Arduino SdSpi Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino SdSpi Library. If not, see + * . + */ +#if defined(ESP8266) +#include "SdSpi.h" +//------------------------------------------------------------------------------ +/** Initialize the SPI bus. + * + * \param[in] chipSelectPin SD card chip select pin. + */ +void SdSpi::begin(uint8_t chipSelectPin) { + pinMode(chipSelectPin, OUTPUT); + digitalWrite(chipSelectPin, HIGH); + SPI.begin(); +} +//------------------------------------------------------------------------------ +/** Set SPI options for access to SD/SDHC cards. + * + * \param[in] divisor SCK clock divider relative to the max SPI clock. + */ +void SdSpi::beginTransaction(uint8_t divisor) { + const uint32_t F_SPI_MAX = 80000000; +#if ENABLE_SPI_TRANSACTIONS + // Note: ESP8266 beginTransaction does not protect for interrupts. + SPISettings settings(F_SPI_MAX/(divisor ? divisor : 1), MSBFIRST, SPI_MODE0); + SPI.beginTransaction(settings); +#else // ENABLE_SPI_TRANSACTIONS + // Note: ESP8266 beginTransaction is the same as following code. + SPI.setFrequency(F_SPI_MAX/(divisor ? divisor : 1)); + SPI.setBitOrder(MSBFIRST); + SPI.setDataMode(SPI_MODE0); +#endif // ENABLE_SPI_TRANSACTIONS +} +//------------------------------------------------------------------------------ +void SdSpi::endTransaction() { +#if ENABLE_SPI_TRANSACTIONS + // Note: endTransaction is an empty function on ESP8266. + SPI.endTransaction(); +#endif // ENABLE_SPI_TRANSACTIONS +} +//------------------------------------------------------------------------------ +/** Receive a byte. + * + * \return The byte. + */ +uint8_t SdSpi::receive() { + return SPI.transfer(0XFF); +} +//------------------------------------------------------------------------------ +/** Receive multiple bytes. + * + * \param[out] buf Buffer to receive the data. + * \param[in] n Number of bytes to receive. + * + * \return Zero for no error or nonzero error code. + */ +uint8_t SdSpi::receive(uint8_t* buf, size_t n) { + // Works without 32-bit alignment of buf. + SPI.transferBytes(0, buf, n); + return 0; +} +//------------------------------------------------------------------------------ +/** Send a byte. + * + * \param[in] b Byte to send + */ +void SdSpi::send(uint8_t b) { + SPI.transfer(b); +} +//------------------------------------------------------------------------------ +/** Send multiple bytes. + * + * \param[in] buf Buffer for data to be sent. + * \param[in] n Number of bytes to send. + */ +void SdSpi::send(const uint8_t* buf , size_t n) { + // Adjust to 32-bit alignment. + while ((reinterpret_cast(buf) & 0X3) && n) { + SPI.transfer(*buf++); + n--; + } + SPI.transferBytes(const_cast(buf), 0, n); +} +#endif // defined(ESP8266) diff --git a/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpiParticle.cpp b/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpiParticle.cpp new file mode 100644 index 0000000..4fdca4f --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpiParticle.cpp @@ -0,0 +1,110 @@ +/* Arduino SdFat Library + * Copyright (C) 2016 by William Greiman + * + * This file is part of the Arduino SdFat Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino SdFat Library. If not, see + * . + */ +#include "SdSpi.h" +#if defined(PLATFORM_ID) + +static uint32_t bugDelay = 0; // fix for SPI DMA bug. + +static volatile bool SPI_DMA_TransferCompleted = false; + +static SPIClass* const spiPtr[] = { + &SPI +#if Wiring_SPI1 + , &SPI1 +#if Wiring_SPI2 + , &SPI2 +#endif // Wiring_SPI2 +#endif // Wiring_SPI1 +}; +#if SPI_INTERFACE_COUNT == 1 +const uint8_t m_spiIf = 0; +#endif +//----------------------------------------------------------------------------- +void SD_SPI_DMA_TransferComplete_Callback(void) { + SPI_DMA_TransferCompleted = true; +} +//------------------------------------------------------------------------------ +void SdSpi::begin(uint8_t chipSelectPin) { + spiPtr[m_spiIf]->begin(chipSelectPin); +} +//------------------------------------------------------------------------------ +void SdSpi::beginTransaction(uint8_t divisor) { + spiPtr[m_spiIf]->setBitOrder(MSBFIRST); + spiPtr[m_spiIf]->setDataMode(SPI_MODE0); +#ifndef SPI_CLOCK_DIV128 + spiPtr[m_spiIf]->setClockDivider(divisor); +#else // SPI_CLOCK_DIV128 + int v; + if (divisor <= 2) { + v = SPI_CLOCK_DIV2; + } else if (divisor <= 4) { + v = SPI_CLOCK_DIV4; + } else if (divisor <= 8) { + v = SPI_CLOCK_DIV8; + } else if (divisor <= 16) { + v = SPI_CLOCK_DIV16; + } else if (divisor <= 32) { + v = SPI_CLOCK_DIV32; + } else if (divisor <= 64) { + v = SPI_CLOCK_DIV64; + } else { + v = SPI_CLOCK_DIV128; + } + spiPtr[m_spiIf]->setClockDivider(v); +#endif // SPI_CLOCK_DIV128 + // delay for SPI transfer done callback too soon bug. + bugDelay = 24*divisor*(1 + m_spiIf)/60; +} +//----------------------------------------------------------------------------- +void SdSpi::endTransaction() { +} +//----------------------------------------------------------------------------- +/** SPI receive a byte */ +uint8_t SdSpi::receive() { + return spiPtr[m_spiIf]->transfer(0xFF); +} +//----------------------------------------------------------------------------- +uint8_t SdSpi::receive(uint8_t* buf, size_t n) { + SPI_DMA_TransferCompleted = false; + spiPtr[m_spiIf]->transfer(0, buf, n, SD_SPI_DMA_TransferComplete_Callback); + while (!SPI_DMA_TransferCompleted) {} + if (bugDelay) { + delayMicroseconds(bugDelay); + } + return 0; +} +//----------------------------------------------------------------------------- +/** SPI send a byte */ +void SdSpi::send(uint8_t b) { + spiPtr[m_spiIf]->transfer(b); +} +//----------------------------------------------------------------------------- +void SdSpi::send(const uint8_t* buf , size_t n) { + SPI_DMA_TransferCompleted = false; + + spiPtr[m_spiIf]->transfer(const_cast(buf), 0, n, + SD_SPI_DMA_TransferComplete_Callback); + + while (!SPI_DMA_TransferCompleted) {} + if (bugDelay) { + delayMicroseconds(bugDelay); + } +} +#endif // defined(PLATFORM_ID) diff --git a/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpiSAM3X.cpp b/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpiSAM3X.cpp new file mode 100644 index 0000000..9e8c6ae --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpiSAM3X.cpp @@ -0,0 +1,229 @@ +/* Arduino SdSpi Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the Arduino SdSpi Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino SdSpi Library. If not, see + * . + */ +#include "SdSpi.h" +#if defined(__SAM3X8E__) || defined(__SAM3X8H__) +/** Use SAM3X DMAC if nonzero */ +#define USE_SAM3X_DMAC 1 +/** Use extra Bus Matrix arbitration fix if nonzero */ +#define USE_SAM3X_BUS_MATRIX_FIX 0 +/** Time in ms for DMA receive timeout */ +#define SAM3X_DMA_TIMEOUT 100 +/** chip select register number */ +#define SPI_CHIP_SEL 3 +/** DMAC receive channel */ +#define SPI_DMAC_RX_CH 1 +/** DMAC transmit channel */ +#define SPI_DMAC_TX_CH 0 +/** DMAC Channel HW Interface Number for SPI TX. */ +#define SPI_TX_IDX 1 +/** DMAC Channel HW Interface Number for SPI RX. */ +#define SPI_RX_IDX 2 +//------------------------------------------------------------------------------ +/** Disable DMA Controller. */ +static void dmac_disable() { + DMAC->DMAC_EN &= (~DMAC_EN_ENABLE); +} +/** Enable DMA Controller. */ +static void dmac_enable() { + DMAC->DMAC_EN = DMAC_EN_ENABLE; +} +/** Disable DMA Channel. */ +static void dmac_channel_disable(uint32_t ul_num) { + DMAC->DMAC_CHDR = DMAC_CHDR_DIS0 << ul_num; +} +/** Enable DMA Channel. */ +static void dmac_channel_enable(uint32_t ul_num) { + DMAC->DMAC_CHER = DMAC_CHER_ENA0 << ul_num; +} +/** Poll for transfer complete. */ +static bool dmac_channel_transfer_done(uint32_t ul_num) { + return (DMAC->DMAC_CHSR & (DMAC_CHSR_ENA0 << ul_num)) ? false : true; +} +//------------------------------------------------------------------------------ +void SdSpi::begin(uint8_t chipSelectPin) { + pinMode(chipSelectPin, OUTPUT); + digitalWrite(chipSelectPin, HIGH); + PIO_Configure( + g_APinDescription[PIN_SPI_MOSI].pPort, + g_APinDescription[PIN_SPI_MOSI].ulPinType, + g_APinDescription[PIN_SPI_MOSI].ulPin, + g_APinDescription[PIN_SPI_MOSI].ulPinConfiguration); + PIO_Configure( + g_APinDescription[PIN_SPI_MISO].pPort, + g_APinDescription[PIN_SPI_MISO].ulPinType, + g_APinDescription[PIN_SPI_MISO].ulPin, + g_APinDescription[PIN_SPI_MISO].ulPinConfiguration); + PIO_Configure( + g_APinDescription[PIN_SPI_SCK].pPort, + g_APinDescription[PIN_SPI_SCK].ulPinType, + g_APinDescription[PIN_SPI_SCK].ulPin, + g_APinDescription[PIN_SPI_SCK].ulPinConfiguration); + pmc_enable_periph_clk(ID_SPI0); +#if USE_SAM3X_DMAC + pmc_enable_periph_clk(ID_DMAC); + dmac_disable(); + DMAC->DMAC_GCFG = DMAC_GCFG_ARB_CFG_FIXED; + dmac_enable(); +#if USE_SAM3X_BUS_MATRIX_FIX + MATRIX->MATRIX_WPMR = 0x4d415400; + MATRIX->MATRIX_MCFG[1] = 1; + MATRIX->MATRIX_MCFG[2] = 1; + MATRIX->MATRIX_SCFG[0] = 0x01000010; + MATRIX->MATRIX_SCFG[1] = 0x01000010; + MATRIX->MATRIX_SCFG[7] = 0x01000010; +#endif // USE_SAM3X_BUS_MATRIX_FIX +#endif // USE_SAM3X_DMAC +} +//------------------------------------------------------------------------------ +// start RX DMA +static void spiDmaRX(uint8_t* dst, uint16_t count) { + dmac_channel_disable(SPI_DMAC_RX_CH); + DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_SADDR = (uint32_t)&SPI0->SPI_RDR; + DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_DADDR = (uint32_t)dst; + DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_DSCR = 0; + DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_CTRLA = count | + DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_DST_WIDTH_BYTE; + DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_CTRLB = DMAC_CTRLB_SRC_DSCR | + DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_PER2MEM_DMA_FC | + DMAC_CTRLB_SRC_INCR_FIXED | DMAC_CTRLB_DST_INCR_INCREMENTING; + DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_CFG = DMAC_CFG_SRC_PER(SPI_RX_IDX) | + DMAC_CFG_SRC_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ASAP_CFG; + dmac_channel_enable(SPI_DMAC_RX_CH); +} +//------------------------------------------------------------------------------ +// start TX DMA +static void spiDmaTX(const uint8_t* src, uint16_t count) { + static uint8_t ff = 0XFF; + uint32_t src_incr = DMAC_CTRLB_SRC_INCR_INCREMENTING; + if (!src) { + src = &ff; + src_incr = DMAC_CTRLB_SRC_INCR_FIXED; + } + dmac_channel_disable(SPI_DMAC_TX_CH); + DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_SADDR = (uint32_t)src; + DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_DADDR = (uint32_t)&SPI0->SPI_TDR; + DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_DSCR = 0; + DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CTRLA = count | + DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_DST_WIDTH_BYTE; + + DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CTRLB = DMAC_CTRLB_SRC_DSCR | + DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_MEM2PER_DMA_FC | + src_incr | DMAC_CTRLB_DST_INCR_FIXED; + + DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CFG = DMAC_CFG_DST_PER(SPI_TX_IDX) | + DMAC_CFG_DST_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ALAP_CFG; + + dmac_channel_enable(SPI_DMAC_TX_CH); +} +//------------------------------------------------------------------------------ +// initialize SPI controller +void SdSpi::beginTransaction(uint8_t sckDivisor) { +#if ENABLE_SPI_TRANSACTIONS + SPI.beginTransaction(SPISettings()); +#endif // ENABLE_SPI_TRANSACTIONS + uint8_t scbr = sckDivisor; + Spi* pSpi = SPI0; + // disable SPI + pSpi->SPI_CR = SPI_CR_SPIDIS; + // reset SPI + pSpi->SPI_CR = SPI_CR_SWRST; + // no mode fault detection, set master mode + pSpi->SPI_MR = SPI_PCS(SPI_CHIP_SEL) | SPI_MR_MODFDIS | SPI_MR_MSTR; + // mode 0, 8-bit, + pSpi->SPI_CSR[SPI_CHIP_SEL] = SPI_CSR_SCBR(scbr) | SPI_CSR_NCPHA; + // enable SPI + pSpi->SPI_CR |= SPI_CR_SPIEN; +} +//------------------------------------------------------------------------------ +void SdSpi::endTransaction() { +#if ENABLE_SPI_TRANSACTIONS + SPI.endTransaction(); +#endif // ENABLE_SPI_TRANSACTIONS +} +//------------------------------------------------------------------------------ +static inline uint8_t spiTransfer(uint8_t b) { + Spi* pSpi = SPI0; + + pSpi->SPI_TDR = b; + while ((pSpi->SPI_SR & SPI_SR_RDRF) == 0) {} + b = pSpi->SPI_RDR; + return b; +} +//------------------------------------------------------------------------------ +/** SPI receive a byte */ +uint8_t SdSpi::receive() { + return spiTransfer(0XFF); +} +//------------------------------------------------------------------------------ +/** SPI receive multiple bytes */ +uint8_t SdSpi::receive(uint8_t* buf, size_t n) { + Spi* pSpi = SPI0; + int rtn = 0; +#if USE_SAM3X_DMAC + // clear overrun error + uint32_t s = pSpi->SPI_SR; + + spiDmaRX(buf, n); + spiDmaTX(0, n); + + uint32_t m = millis(); + while (!dmac_channel_transfer_done(SPI_DMAC_RX_CH)) { + if ((millis() - m) > SAM3X_DMA_TIMEOUT) { + dmac_channel_disable(SPI_DMAC_RX_CH); + dmac_channel_disable(SPI_DMAC_TX_CH); + rtn = 2; + break; + } + } + if (pSpi->SPI_SR & SPI_SR_OVRES) { + rtn |= 1; + } +#else // USE_SAM3X_DMAC + for (size_t i = 0; i < n; i++) { + pSpi->SPI_TDR = 0XFF; + while ((pSpi->SPI_SR & SPI_SR_RDRF) == 0) {} + buf[i] = pSpi->SPI_RDR; + } +#endif // USE_SAM3X_DMAC + return rtn; +} +//------------------------------------------------------------------------------ +/** SPI send a byte */ +void SdSpi::send(uint8_t b) { + spiTransfer(b); +} +//------------------------------------------------------------------------------ +void SdSpi::send(const uint8_t* buf , size_t n) { + Spi* pSpi = SPI0; +#if USE_SAM3X_DMAC + spiDmaTX(buf, n); + while (!dmac_channel_transfer_done(SPI_DMAC_TX_CH)) {} +#else // #if USE_SAM3X_DMAC + while ((pSpi->SPI_SR & SPI_SR_TXEMPTY) == 0) {} + for (size_t i = 0; i < n; i++) { + pSpi->SPI_TDR = buf[i]; + while ((pSpi->SPI_SR & SPI_SR_TDRE) == 0) {} + } +#endif // #if USE_SAM3X_DMAC + while ((pSpi->SPI_SR & SPI_SR_TXEMPTY) == 0) {} + // leave RDR empty + uint8_t b = pSpi->SPI_RDR; +} +#endif // defined(__SAM3X8E__) || defined(__SAM3X8H__) diff --git a/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpiSTM32F1.cpp b/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpiSTM32F1.cpp new file mode 100644 index 0000000..2dd7c22 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpiSTM32F1.cpp @@ -0,0 +1,125 @@ +/* Arduino SdSpi Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the Arduino SdSpi Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino SdSpi Library. If not, see + * . + */ +#if defined(__STM32F1__) +#include "SdSpi.h" +#define USE_STM32F1_DMAC 1 +//------------------------------------------------------------------------------ +/** Initialize the SPI bus. + * + * \param[in] chipSelectPin SD card chip select pin. + */ +void SdSpi::begin(uint8_t chipSelectPin) { + pinMode(chipSelectPin, OUTPUT); + digitalWrite(chipSelectPin, HIGH); + SPI.begin(); +} +//------------------------------------------------------------------------------ +/** Set SPI options for access to SD/SDHC cards. + * + * \param[in] divisor SCK clock divider relative to the APB1 or APB2 clock. + */ +void SdSpi::beginTransaction(uint8_t divisor) { +#if ENABLE_SPI_TRANSACTIONS + // Correct divisor will be set below. + SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0)); +#endif // ENABLE_SPI_TRANSACTIONS + uint32_t br; // Baud rate control field in SPI_CR1. + if (divisor <= 2) { + br = SPI_CLOCK_DIV2; + } else if (divisor <= 4) { + br = SPI_CLOCK_DIV4; + } else if (divisor <= 8) { + br = SPI_CLOCK_DIV8; + } else if (divisor <= 16) { + br = SPI_CLOCK_DIV16; + } else if (divisor <= 32) { + br = SPI_CLOCK_DIV32; + } else if (divisor <= 64) { + br = SPI_CLOCK_DIV64; + } else if (divisor <= 128) { + br = SPI_CLOCK_DIV128; + } else { + br = SPI_CLOCK_DIV256; + } + SPI.setClockDivider(br); +#if !ENABLE_SPI_TRANSACTIONS + SPI.setBitOrder(MSBFIRST); + SPI.setDataMode(SPI_MODE0); +#endif // !ENABLE_SPI_TRANSACTIONS +} +//------------------------------------------------------------------------------ +/** + * End SPI transaction. + */ +void SdSpi::endTransaction() { +#if ENABLE_SPI_TRANSACTIONS + SPI.endTransaction(); +#endif // ENABLE_SPI_TRANSACTIONS +} +//------------------------------------------------------------------------------ +/** Receive a byte. + * + * \return The byte. + */ +uint8_t SdSpi::receive() { + return SPI.transfer(0XFF); +} +//------------------------------------------------------------------------------ +/** Receive multiple bytes. + * + * \param[out] buf Buffer to receive the data. + * \param[in] n Number of bytes to receive. + * + * \return Zero for no error or nonzero error code. + */ +uint8_t SdSpi::receive(uint8_t* buf, size_t n) { + int rtn = 0; +#if USE_STM32F1_DMAC + rtn = SPI.dmaTransfer(0, const_cast(buf), n); +#else // USE_STM32F1_DMAC +// SPI.read(buf, n); + for (size_t i = 0; i < n; i++) { + buf[i] = SPI.transfer(0XFF); + } +#endif // USE_STM32F1_DMAC + return rtn; +} +//------------------------------------------------------------------------------ +/** Send a byte. + * + * \param[in] b Byte to send + */ +void SdSpi::send(uint8_t b) { + SPI.transfer(b); +} +//------------------------------------------------------------------------------ +/** Send multiple bytes. + * + * \param[in] buf Buffer for data to be sent. + * \param[in] n Number of bytes to send. + */ +void SdSpi::send(const uint8_t* buf , size_t n) { +#if USE_STM32F1_DMAC + SPI.dmaSend(const_cast(buf), n); +#else // #if USE_STM32F1_DMAC + SPI.write(buf, n); +#endif // USE_STM32F1_DMAC +} +#endif // USE_NATIVE_STM32F1_SPI diff --git a/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpiTeensy3.cpp b/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpiTeensy3.cpp new file mode 100644 index 0000000..d669395 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdSpiCard/SdSpiTeensy3.cpp @@ -0,0 +1,316 @@ +/* Arduino SdSpi Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the Arduino SdSpi Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino SdSpi Library. If not, see + * . + */ +#include "SdSpi.h" +#if defined(__arm__) && defined(CORE_TEENSY) +// SPI definitions +#include "kinetis.h" +#ifdef KINETISK +// use 16-bit frame if SPI_USE_8BIT_FRAME is zero +#define SPI_USE_8BIT_FRAME 0 +// Limit initial fifo to three entries to avoid fifo overrun +#define SPI_INITIAL_FIFO_DEPTH 3 +// define some symbols that are not in mk20dx128.h +#ifndef SPI_SR_RXCTR +#define SPI_SR_RXCTR 0XF0 +#endif // SPI_SR_RXCTR +#ifndef SPI_PUSHR_CONT +#define SPI_PUSHR_CONT 0X80000000 +#endif // SPI_PUSHR_CONT +#ifndef SPI_PUSHR_CTAS +#define SPI_PUSHR_CTAS(n) (((n) & 7) << 28) +#endif // SPI_PUSHR_CTAS +//------------------------------------------------------------------------------ +/** + * initialize SPI pins + */ +void SdSpi::begin(uint8_t chipSelectPin) { + pinMode(chipSelectPin, OUTPUT); + digitalWrite(chipSelectPin, HIGH); + SIM_SCGC6 |= SIM_SCGC6_SPI0; +} +//------------------------------------------------------------------------------ +/** + * Initialize hardware SPI + * + */ +void SdSpi::beginTransaction(uint8_t sckDivisor) { + uint32_t ctar, ctar0, ctar1; +#if ENABLE_SPI_TRANSACTIONS + SPI.beginTransaction(SPISettings()); +#endif // #if ENABLE_SPI_TRANSACTIONS + if (sckDivisor <= 2) { + // 1/2 speed + ctar = SPI_CTAR_DBR | SPI_CTAR_BR(0) | SPI_CTAR_CSSCK(0); + } else if (sckDivisor <= 4) { + // 1/4 speed + ctar = SPI_CTAR_BR(0) | SPI_CTAR_CSSCK(0); + } else if (sckDivisor <= 8) { + // 1/8 speed + ctar = SPI_CTAR_BR(1) | SPI_CTAR_CSSCK(1); + } else if (sckDivisor <= 12) { + // 1/12 speed + ctar = SPI_CTAR_BR(2) | SPI_CTAR_CSSCK(2); + } else if (sckDivisor <= 16) { + // 1/16 speed + ctar = SPI_CTAR_BR(3) | SPI_CTAR_CSSCK(3); + } else if (sckDivisor <= 32) { + // 1/32 speed + ctar = SPI_CTAR_PBR(1) | SPI_CTAR_BR(4) | SPI_CTAR_CSSCK(4); + } else if (sckDivisor <= 64) { + // 1/64 speed + ctar = SPI_CTAR_PBR(1) | SPI_CTAR_BR(5) | SPI_CTAR_CSSCK(5); + } else { + // 1/128 speed + ctar = SPI_CTAR_PBR(1) | SPI_CTAR_BR(6) | SPI_CTAR_CSSCK(6); + } + // CTAR0 - 8 bit transfer + ctar0 = ctar | SPI_CTAR_FMSZ(7); + + // CTAR1 - 16 bit transfer + ctar1 = ctar | SPI_CTAR_FMSZ(15); + + if (SPI0_CTAR0 != ctar0 || SPI0_CTAR1 != ctar1) { + SPI0_MCR = SPI_MCR_MSTR | SPI_MCR_MDIS | SPI_MCR_HALT | SPI_MCR_PCSIS(0x1F); + SPI0_CTAR0 = ctar0; + SPI0_CTAR1 = ctar1; + } + SPI0_MCR = SPI_MCR_MSTR | SPI_MCR_PCSIS(0x1F); + CORE_PIN11_CONFIG = PORT_PCR_DSE | PORT_PCR_MUX(2); + CORE_PIN12_CONFIG = PORT_PCR_MUX(2); + CORE_PIN13_CONFIG = PORT_PCR_DSE | PORT_PCR_MUX(2); +} +//------------------------------------------------------------------------------ +/** SPI receive a byte */ +uint8_t SdSpi::receive() { + SPI0_MCR |= SPI_MCR_CLR_RXF; + SPI0_SR = SPI_SR_TCF; + SPI0_PUSHR = 0xFF; + while (!(SPI0_SR & SPI_SR_TCF)) {} + return SPI0_POPR; +} +//------------------------------------------------------------------------------ +/** SPI receive multiple bytes */ +uint8_t SdSpi::receive(uint8_t* buf, size_t n) { + // clear any data in RX FIFO + SPI0_MCR = SPI_MCR_MSTR | SPI_MCR_CLR_RXF | SPI_MCR_PCSIS(0x1F); +#if SPI_USE_8BIT_FRAME + // initial number of bytes to push into TX FIFO + int nf = n < SPI_INITIAL_FIFO_DEPTH ? n : SPI_INITIAL_FIFO_DEPTH; + for (int i = 0; i < nf; i++) { + SPI0_PUSHR = 0XFF; + } + // limit for pushing dummy data into TX FIFO + uint8_t* limit = buf + n - nf; + while (buf < limit) { + while (!(SPI0_SR & SPI_SR_RXCTR)) {} + SPI0_PUSHR = 0XFF; + *buf++ = SPI0_POPR; + } + // limit for rest of RX data + limit += nf; + while (buf < limit) { + while (!(SPI0_SR & SPI_SR_RXCTR)) {} + *buf++ = SPI0_POPR; + } +#else // SPI_USE_8BIT_FRAME + // use 16 bit frame to avoid TD delay between frames + // get one byte if n is odd + if (n & 1) { + *buf++ = receive(); + n--; + } + // initial number of words to push into TX FIFO + int nf = n/2 < SPI_INITIAL_FIFO_DEPTH ? n/2 : SPI_INITIAL_FIFO_DEPTH; + for (int i = 0; i < nf; i++) { + SPI0_PUSHR = SPI_PUSHR_CONT | SPI_PUSHR_CTAS(1) | 0XFFFF; + } + uint8_t* limit = buf + n - 2*nf; + while (buf < limit) { + while (!(SPI0_SR & SPI_SR_RXCTR)) {} + SPI0_PUSHR = SPI_PUSHR_CONT | SPI_PUSHR_CTAS(1) | 0XFFFF; + uint16_t w = SPI0_POPR; + *buf++ = w >> 8; + *buf++ = w & 0XFF; + } + // limit for rest of RX data + limit += 2*nf; + while (buf < limit) { + while (!(SPI0_SR & SPI_SR_RXCTR)) {} + uint16_t w = SPI0_POPR; + *buf++ = w >> 8; + *buf++ = w & 0XFF; + } +#endif // SPI_USE_8BIT_FRAME + return 0; +} +//------------------------------------------------------------------------------ +/** SPI send a byte */ +void SdSpi::send(uint8_t b) { + SPI0_MCR |= SPI_MCR_CLR_RXF; + SPI0_SR = SPI_SR_TCF; + SPI0_PUSHR = b; + while (!(SPI0_SR & SPI_SR_TCF)) {} +} +//------------------------------------------------------------------------------ +/** SPI send multiple bytes */ +void SdSpi::send(const uint8_t* buf , size_t n) { + // clear any data in RX FIFO + SPI0_MCR = SPI_MCR_MSTR | SPI_MCR_CLR_RXF | SPI_MCR_PCSIS(0x1F); +#if SPI_USE_8BIT_FRAME + // initial number of bytes to push into TX FIFO + int nf = n < SPI_INITIAL_FIFO_DEPTH ? n : SPI_INITIAL_FIFO_DEPTH; + // limit for pushing data into TX fifo + const uint8_t* limit = buf + n; + for (int i = 0; i < nf; i++) { + SPI0_PUSHR = *buf++; + } + // write data to TX FIFO + while (buf < limit) { + while (!(SPI0_SR & SPI_SR_RXCTR)) {} + SPI0_PUSHR = *buf++; + SPI0_POPR; + } + // wait for data to be sent + while (nf) { + while (!(SPI0_SR & SPI_SR_RXCTR)) {} + SPI0_POPR; + nf--; + } +#else // SPI_USE_8BIT_FRAME + // use 16 bit frame to avoid TD delay between frames + // send one byte if n is odd + if (n & 1) { + send(*buf++); + n--; + } + // initial number of words to push into TX FIFO + int nf = n/2 < SPI_INITIAL_FIFO_DEPTH ? n/2 : SPI_INITIAL_FIFO_DEPTH; + // limit for pushing data into TX fifo + const uint8_t* limit = buf + n; + for (int i = 0; i < nf; i++) { + uint16_t w = (*buf++) << 8; + w |= *buf++; + SPI0_PUSHR = SPI_PUSHR_CONT | SPI_PUSHR_CTAS(1) | w; + } + // write data to TX FIFO + while (buf < limit) { + uint16_t w = *buf++ << 8; + w |= *buf++; + while (!(SPI0_SR & SPI_SR_RXCTR)) {} + SPI0_PUSHR = SPI_PUSHR_CONT | SPI_PUSHR_CTAS(1) | w; + SPI0_POPR; + } + // wait for data to be sent + while (nf) { + while (!(SPI0_SR & SPI_SR_RXCTR)) {} + SPI0_POPR; + nf--; + } +#endif // SPI_USE_8BIT_FRAME +} +#else // KINETISK +//============================================================================== +// Use standard SPI library if not KINETISK +/** + * Initialize SPI pins. + */ +void SdSpi::begin(uint8_t chipSelectPin) { + pinMode(chipSelectPin, OUTPUT); + digitalWrite(chipSelectPin, HIGH); + SPI.begin(); +} +/** Set SPI options for access to SD/SDHC cards. + * + * \param[in] divisor SCK clock divider relative to the system clock. + */ +void SdSpi::beginTransaction(uint8_t divisor) { +#if ENABLE_SPI_TRANSACTIONS + SPI.beginTransaction(SPISettings()); +#else // #if ENABLE_SPI_TRANSACTIONS + SPI.setBitOrder(MSBFIRST); + SPI.setDataMode(SPI_MODE0); +#endif // #if ENABLE_SPI_TRANSACTIONS +#ifndef SPI_CLOCK_DIV128 + SPI.setClockDivider(divisor); +#else // SPI_CLOCK_DIV128 + int v; + if (divisor <= 2) { + v = SPI_CLOCK_DIV2; + } else if (divisor <= 4) { + v = SPI_CLOCK_DIV4; + } else if (divisor <= 8) { + v = SPI_CLOCK_DIV8; + } else if (divisor <= 16) { + v = SPI_CLOCK_DIV16; + } else if (divisor <= 32) { + v = SPI_CLOCK_DIV32; + } else if (divisor <= 64) { + v = SPI_CLOCK_DIV64; + } else { + v = SPI_CLOCK_DIV128; + } + SPI.setClockDivider(v); +#endif // SPI_CLOCK_DIV128 +} +/** Receive a byte. + * + * \return The byte. + */ +uint8_t SdSpi::receive() { + return SPI.transfer(0XFF); +} +/** Receive multiple bytes. + * + * \param[out] buf Buffer to receive the data. + * \param[in] n Number of bytes to receive. + * + * \return Zero for no error or nonzero error code. + */ +uint8_t SdSpi::receive(uint8_t* buf, size_t n) { + for (size_t i = 0; i < n; i++) { + buf[i] = SPI.transfer(0XFF); + } + return 0; +} +/** Send a byte. + * + * \param[in] b Byte to send + */ +void SdSpi::send(uint8_t b) { + SPI.transfer(b); +} +/** Send multiple bytes. + * + * \param[in] buf Buffer for data to be sent. + * \param[in] n Number of bytes to send. + */ +void SdSpi::send(const uint8_t* buf , size_t n) { + for (size_t i = 0; i < n; i++) { + SPI.transfer(buf[i]); + } +} +#endif // KINETISK +//------------------------------------------------------------------------------ +void SdSpi::endTransaction() { +#if ENABLE_SPI_TRANSACTIONS + SPI.endTransaction(); +#endif // ENABLE_SPI_TRANSACTIONS +} +#endif // defined(__arm__) && defined(CORE_TEENSY) diff --git a/Firmware_V2/src/libraries/SdFat/SdSpiCard/SoftSPI.h b/Firmware_V2/src/libraries/SdFat/SdSpiCard/SoftSPI.h new file mode 100644 index 0000000..822932f --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdSpiCard/SoftSPI.h @@ -0,0 +1,162 @@ +/* Arduino DigitalIO Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the Arduino DigitalIO Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino DigitalIO Library. If not, see + * . + */ +/** + * @file + * @brief Software SPI. + * + * @defgroup softSPI Software SPI + * @details Software SPI Template Class. + * @{ + */ + +#ifndef SoftSPI_h +#define SoftSPI_h +#include "DigitalPin.h" +//------------------------------------------------------------------------------ +/** Nop for timing. */ +#define nop asm volatile ("nop\n\t") +//------------------------------------------------------------------------------ +/** Pin Mode for MISO is input.*/ +#define MISO_MODE INPUT +/** Pullups disabled for MISO are disabled. */ +#define MISO_LEVEL false +/** Pin Mode for MOSI is output.*/ +#define MOSI_MODE OUTPUT +/** Pin Mode for SCK is output. */ +#define SCK_MODE OUTPUT +//------------------------------------------------------------------------------ +/** + * @class SoftSPI + * @brief Fast software SPI. + */ +template +class SoftSPI { + public: + //---------------------------------------------------------------------------- + /** Initialize SoftSPI pins. */ + void begin() { + fastPinConfig(MisoPin, MISO_MODE, MISO_LEVEL); + fastPinConfig(MosiPin, MOSI_MODE, !MODE_CPHA(Mode)); + fastPinConfig(SckPin, SCK_MODE, MODE_CPOL(Mode)); + } + //---------------------------------------------------------------------------- + /** Soft SPI receive byte. + * @return Data byte received. + */ + inline __attribute__((always_inline)) + uint8_t receive() { + uint8_t data = 0; + receiveBit(7, &data); + receiveBit(6, &data); + receiveBit(5, &data); + receiveBit(4, &data); + receiveBit(3, &data); + receiveBit(2, &data); + receiveBit(1, &data); + receiveBit(0, &data); + return data; + } + //---------------------------------------------------------------------------- + /** Soft SPI send byte. + * @param[in] data Data byte to send. + */ + inline __attribute__((always_inline)) + void send(uint8_t data) { + sendBit(7, data); + sendBit(6, data); + sendBit(5, data); + sendBit(4, data); + sendBit(3, data); + sendBit(2, data); + sendBit(1, data); + sendBit(0, data); + } + //---------------------------------------------------------------------------- + /** Soft SPI transfer byte. + * @param[in] txData Data byte to send. + * @return Data byte received. + */ + inline __attribute__((always_inline)) + uint8_t transfer(uint8_t txData) { + uint8_t rxData = 0; + transferBit(7, &rxData, txData); + transferBit(6, &rxData, txData); + transferBit(5, &rxData, txData); + transferBit(4, &rxData, txData); + transferBit(3, &rxData, txData); + transferBit(2, &rxData, txData); + transferBit(1, &rxData, txData); + transferBit(0, &rxData, txData); + return rxData; + } + + private: + //---------------------------------------------------------------------------- + inline __attribute__((always_inline)) + bool MODE_CPHA(uint8_t mode) {return (mode & 1) != 0;} + inline __attribute__((always_inline)) + bool MODE_CPOL(uint8_t mode) {return (mode & 2) != 0;} + inline __attribute__((always_inline)) + void receiveBit(uint8_t bit, uint8_t* data) { + if (MODE_CPHA(Mode)) { + fastDigitalWrite(SckPin, !MODE_CPOL(Mode)); + } + nop; + nop; + fastDigitalWrite(SckPin, + MODE_CPHA(Mode) ? MODE_CPOL(Mode) : !MODE_CPOL(Mode)); + if (fastDigitalRead(MisoPin)) *data |= 1 << bit; + if (!MODE_CPHA(Mode)) { + fastDigitalWrite(SckPin, MODE_CPOL(Mode)); + } + } + //---------------------------------------------------------------------------- + inline __attribute__((always_inline)) + void sendBit(uint8_t bit, uint8_t data) { + if (MODE_CPHA(Mode)) { + fastDigitalWrite(SckPin, !MODE_CPOL(Mode)); + } + fastDigitalWrite(MosiPin, data & (1 << bit)); + fastDigitalWrite(SckPin, + MODE_CPHA(Mode) ? MODE_CPOL(Mode) : !MODE_CPOL(Mode)); + nop; + nop; + if (!MODE_CPHA(Mode)) { + fastDigitalWrite(SckPin, MODE_CPOL(Mode)); + } + } + //---------------------------------------------------------------------------- + inline __attribute__((always_inline)) + void transferBit(uint8_t bit, uint8_t* rxData, uint8_t txData) { + if (MODE_CPHA(Mode)) { + fastDigitalWrite(SckPin, !MODE_CPOL(Mode)); + } + fastDigitalWrite(MosiPin, txData & (1 << bit)); + fastDigitalWrite(SckPin, + MODE_CPHA(Mode) ? MODE_CPOL(Mode) : !MODE_CPOL(Mode)); + if (fastDigitalRead(MisoPin)) *rxData |= 1 << bit; + if (!MODE_CPHA(Mode)) { + fastDigitalWrite(SckPin, MODE_CPOL(Mode)); + } + } + //---------------------------------------------------------------------------- +}; +#endif // SoftSPI_h +/** @} */ diff --git a/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/AvrDevelopersGpioPinMap.h b/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/AvrDevelopersGpioPinMap.h new file mode 100644 index 0000000..67a8ec2 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/AvrDevelopersGpioPinMap.h @@ -0,0 +1,37 @@ +#ifndef AvrDevelopersGpioPinMap_h +#define AvrDevelopersGpioPinMap_h +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(B, 0), // D0 + GPIO_PIN(B, 1), // D1 + GPIO_PIN(B, 2), // D2 + GPIO_PIN(B, 3), // D3 + GPIO_PIN(B, 4), // D4 + GPIO_PIN(B, 5), // D5 + GPIO_PIN(B, 6), // D6 + GPIO_PIN(B, 7), // D7 + GPIO_PIN(D, 0), // D8 + GPIO_PIN(D, 1), // D9 + GPIO_PIN(D, 2), // D10 + GPIO_PIN(D, 3), // D11 + GPIO_PIN(D, 4), // D12 + GPIO_PIN(D, 5), // D13 + GPIO_PIN(D, 6), // D14 + GPIO_PIN(D, 7), // D15 + GPIO_PIN(C, 0), // D16 + GPIO_PIN(C, 1), // D17 + GPIO_PIN(C, 2), // D18 + GPIO_PIN(C, 3), // D19 + GPIO_PIN(C, 4), // D20 + GPIO_PIN(C, 5), // D21 + GPIO_PIN(C, 6), // D22 + GPIO_PIN(C, 7), // D23 + GPIO_PIN(A, 7), // D24 + GPIO_PIN(A, 6), // D25 + GPIO_PIN(A, 5), // D26 + GPIO_PIN(A, 4), // D27 + GPIO_PIN(A, 3), // D28 + GPIO_PIN(A, 2), // D29 + GPIO_PIN(A, 1), // D30 + GPIO_PIN(A, 0) // D31 +}; +#endif // AvrDevelopersGpioPinMap_h \ No newline at end of file diff --git a/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/BobuinoGpioPinMap.h b/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/BobuinoGpioPinMap.h new file mode 100644 index 0000000..2d19944 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/BobuinoGpioPinMap.h @@ -0,0 +1,37 @@ +#ifndef BobuinoGpioPinMap_h +#define BobuinoGpioPinMap_h +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(B, 0), // D0 + GPIO_PIN(B, 1), // D1 + GPIO_PIN(B, 2), // D2 + GPIO_PIN(B, 3), // D3 + GPIO_PIN(B, 4), // D4 + GPIO_PIN(B, 5), // D5 + GPIO_PIN(B, 6), // D6 + GPIO_PIN(B, 7), // D7 + GPIO_PIN(D, 0), // D8 + GPIO_PIN(D, 1), // D9 + GPIO_PIN(D, 2), // D10 + GPIO_PIN(D, 3), // D11 + GPIO_PIN(D, 4), // D12 + GPIO_PIN(D, 5), // D13 + GPIO_PIN(D, 6), // D14 + GPIO_PIN(D, 7), // D15 + GPIO_PIN(C, 0), // D16 + GPIO_PIN(C, 1), // D17 + GPIO_PIN(C, 2), // D18 + GPIO_PIN(C, 3), // D19 + GPIO_PIN(C, 4), // D20 + GPIO_PIN(C, 5), // D21 + GPIO_PIN(C, 6), // D22 + GPIO_PIN(C, 7), // D23 + GPIO_PIN(A, 0), // D24 + GPIO_PIN(A, 1), // D25 + GPIO_PIN(A, 2), // D26 + GPIO_PIN(A, 3), // D27 + GPIO_PIN(A, 4), // D28 + GPIO_PIN(A, 5), // D29 + GPIO_PIN(A, 6), // D30 + GPIO_PIN(A, 7) // D31 +}; +#endif // BobuinoGpioPinMap_h \ No newline at end of file diff --git a/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/GpioPinMap.h b/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/GpioPinMap.h new file mode 100644 index 0000000..2fd3a8a --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/GpioPinMap.h @@ -0,0 +1,45 @@ +#ifndef GpioPinMap_h +#define GpioPinMap_h +#if defined(__AVR_ATmega168__)\ +||defined(__AVR_ATmega168P__)\ +||defined(__AVR_ATmega328P__) +// 168 and 328 Arduinos +#include "UnoGpioPinMap.h" +#elif defined(__AVR_ATmega1280__)\ +|| defined(__AVR_ATmega2560__) +// Mega ADK +#include "MegaGpioPinMap.h" +#elif defined(__AVR_ATmega32U4__) +#ifdef CORE_TEENSY +#include "Teensy2GpioPinMap.h" +#else // CORE_TEENSY +// Leonardo or Yun +#include "LeonardoGpioPinMap.h" +#endif // CORE_TEENSY +#elif defined(__AVR_AT90USB646__)\ +|| defined(__AVR_AT90USB1286__) +// Teensy++ 1.0 & 2.0 +#include "Teensy2ppGpioPinMap.h" +#elif defined(__AVR_ATmega1284P__)\ +|| defined(__AVR_ATmega1284__)\ +|| defined(__AVR_ATmega644P__)\ +|| defined(__AVR_ATmega644__)\ +|| defined(__AVR_ATmega64__)\ +|| defined(__AVR_ATmega32__)\ +|| defined(__AVR_ATmega324__)\ +|| defined(__AVR_ATmega16__) +#ifdef ARDUINO_1284P_AVR_DEVELOPERS +#include "AvrDevelopersGpioPinMap.h" +#elif defined(ARDUINO_1284P_BOBUINO) +#include "BobuinoGpioPinMap.h" +#elif defined(ARDUINO_1284P_SLEEPINGBEAUTY) +#include "SleepingBeautyGpioPinMap.h" +#elif defined(ARDUINO_1284P_STANDARD) +#include "Standard1284GpioPinMap.h" +#else // ARDUINO_1284P_SLEEPINGBEAUTY +#error Undefined variant 1284, 644, 324 +#endif // ARDUINO_1284P_SLEEPINGBEAUTY +#else // 1284P, 1284, 644 +#error Unknown board type. +#endif // end all boards +#endif // GpioPinMap_h diff --git a/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/LeonardoGpioPinMap.h b/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/LeonardoGpioPinMap.h new file mode 100644 index 0000000..73544e6 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/LeonardoGpioPinMap.h @@ -0,0 +1,35 @@ +#ifndef LeonardoGpioPinMap_h +#define LeonardoGpioPinMap_h +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(D, 2), // D0 + GPIO_PIN(D, 3), // D1 + GPIO_PIN(D, 1), // D2 + GPIO_PIN(D, 0), // D3 + GPIO_PIN(D, 4), // D4 + GPIO_PIN(C, 6), // D5 + GPIO_PIN(D, 7), // D6 + GPIO_PIN(E, 6), // D7 + GPIO_PIN(B, 4), // D8 + GPIO_PIN(B, 5), // D9 + GPIO_PIN(B, 6), // D10 + GPIO_PIN(B, 7), // D11 + GPIO_PIN(D, 6), // D12 + GPIO_PIN(C, 7), // D13 + GPIO_PIN(B, 3), // D14 + GPIO_PIN(B, 1), // D15 + GPIO_PIN(B, 2), // D16 + GPIO_PIN(B, 0), // D17 + GPIO_PIN(F, 7), // D18 + GPIO_PIN(F, 6), // D19 + GPIO_PIN(F, 5), // D20 + GPIO_PIN(F, 4), // D21 + GPIO_PIN(F, 1), // D22 + GPIO_PIN(F, 0), // D23 + GPIO_PIN(D, 4), // D24 + GPIO_PIN(D, 7), // D25 + GPIO_PIN(B, 4), // D26 + GPIO_PIN(B, 5), // D27 + GPIO_PIN(B, 6), // D28 + GPIO_PIN(D, 6) // D29 +}; +#endif // LeonardoGpioPinMap_h diff --git a/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/MegaGpioPinMap.h b/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/MegaGpioPinMap.h new file mode 100644 index 0000000..c041343 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/MegaGpioPinMap.h @@ -0,0 +1,75 @@ +#ifndef MegaGpioPinMap_h +#define MegaGpioPinMap_h +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(E, 0), // D0 + GPIO_PIN(E, 1), // D1 + GPIO_PIN(E, 4), // D2 + GPIO_PIN(E, 5), // D3 + GPIO_PIN(G, 5), // D4 + GPIO_PIN(E, 3), // D5 + GPIO_PIN(H, 3), // D6 + GPIO_PIN(H, 4), // D7 + GPIO_PIN(H, 5), // D8 + GPIO_PIN(H, 6), // D9 + GPIO_PIN(B, 4), // D10 + GPIO_PIN(B, 5), // D11 + GPIO_PIN(B, 6), // D12 + GPIO_PIN(B, 7), // D13 + GPIO_PIN(J, 1), // D14 + GPIO_PIN(J, 0), // D15 + GPIO_PIN(H, 1), // D16 + GPIO_PIN(H, 0), // D17 + GPIO_PIN(D, 3), // D18 + GPIO_PIN(D, 2), // D19 + GPIO_PIN(D, 1), // D20 + GPIO_PIN(D, 0), // D21 + GPIO_PIN(A, 0), // D22 + GPIO_PIN(A, 1), // D23 + GPIO_PIN(A, 2), // D24 + GPIO_PIN(A, 3), // D25 + GPIO_PIN(A, 4), // D26 + GPIO_PIN(A, 5), // D27 + GPIO_PIN(A, 6), // D28 + GPIO_PIN(A, 7), // D29 + GPIO_PIN(C, 7), // D30 + GPIO_PIN(C, 6), // D31 + GPIO_PIN(C, 5), // D32 + GPIO_PIN(C, 4), // D33 + GPIO_PIN(C, 3), // D34 + GPIO_PIN(C, 2), // D35 + GPIO_PIN(C, 1), // D36 + GPIO_PIN(C, 0), // D37 + GPIO_PIN(D, 7), // D38 + GPIO_PIN(G, 2), // D39 + GPIO_PIN(G, 1), // D40 + GPIO_PIN(G, 0), // D41 + GPIO_PIN(L, 7), // D42 + GPIO_PIN(L, 6), // D43 + GPIO_PIN(L, 5), // D44 + GPIO_PIN(L, 4), // D45 + GPIO_PIN(L, 3), // D46 + GPIO_PIN(L, 2), // D47 + GPIO_PIN(L, 1), // D48 + GPIO_PIN(L, 0), // D49 + GPIO_PIN(B, 3), // D50 + GPIO_PIN(B, 2), // D51 + GPIO_PIN(B, 1), // D52 + GPIO_PIN(B, 0), // D53 + GPIO_PIN(F, 0), // D54 + GPIO_PIN(F, 1), // D55 + GPIO_PIN(F, 2), // D56 + GPIO_PIN(F, 3), // D57 + GPIO_PIN(F, 4), // D58 + GPIO_PIN(F, 5), // D59 + GPIO_PIN(F, 6), // D60 + GPIO_PIN(F, 7), // D61 + GPIO_PIN(K, 0), // D62 + GPIO_PIN(K, 1), // D63 + GPIO_PIN(K, 2), // D64 + GPIO_PIN(K, 3), // D65 + GPIO_PIN(K, 4), // D66 + GPIO_PIN(K, 5), // D67 + GPIO_PIN(K, 6), // D68 + GPIO_PIN(K, 7) // D69 +}; +#endif // MegaGpioPinMap_h diff --git a/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/SleepingBeautyGpioPinMap.h b/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/SleepingBeautyGpioPinMap.h new file mode 100644 index 0000000..bf040d9 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/SleepingBeautyGpioPinMap.h @@ -0,0 +1,37 @@ +#ifndef SleepingBeautyGpioPinMap_h +#define SleepingBeautyGpioPinMap_h +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(D, 0), // D0 + GPIO_PIN(D, 1), // D1 + GPIO_PIN(D, 2), // D2 + GPIO_PIN(D, 3), // D3 + GPIO_PIN(B, 0), // D4 + GPIO_PIN(B, 1), // D5 + GPIO_PIN(B, 2), // D6 + GPIO_PIN(B, 3), // D7 + GPIO_PIN(D, 6), // D8 + GPIO_PIN(D, 5), // D9 + GPIO_PIN(B, 4), // D10 + GPIO_PIN(B, 5), // D11 + GPIO_PIN(B, 6), // D12 + GPIO_PIN(B, 7), // D13 + GPIO_PIN(C, 7), // D14 + GPIO_PIN(C, 6), // D15 + GPIO_PIN(A, 5), // D16 + GPIO_PIN(A, 4), // D17 + GPIO_PIN(A, 3), // D18 + GPIO_PIN(A, 2), // D19 + GPIO_PIN(A, 1), // D20 + GPIO_PIN(A, 0), // D21 + GPIO_PIN(D, 4), // D22 + GPIO_PIN(D, 7), // D23 + GPIO_PIN(C, 2), // D24 + GPIO_PIN(C, 3), // D25 + GPIO_PIN(C, 4), // D26 + GPIO_PIN(C, 5), // D27 + GPIO_PIN(C, 1), // D28 + GPIO_PIN(C, 0), // D29 + GPIO_PIN(A, 6), // D30 + GPIO_PIN(A, 7) // D31 +}; +#endif // SleepingBeautyGpioPinMap_h \ No newline at end of file diff --git a/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/Standard1284GpioPinMap.h b/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/Standard1284GpioPinMap.h new file mode 100644 index 0000000..d38ff0c --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/Standard1284GpioPinMap.h @@ -0,0 +1,37 @@ +#ifndef Standard1284GpioPinMap_h +#define Standard1284GpioPinMap_h +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(B, 0), // D0 + GPIO_PIN(B, 1), // D1 + GPIO_PIN(B, 2), // D2 + GPIO_PIN(B, 3), // D3 + GPIO_PIN(B, 4), // D4 + GPIO_PIN(B, 5), // D5 + GPIO_PIN(B, 6), // D6 + GPIO_PIN(B, 7), // D7 + GPIO_PIN(D, 0), // D8 + GPIO_PIN(D, 1), // D9 + GPIO_PIN(D, 2), // D10 + GPIO_PIN(D, 3), // D11 + GPIO_PIN(D, 4), // D12 + GPIO_PIN(D, 5), // D13 + GPIO_PIN(D, 6), // D14 + GPIO_PIN(D, 7), // D15 + GPIO_PIN(C, 0), // D16 + GPIO_PIN(C, 1), // D17 + GPIO_PIN(C, 2), // D18 + GPIO_PIN(C, 3), // D19 + GPIO_PIN(C, 4), // D20 + GPIO_PIN(C, 5), // D21 + GPIO_PIN(C, 6), // D22 + GPIO_PIN(C, 7), // D23 + GPIO_PIN(A, 0), // D24 + GPIO_PIN(A, 1), // D25 + GPIO_PIN(A, 2), // D26 + GPIO_PIN(A, 3), // D27 + GPIO_PIN(A, 4), // D28 + GPIO_PIN(A, 5), // D29 + GPIO_PIN(A, 6), // D30 + GPIO_PIN(A, 7) // D31 +}; +#endif // Standard1284GpioPinMap_h \ No newline at end of file diff --git a/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/Teensy2GpioPinMap.h b/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/Teensy2GpioPinMap.h new file mode 100644 index 0000000..00aa437 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/Teensy2GpioPinMap.h @@ -0,0 +1,30 @@ +#ifndef Teensy2GpioPinMap_h +#define Teensy2GpioPinMap_h +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(B, 0), // D0 + GPIO_PIN(B, 1), // D1 + GPIO_PIN(B, 2), // D2 + GPIO_PIN(B, 3), // D3 + GPIO_PIN(B, 7), // D4 + GPIO_PIN(D, 0), // D5 + GPIO_PIN(D, 1), // D6 + GPIO_PIN(D, 2), // D7 + GPIO_PIN(D, 3), // D8 + GPIO_PIN(C, 6), // D9 + GPIO_PIN(C, 7), // D10 + GPIO_PIN(D, 6), // D11 + GPIO_PIN(D, 7), // D12 + GPIO_PIN(B, 4), // D13 + GPIO_PIN(B, 5), // D14 + GPIO_PIN(B, 6), // D15 + GPIO_PIN(F, 7), // D16 + GPIO_PIN(F, 6), // D17 + GPIO_PIN(F, 5), // D18 + GPIO_PIN(F, 4), // D19 + GPIO_PIN(F, 1), // D20 + GPIO_PIN(F, 0), // D21 + GPIO_PIN(D, 4), // D22 + GPIO_PIN(D, 5), // D23 + GPIO_PIN(E, 6), // D24 +}; +#endif // Teensy2GpioPinMap_h diff --git a/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/Teensy2ppGpioPinMap.h b/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/Teensy2ppGpioPinMap.h new file mode 100644 index 0000000..68c51d7 --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/Teensy2ppGpioPinMap.h @@ -0,0 +1,51 @@ +#ifndef Teensypp2GpioPinMap_h +#define Teensypp2GpioPinMap_h +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(D, 0), // D0 + GPIO_PIN(D, 1), // D1 + GPIO_PIN(D, 2), // D2 + GPIO_PIN(D, 3), // D3 + GPIO_PIN(D, 4), // D4 + GPIO_PIN(D, 5), // D5 + GPIO_PIN(D, 6), // D6 + GPIO_PIN(D, 7), // D7 + GPIO_PIN(E, 0), // D8 + GPIO_PIN(E, 1), // D9 + GPIO_PIN(C, 0), // D10 + GPIO_PIN(C, 1), // D11 + GPIO_PIN(C, 2), // D12 + GPIO_PIN(C, 3), // D13 + GPIO_PIN(C, 4), // D14 + GPIO_PIN(C, 5), // D15 + GPIO_PIN(C, 6), // D16 + GPIO_PIN(C, 7), // D17 + GPIO_PIN(E, 6), // D18 + GPIO_PIN(E, 7), // D19 + GPIO_PIN(B, 0), // D20 + GPIO_PIN(B, 1), // D21 + GPIO_PIN(B, 2), // D22 + GPIO_PIN(B, 3), // D23 + GPIO_PIN(B, 4), // D24 + GPIO_PIN(B, 5), // D25 + GPIO_PIN(B, 6), // D26 + GPIO_PIN(B, 7), // D27 + GPIO_PIN(A, 0), // D28 + GPIO_PIN(A, 1), // D29 + GPIO_PIN(A, 2), // D30 + GPIO_PIN(A, 3), // D31 + GPIO_PIN(A, 4), // D32 + GPIO_PIN(A, 5), // D33 + GPIO_PIN(A, 6), // D34 + GPIO_PIN(A, 7), // D35 + GPIO_PIN(E, 4), // D36 + GPIO_PIN(E, 5), // D37 + GPIO_PIN(F, 0), // D38 + GPIO_PIN(F, 1), // D39 + GPIO_PIN(F, 2), // D40 + GPIO_PIN(F, 3), // D41 + GPIO_PIN(F, 4), // D42 + GPIO_PIN(F, 5), // D43 + GPIO_PIN(F, 6), // D44 + GPIO_PIN(F, 7), // D45 +}; +#endif // Teensypp2GpioPinMap_h diff --git a/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/UnoGpioPinMap.h b/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/UnoGpioPinMap.h new file mode 100644 index 0000000..21ec75d --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SdSpiCard/boards/UnoGpioPinMap.h @@ -0,0 +1,25 @@ +#ifndef UnoGpioPinMap_h +#define UnoGpioPinMap_h +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(D, 0), // D0 + GPIO_PIN(D, 1), // D1 + GPIO_PIN(D, 2), // D2 + GPIO_PIN(D, 3), // D3 + GPIO_PIN(D, 4), // D4 + GPIO_PIN(D, 5), // D5 + GPIO_PIN(D, 6), // D6 + GPIO_PIN(D, 7), // D7 + GPIO_PIN(B, 0), // D8 + GPIO_PIN(B, 1), // D9 + GPIO_PIN(B, 2), // D10 + GPIO_PIN(B, 3), // D11 + GPIO_PIN(B, 4), // D12 + GPIO_PIN(B, 5), // D13 + GPIO_PIN(C, 0), // D14 + GPIO_PIN(C, 1), // D15 + GPIO_PIN(C, 2), // D16 + GPIO_PIN(C, 3), // D17 + GPIO_PIN(C, 4), // D18 + GPIO_PIN(C, 5) // D19 +}; +#endif // UnoGpioPinMap_h \ No newline at end of file diff --git a/Firmware_V2/src/libraries/SdFat/SystemInclude.h b/Firmware_V2/src/libraries/SdFat/SystemInclude.h new file mode 100644 index 0000000..3ee341d --- /dev/null +++ b/Firmware_V2/src/libraries/SdFat/SystemInclude.h @@ -0,0 +1,29 @@ +/* Arduino SdFat Library + * Copyright (C) 2016 by William Greiman + * + * This file is part of the Arduino SdFat Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino SdFat Library. If not, see + * . + */ +#ifndef SystemInclude_h +#define SystemInclude_h +#if defined(ARDUINO) +#include "FatLib/SysCall.h" +#elif defined(PLATFORM_ID) // Only defined if a Particle device +#include "SysCall.h" +#else // System type +#error Unknown System. +#endif // System type +#endif // SystemInclude_h diff --git a/Firmware_V2/src/libraries/Time/DateStrings.cpp b/Firmware_V2/src/libraries/Time/DateStrings.cpp new file mode 100644 index 0000000..3eccff3 --- /dev/null +++ b/Firmware_V2/src/libraries/Time/DateStrings.cpp @@ -0,0 +1,97 @@ +/* DateStrings.cpp + * Definitions for date strings for use with the Time library + * + * Updated for Arduino 1.5.7 18 July 2014 + * + * No memory is consumed in the sketch if your code does not call any of the string methods + * You can change the text of the strings, make sure the short strings are each exactly 3 characters + * the long strings can be any length up to the constant dt_MAX_STRING_LEN defined in TimeLib.h + * + */ + +#if defined(__AVR__) +#include +#else +// for compatiblity with Arduino Due and Teensy 3.0 and maybe others? +#define PROGMEM +#define PGM_P const char * +#define pgm_read_byte(addr) (*(const unsigned char *)(addr)) +#define pgm_read_word(addr) (*(const unsigned char **)(addr)) +#define strcpy_P(dest, src) strcpy((dest), (src)) +#endif +#include // for strcpy_P or strcpy +#include "TimeLib.h" + +// the short strings for each day or month must be exactly dt_SHORT_STR_LEN +#define dt_SHORT_STR_LEN 3 // the length of short strings + +static char buffer[dt_MAX_STRING_LEN+1]; // must be big enough for longest string and the terminating null + +const char monthStr0[] PROGMEM = ""; +const char monthStr1[] PROGMEM = "January"; +const char monthStr2[] PROGMEM = "February"; +const char monthStr3[] PROGMEM = "March"; +const char monthStr4[] PROGMEM = "April"; +const char monthStr5[] PROGMEM = "May"; +const char monthStr6[] PROGMEM = "June"; +const char monthStr7[] PROGMEM = "July"; +const char monthStr8[] PROGMEM = "August"; +const char monthStr9[] PROGMEM = "September"; +const char monthStr10[] PROGMEM = "October"; +const char monthStr11[] PROGMEM = "November"; +const char monthStr12[] PROGMEM = "December"; + +const PROGMEM char * const PROGMEM monthNames_P[] = +{ + monthStr0,monthStr1,monthStr2,monthStr3,monthStr4,monthStr5,monthStr6, + monthStr7,monthStr8,monthStr9,monthStr10,monthStr11,monthStr12 +}; + +const char monthShortNames_P[] PROGMEM = "ErrJanFebMarAprMayJunJulAugSepOctNovDec"; + +const char dayStr0[] PROGMEM = "Err"; +const char dayStr1[] PROGMEM = "Sunday"; +const char dayStr2[] PROGMEM = "Monday"; +const char dayStr3[] PROGMEM = "Tuesday"; +const char dayStr4[] PROGMEM = "Wednesday"; +const char dayStr5[] PROGMEM = "Thursday"; +const char dayStr6[] PROGMEM = "Friday"; +const char dayStr7[] PROGMEM = "Saturday"; + +const PROGMEM char * const PROGMEM dayNames_P[] = +{ + dayStr0,dayStr1,dayStr2,dayStr3,dayStr4,dayStr5,dayStr6,dayStr7 +}; + +const char dayShortNames_P[] PROGMEM = "ErrSunMonTueWedThuFriSat"; + +/* functions to return date strings */ + +char* monthStr(uint8_t month) +{ + strcpy_P(buffer, (PGM_P)pgm_read_word(&(monthNames_P[month]))); + return buffer; +} + +char* monthShortStr(uint8_t month) +{ + for (int i=0; i < dt_SHORT_STR_LEN; i++) + buffer[i] = pgm_read_byte(&(monthShortNames_P[i+ (month*dt_SHORT_STR_LEN)])); + buffer[dt_SHORT_STR_LEN] = 0; + return buffer; +} + +char* dayStr(uint8_t day) +{ + strcpy_P(buffer, (PGM_P)pgm_read_word(&(dayNames_P[day]))); + return buffer; +} + +char* dayShortStr(uint8_t day) +{ + uint8_t index = day*dt_SHORT_STR_LEN; + for (int i=0; i < dt_SHORT_STR_LEN; i++) + buffer[i] = pgm_read_byte(&(dayShortNames_P[index + i])); + buffer[dt_SHORT_STR_LEN] = 0; + return buffer; +} diff --git a/Firmware_V2/src/libraries/Time/Time.cpp b/Firmware_V2/src/libraries/Time/Time.cpp new file mode 100644 index 0000000..5ca1327 --- /dev/null +++ b/Firmware_V2/src/libraries/Time/Time.cpp @@ -0,0 +1,321 @@ +/* + time.c - low level time and date functions + Copyright (c) Michael Margolis 2009-2014 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + 1.0 6 Jan 2010 - initial release + 1.1 12 Feb 2010 - fixed leap year calculation error + 1.2 1 Nov 2010 - fixed setTime bug (thanks to Korman for this) + 1.3 24 Mar 2012 - many edits by Paul Stoffregen: fixed timeStatus() to update + status, updated examples for Arduino 1.0, fixed ARM + compatibility issues, added TimeArduinoDue and TimeTeensy3 + examples, add error checking and messages to RTC examples, + add examples to DS1307RTC library. + 1.4 5 Sep 2014 - compatibility with Arduino 1.5.7 +*/ + +#if ARDUINO >= 100 +#include +#else +#include +#endif + +#include "TimeLib.h" + +static tmElements_t tm; // a cache of time elements +static time_t cacheTime; // the time the cache was updated +static uint32_t syncInterval = 300; // time sync will be attempted after this many seconds + +void refreshCache(time_t t) { + if (t != cacheTime) { + breakTime(t, tm); + cacheTime = t; + } +} + +int hour() { // the hour now + return hour(now()); +} + +int hour(time_t t) { // the hour for the given time + refreshCache(t); + return tm.Hour; +} + +int hourFormat12() { // the hour now in 12 hour format + return hourFormat12(now()); +} + +int hourFormat12(time_t t) { // the hour for the given time in 12 hour format + refreshCache(t); + if( tm.Hour == 0 ) + return 12; // 12 midnight + else if( tm.Hour > 12) + return tm.Hour - 12 ; + else + return tm.Hour ; +} + +uint8_t isAM() { // returns true if time now is AM + return !isPM(now()); +} + +uint8_t isAM(time_t t) { // returns true if given time is AM + return !isPM(t); +} + +uint8_t isPM() { // returns true if PM + return isPM(now()); +} + +uint8_t isPM(time_t t) { // returns true if PM + return (hour(t) >= 12); +} + +int minute() { + return minute(now()); +} + +int minute(time_t t) { // the minute for the given time + refreshCache(t); + return tm.Minute; +} + +int second() { + return second(now()); +} + +int second(time_t t) { // the second for the given time + refreshCache(t); + return tm.Second; +} + +int day(){ + return(day(now())); +} + +int day(time_t t) { // the day for the given time (0-6) + refreshCache(t); + return tm.Day; +} + +int weekday() { // Sunday is day 1 + return weekday(now()); +} + +int weekday(time_t t) { + refreshCache(t); + return tm.Wday; +} + +int month(){ + return month(now()); +} + +int month(time_t t) { // the month for the given time + refreshCache(t); + return tm.Month; +} + +int year() { // as in Processing, the full four digit year: (2009, 2010 etc) + return year(now()); +} + +int year(time_t t) { // the year for the given time + refreshCache(t); + return tmYearToCalendar(tm.Year); +} + +/*============================================================================*/ +/* functions to convert to and from system time */ +/* These are for interfacing with time serivces and are not normally needed in a sketch */ + +// leap year calulator expects year argument as years offset from 1970 +#define LEAP_YEAR(Y) ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) ) + +static const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0 + +void breakTime(time_t timeInput, tmElements_t &tm){ +// break the given time_t into time components +// this is a more compact version of the C library localtime function +// note that year is offset from 1970 !!! + + uint8_t year; + uint8_t month, monthLength; + uint32_t time; + unsigned long days; + + time = (uint32_t)timeInput; + tm.Second = time % 60; + time /= 60; // now it is minutes + tm.Minute = time % 60; + time /= 60; // now it is hours + tm.Hour = time % 24; + time /= 24; // now it is days + tm.Wday = ((time + 4) % 7) + 1; // Sunday is day 1 + + year = 0; + days = 0; + while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) { + year++; + } + tm.Year = year; // year is offset from 1970 + + days -= LEAP_YEAR(year) ? 366 : 365; + time -= days; // now it is days in this year, starting at 0 + + days=0; + month=0; + monthLength=0; + for (month=0; month<12; month++) { + if (month==1) { // february + if (LEAP_YEAR(year)) { + monthLength=29; + } else { + monthLength=28; + } + } else { + monthLength = monthDays[month]; + } + + if (time >= monthLength) { + time -= monthLength; + } else { + break; + } + } + tm.Month = month + 1; // jan is month 1 + tm.Day = time + 1; // day of month +} + +time_t makeTime(tmElements_t &tm){ +// assemble time elements into time_t +// note year argument is offset from 1970 (see macros in time.h to convert to other formats) +// previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9 + + int i; + uint32_t seconds; + + // seconds from 1970 till 1 jan 00:00:00 of the given year + seconds= tm.Year*(SECS_PER_DAY * 365); + for (i = 0; i < tm.Year; i++) { + if (LEAP_YEAR(i)) { + seconds += SECS_PER_DAY; // add extra days for leap years + } + } + + // add days for this year, months start from 1 + for (i = 1; i < tm.Month; i++) { + if ( (i == 2) && LEAP_YEAR(tm.Year)) { + seconds += SECS_PER_DAY * 29; + } else { + seconds += SECS_PER_DAY * monthDays[i-1]; //monthDay array starts from 0 + } + } + seconds+= (tm.Day-1) * SECS_PER_DAY; + seconds+= tm.Hour * SECS_PER_HOUR; + seconds+= tm.Minute * SECS_PER_MIN; + seconds+= tm.Second; + return (time_t)seconds; +} +/*=====================================================*/ +/* Low level system time functions */ + +static uint32_t sysTime = 0; +static uint32_t prevMillis = 0; +static uint32_t nextSyncTime = 0; +static timeStatus_t Status = timeNotSet; + +getExternalTime getTimePtr; // pointer to external sync function +//setExternalTime setTimePtr; // not used in this version + +#ifdef TIME_DRIFT_INFO // define this to get drift data +time_t sysUnsyncedTime = 0; // the time sysTime unadjusted by sync +#endif + + +time_t now() { + // calculate number of seconds passed since last call to now() + while (millis() - prevMillis >= 1000) { + // millis() and prevMillis are both unsigned ints thus the subtraction will always be the absolute value of the difference + sysTime++; + prevMillis += 1000; +#ifdef TIME_DRIFT_INFO + sysUnsyncedTime++; // this can be compared to the synced time to measure long term drift +#endif + } + if (nextSyncTime <= sysTime) { + if (getTimePtr != 0) { + time_t t = getTimePtr(); + if (t != 0) { + setTime(t); + } else { + nextSyncTime = sysTime + syncInterval; + Status = (Status == timeNotSet) ? timeNotSet : timeNeedsSync; + } + } + } + return (time_t)sysTime; +} + +void setTime(time_t t) { +#ifdef TIME_DRIFT_INFO + if(sysUnsyncedTime == 0) + sysUnsyncedTime = t; // store the time of the first call to set a valid Time +#endif + + sysTime = (uint32_t)t; + nextSyncTime = (uint32_t)t + syncInterval; + Status = timeSet; + prevMillis = millis(); // restart counting from now (thanks to Korman for this fix) +} + +void setTime(int hr,int min,int sec,int dy, int mnth, int yr){ + // year can be given as full four digit year or two digts (2010 or 10 for 2010); + //it is converted to years since 1970 + if( yr > 99) + yr = yr - 1970; + else + yr += 30; + tm.Year = yr; + tm.Month = mnth; + tm.Day = dy; + tm.Hour = hr; + tm.Minute = min; + tm.Second = sec; + setTime(makeTime(tm)); +} + +void adjustTime(long adjustment) { + sysTime += adjustment; +} + +// indicates if time has been set and recently synchronized +timeStatus_t timeStatus() { + now(); // required to actually update the status + return Status; +} + +void setSyncProvider( getExternalTime getTimeFunction){ + getTimePtr = getTimeFunction; + nextSyncTime = sysTime; + now(); // this will sync the clock +} + +void setSyncInterval(time_t interval){ // set the number of seconds between re-sync + syncInterval = (uint32_t)interval; + nextSyncTime = sysTime + syncInterval; +} diff --git a/Firmware_V2/src/libraries/Time/Time.h b/Firmware_V2/src/libraries/Time/Time.h new file mode 100644 index 0000000..a79b080 --- /dev/null +++ b/Firmware_V2/src/libraries/Time/Time.h @@ -0,0 +1 @@ +#include "TimeLib.h" diff --git a/Firmware_V2/src/libraries/Time/TimeLib.h b/Firmware_V2/src/libraries/Time/TimeLib.h new file mode 100644 index 0000000..ddb1668 --- /dev/null +++ b/Firmware_V2/src/libraries/Time/TimeLib.h @@ -0,0 +1,144 @@ +/* + time.h - low level time and date functions +*/ + +/* + July 3 2011 - fixed elapsedSecsThisWeek macro (thanks Vincent Valdy for this) + - fixed daysToTime_t macro (thanks maniacbug) +*/ + +#ifndef _Time_h +#ifdef __cplusplus +#define _Time_h + +#include +#ifndef __AVR__ +#include // for __time_t_defined, but avr libc lacks sys/types.h +#endif + + +#if !defined(__time_t_defined) // avoid conflict with newlib or other posix libc +typedef unsigned long time_t; +#endif + + +// This ugly hack allows us to define C++ overloaded functions, when included +// from within an extern "C", as newlib's sys/stat.h does. Actually it is +// intended to include "time.h" from the C library (on ARM, but AVR does not +// have that file at all). On Mac and Windows, the compiler will find this +// "Time.h" instead of the C library "time.h", so we may cause other weird +// and unpredictable effects by conflicting with the C library header "time.h", +// but at least this hack lets us define C++ functions as intended. Hopefully +// nothing too terrible will result from overriding the C library header?! +extern "C++" { +typedef enum {timeNotSet, timeNeedsSync, timeSet +} timeStatus_t ; + +typedef enum { + dowInvalid, dowSunday, dowMonday, dowTuesday, dowWednesday, dowThursday, dowFriday, dowSaturday +} timeDayOfWeek_t; + +typedef enum { + tmSecond, tmMinute, tmHour, tmWday, tmDay,tmMonth, tmYear, tmNbrFields +} tmByteFields; + +typedef struct { + uint8_t Second; + uint8_t Minute; + uint8_t Hour; + uint8_t Wday; // day of week, sunday is day 1 + uint8_t Day; + uint8_t Month; + uint8_t Year; // offset from 1970; +} tmElements_t, TimeElements, *tmElementsPtr_t; + +//convenience macros to convert to and from tm years +#define tmYearToCalendar(Y) ((Y) + 1970) // full four digit year +#define CalendarYrToTm(Y) ((Y) - 1970) +#define tmYearToY2k(Y) ((Y) - 30) // offset is from 2000 +#define y2kYearToTm(Y) ((Y) + 30) + +typedef time_t(*getExternalTime)(); +//typedef void (*setExternalTime)(const time_t); // not used in this version + + +/*==============================================================================*/ +/* Useful Constants */ +#define SECS_PER_MIN ((time_t)(60UL)) +#define SECS_PER_HOUR ((time_t)(3600UL)) +#define SECS_PER_DAY ((time_t)(SECS_PER_HOUR * 24UL)) +#define DAYS_PER_WEEK ((time_t)(7UL)) +#define SECS_PER_WEEK ((time_t)(SECS_PER_DAY * DAYS_PER_WEEK)) +#define SECS_PER_YEAR ((time_t)(SECS_PER_WEEK * 52UL)) +#define SECS_YR_2000 ((time_t)(946684800UL)) // the time at the start of y2k + +/* Useful Macros for getting elapsed time */ +#define numberOfSeconds(_time_) (_time_ % SECS_PER_MIN) +#define numberOfMinutes(_time_) ((_time_ / SECS_PER_MIN) % SECS_PER_MIN) +#define numberOfHours(_time_) (( _time_% SECS_PER_DAY) / SECS_PER_HOUR) +#define dayOfWeek(_time_) ((( _time_ / SECS_PER_DAY + 4) % DAYS_PER_WEEK)+1) // 1 = Sunday +#define elapsedDays(_time_) ( _time_ / SECS_PER_DAY) // this is number of days since Jan 1 1970 +#define elapsedSecsToday(_time_) (_time_ % SECS_PER_DAY) // the number of seconds since last midnight +// The following macros are used in calculating alarms and assume the clock is set to a date later than Jan 1 1971 +// Always set the correct time before settting alarms +#define previousMidnight(_time_) (( _time_ / SECS_PER_DAY) * SECS_PER_DAY) // time at the start of the given day +#define nextMidnight(_time_) ( previousMidnight(_time_) + SECS_PER_DAY ) // time at the end of the given day +#define elapsedSecsThisWeek(_time_) (elapsedSecsToday(_time_) + ((dayOfWeek(_time_)-1) * SECS_PER_DAY) ) // note that week starts on day 1 +#define previousSunday(_time_) (_time_ - elapsedSecsThisWeek(_time_)) // time at the start of the week for the given time +#define nextSunday(_time_) ( previousSunday(_time_)+SECS_PER_WEEK) // time at the end of the week for the given time + + +/* Useful Macros for converting elapsed time to a time_t */ +#define minutesToTime_t ((M)) ( (M) * SECS_PER_MIN) +#define hoursToTime_t ((H)) ( (H) * SECS_PER_HOUR) +#define daysToTime_t ((D)) ( (D) * SECS_PER_DAY) // fixed on Jul 22 2011 +#define weeksToTime_t ((W)) ( (W) * SECS_PER_WEEK) + +/*============================================================================*/ +/* time and date functions */ +int hour(); // the hour now +int hour(time_t t); // the hour for the given time +int hourFormat12(); // the hour now in 12 hour format +int hourFormat12(time_t t); // the hour for the given time in 12 hour format +uint8_t isAM(); // returns true if time now is AM +uint8_t isAM(time_t t); // returns true the given time is AM +uint8_t isPM(); // returns true if time now is PM +uint8_t isPM(time_t t); // returns true the given time is PM +int minute(); // the minute now +int minute(time_t t); // the minute for the given time +int second(); // the second now +int second(time_t t); // the second for the given time +int day(); // the day now +int day(time_t t); // the day for the given time +int weekday(); // the weekday now (Sunday is day 1) +int weekday(time_t t); // the weekday for the given time +int month(); // the month now (Jan is month 1) +int month(time_t t); // the month for the given time +int year(); // the full four digit year: (2009, 2010 etc) +int year(time_t t); // the year for the given time + +time_t now(); // return the current time as seconds since Jan 1 1970 +void setTime(time_t t); +void setTime(int hr,int min,int sec,int day, int month, int yr); +void adjustTime(long adjustment); + +/* date strings */ +#define dt_MAX_STRING_LEN 9 // length of longest date string (excluding terminating null) +char* monthStr(uint8_t month); +char* dayStr(uint8_t day); +char* monthShortStr(uint8_t month); +char* dayShortStr(uint8_t day); + +/* time sync functions */ +timeStatus_t timeStatus(); // indicates if time has been set and recently synchronized +void setSyncProvider( getExternalTime getTimeFunction); // identify the external time provider +void setSyncInterval(time_t interval); // set the number of seconds between re-sync + +/* low level functions to convert to and from system time */ +void breakTime(time_t time, tmElements_t &tm); // break time_t into elements +time_t makeTime(tmElements_t &tm); // convert time elements into time_t + +} // extern "C++" +#endif // __cplusplus +#endif /* _Time_h */ + diff --git a/Firmware_V2/src/libraries/tjpgd/tjpgd.c b/Firmware_V2/src/libraries/tjpgd/tjpgd.c new file mode 100644 index 0000000..17a442c --- /dev/null +++ b/Firmware_V2/src/libraries/tjpgd/tjpgd.c @@ -0,0 +1,968 @@ +/*----------------------------------------------------------------------------/ +/ TJpgDec - Tiny JPEG Decompressor R0.01b (C)ChaN, 2012 +/-----------------------------------------------------------------------------/ +/ The TJpgDec is a generic JPEG decompressor module for tiny embedded systems. +/ This is a free software that opened for education, research and commercial +/ developments under license policy of following terms. +/ +/ Copyright (C) 2012, ChaN, all right reserved. +/ +/ * The TJpgDec module is a free software and there is NO WARRANTY. +/ * No restriction on use. You can use, modify and redistribute it for +/ personal, non-profit or commercial products UNDER YOUR RESPONSIBILITY. +/ * Redistributions of source code must retain the above copyright notice. +/ +/-----------------------------------------------------------------------------/ +/ Oct 04,'11 R0.01 First release. +/ Feb 19,'12 R0.01a Fixed decompression fails when scan starts with an escape seq. +/ Sep 03,'12 R0.01b Added JD_TBLCLIP option. +/----------------------------------------------------------------------------*/ + +#include "tjpgd.h" + + +/*-----------------------------------------------*/ +/* Zigzag-order to raster-order conversion table */ +/*-----------------------------------------------*/ + +#define ZIG(n) Zig[n] + +static +const unsigned char Zig[64] = { /* Zigzag-order to raster-order conversion table */ + 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 +}; + + + +/*-------------------------------------------------*/ +/* Input scale factor of Arai algorithm */ +/* (scaled up 16 bits for fixed point operations) */ +/*-------------------------------------------------*/ + +#define IPSF(n) Ipsf[n] + +static +const unsigned short Ipsf[64] = { /* See also aa_idct.png */ + (unsigned short)(1.00000*8192), (unsigned short)(1.38704*8192), (unsigned short)(1.30656*8192), (unsigned short)(1.17588*8192), (unsigned short)(1.00000*8192), (unsigned short)(0.78570*8192), (unsigned short)(0.54120*8192), (unsigned short)(0.27590*8192), + (unsigned short)(1.38704*8192), (unsigned short)(1.92388*8192), (unsigned short)(1.81226*8192), (unsigned short)(1.63099*8192), (unsigned short)(1.38704*8192), (unsigned short)(1.08979*8192), (unsigned short)(0.75066*8192), (unsigned short)(0.38268*8192), + (unsigned short)(1.30656*8192), (unsigned short)(1.81226*8192), (unsigned short)(1.70711*8192), (unsigned short)(1.53636*8192), (unsigned short)(1.30656*8192), (unsigned short)(1.02656*8192), (unsigned short)(0.70711*8192), (unsigned short)(0.36048*8192), + (unsigned short)(1.17588*8192), (unsigned short)(1.63099*8192), (unsigned short)(1.53636*8192), (unsigned short)(1.38268*8192), (unsigned short)(1.17588*8192), (unsigned short)(0.92388*8192), (unsigned short)(0.63638*8192), (unsigned short)(0.32442*8192), + (unsigned short)(1.00000*8192), (unsigned short)(1.38704*8192), (unsigned short)(1.30656*8192), (unsigned short)(1.17588*8192), (unsigned short)(1.00000*8192), (unsigned short)(0.78570*8192), (unsigned short)(0.54120*8192), (unsigned short)(0.27590*8192), + (unsigned short)(0.78570*8192), (unsigned short)(1.08979*8192), (unsigned short)(1.02656*8192), (unsigned short)(0.92388*8192), (unsigned short)(0.78570*8192), (unsigned short)(0.61732*8192), (unsigned short)(0.42522*8192), (unsigned short)(0.21677*8192), + (unsigned short)(0.54120*8192), (unsigned short)(0.75066*8192), (unsigned short)(0.70711*8192), (unsigned short)(0.63638*8192), (unsigned short)(0.54120*8192), (unsigned short)(0.42522*8192), (unsigned short)(0.29290*8192), (unsigned short)(0.14932*8192), + (unsigned short)(0.27590*8192), (unsigned short)(0.38268*8192), (unsigned short)(0.36048*8192), (unsigned short)(0.32442*8192), (unsigned short)(0.27590*8192), (unsigned short)(0.21678*8192), (unsigned short)(0.14932*8192), (unsigned short)(0.07612*8192) +}; + + + +/*---------------------------------------------*/ +/* Conversion table for fast clipping process */ +/*---------------------------------------------*/ + +#if JD_TBLCLIP + +#define BYTECLIP(v) Clip8[(unsigned int)(v) & 0x3FF] + +static +const unsigned char Clip8[1024] = { + /* 0..255 */ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, + 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, + 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, + /* 256..511 */ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + /* -512..-257 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* -256..-1 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +#else /* JD_TBLCLIP */ + +inline +unsigned char BYTECLIP ( + int val +) +{ + if (val < 0) val = 0; + if (val > 255) val = 255; + + return (unsigned char)val; +} + +#endif + + + +/*-----------------------------------------------------------------------*/ +/* Allocate a memory block from memory pool */ +/*-----------------------------------------------------------------------*/ + +static +void* alloc_pool ( /* Pointer to allocated memory block (NULL:no memory available) */ + JDEC* jd, /* Pointer to the decompressor object */ + unsigned int nd /* Number of bytes to allocate */ +) +{ + char *rp = 0; + + + nd = (nd + 3) & ~3; /* Align block size to the word boundary */ + + if (jd->sz_pool >= nd) { + jd->sz_pool -= nd; + rp = (char*)jd->pool; /* Get start of available memory pool */ + jd->pool = (void*)(rp + nd); /* Allocate requierd bytes */ + } + + return (void*)rp; /* Return allocated memory block (NULL:no memory to allocate) */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Create de-quantization and prescaling tables with a DQT segment */ +/*-----------------------------------------------------------------------*/ + +static +unsigned int create_qt_tbl ( /* 0:OK, !0:Failed */ + JDEC* jd, /* Pointer to the decompressor object */ + const unsigned char* data, /* Pointer to the quantizer tables */ + unsigned int ndata /* Size of input data */ +) +{ + unsigned int i; + unsigned char d, z; + long *pb; + + + while (ndata) { /* Process all tables in the segment */ + if (ndata < 65) return JDR_FMT1; /* Err: table size is unaligned */ + ndata -= 65; + d = *data++; /* Get table property */ + if (d & 0xF0) return JDR_FMT1; /* Err: not 8-bit resolution */ + i = d & 3; /* Get table ID */ + pb = alloc_pool(jd, 64 * sizeof (long));/* Allocate a memory block for the table */ + if (!pb) return JDR_MEM1; /* Err: not enough memory */ + jd->qttbl[i] = pb; /* Register the table */ + for (i = 0; i < 64; i++) { /* Load the table */ + z = ZIG(i); /* Zigzag-order to raster-order conversion */ + pb[z] = (long)((unsigned long)*data++ * IPSF(z)); /* Apply scale factor of Arai algorithm to the de-quantizers */ + } + } + + return JDR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Create huffman code tables with a DHT segment */ +/*-----------------------------------------------------------------------*/ + +static +unsigned int create_huffman_tbl ( /* 0:OK, !0:Failed */ + JDEC* jd, /* Pointer to the decompressor object */ + const unsigned char* data, /* Pointer to the packed huffman tables */ + unsigned int ndata /* Size of input data */ +) +{ + unsigned int i, j, b, np, cls, num; + unsigned char d, *pb, *pd; + unsigned short hc, *ph; + + + while (ndata) { /* Process all tables in the segment */ + if (ndata < 17) return JDR_FMT1; /* Err: wrong data size */ + ndata -= 17; + d = *data++; /* Get table number and class */ + cls = (d >> 4); num = d & 0x0F; /* class = dc(0)/ac(1), table number = 0/1 */ + if (d & 0xEE) return JDR_FMT1; /* Err: invalid class/number */ + pb = alloc_pool(jd, 16); /* Allocate a memory block for the bit distribution table */ + if (!pb) return JDR_MEM1; /* Err: not enough memory */ + jd->huffbits[num][cls] = pb; + for (np = i = 0; i < 16; i++) { /* Load number of patterns for 1 to 16-bit code */ + pb[i] = b = *data++; + np += b; /* Get sum of code words for each code */ + } + + ph = alloc_pool(jd, np * sizeof (unsigned short));/* Allocate a memory block for the code word table */ + if (!ph) return JDR_MEM1; /* Err: not enough memory */ + jd->huffcode[num][cls] = ph; + hc = 0; + for (j = i = 0; i < 16; i++) { /* Re-build huffman code word table */ + b = pb[i]; + while (b--) ph[j++] = hc++; + hc <<= 1; + } + + if (ndata < np) return JDR_FMT1; /* Err: wrong data size */ + ndata -= np; + pd = alloc_pool(jd, np); /* Allocate a memory block for the decoded data */ + if (!pd) return JDR_MEM1; /* Err: not enough memory */ + jd->huffdata[num][cls] = pd; + for (i = 0; i < np; i++) { /* Load decoded data corresponds to each code ward */ + d = *data++; + if (!cls && d > 11) return JDR_FMT1; + *pd++ = d; + } + } + + return JDR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Extract N bits from input stream */ +/*-----------------------------------------------------------------------*/ + +static +int bitext ( /* >=0: extracted data, <0: error code */ + JDEC* jd, /* Pointer to the decompressor object */ + unsigned int nbit /* Number of bits to extract (1 to 11) */ +) +{ + unsigned char msk, s, *dp; + unsigned int dc, v, f; + + + msk = jd->dmsk; dc = jd->dctr; dp = jd->dptr; /* Bit mask, number of data available, read ptr */ + s = *dp; v = f = 0; + do { + if (!msk) { /* Next byte? */ + if (!dc) { /* No input data is available, re-fill input buffer */ + dp = jd->inbuf; /* Top of input buffer */ + dc = jd->infunc(jd, dp, JD_SZBUF); + if (!dc) return 0 - (int)JDR_INP; /* Err: read error or wrong stream termination */ + } else { + dp++; /* Next data ptr */ + } + dc--; /* Decrement number of available bytes */ + if (f) { /* In flag sequence? */ + f = 0; /* Exit flag sequence */ + if (*dp != 0) return 0 - (int)JDR_FMT1; /* Err: unexpected flag is detected (may be collapted data) */ + *dp = s = 0xFF; /* The flag is a data 0xFF */ + } else { + s = *dp; /* Get next data byte */ + if (s == 0xFF) { /* Is start of flag sequence? */ + f = 1; continue; /* Enter flag sequence */ + } + } + msk = 0x80; /* Read from MSB */ + } + v <<= 1; /* Get a bit */ + if (s & msk) v++; + msk >>= 1; + nbit--; + } while (nbit); + jd->dmsk = msk; jd->dctr = dc; jd->dptr = dp; + + return (int)v; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Extract a huffman decoded data from input stream */ +/*-----------------------------------------------------------------------*/ + +static +int huffext ( /* >=0: decoded data, <0: error code */ + JDEC* jd, /* Pointer to the decompressor object */ + const unsigned char* hbits, /* Pointer to the bit distribution table */ + const unsigned short* hcode, /* Pointer to the code word table */ + const unsigned char* hdata /* Pointer to the data table */ +) +{ + unsigned char msk, s, *dp; + unsigned int dc, v, f, bl, nd; + + + msk = jd->dmsk; dc = jd->dctr; dp = jd->dptr; /* Bit mask, number of data available, read ptr */ + s = *dp; v = f = 0; + bl = 16; /* Max code length */ + do { + if (!msk) { /* Next byte? */ + if (!dc) { /* No input data is available, re-fill input buffer */ + dp = jd->inbuf; /* Top of input buffer */ + dc = jd->infunc(jd, dp, JD_SZBUF); + if (!dc) return 0 - (int)JDR_INP; /* Err: read error or wrong stream termination */ + } else { + dp++; /* Next data ptr */ + } + dc--; /* Decrement number of available bytes */ + if (f) { /* In flag sequence? */ + f = 0; /* Exit flag sequence */ + if (*dp != 0) + return 0 - (int)JDR_FMT1; /* Err: unexpected flag is detected (may be collapted data) */ + *dp = s = 0xFF; /* The flag is a data 0xFF */ + } else { + s = *dp; /* Get next data byte */ + if (s == 0xFF) { /* Is start of flag sequence? */ + f = 1; continue; /* Enter flag sequence, get trailing byte */ + } + } + msk = 0x80; /* Read from MSB */ + } + v <<= 1; /* Get a bit */ + if (s & msk) v++; + msk >>= 1; + + for (nd = *hbits++; nd; nd--) { /* Search the code word in this bit length */ + if (v == *hcode++) { /* Matched? */ + jd->dmsk = msk; jd->dctr = dc; jd->dptr = dp; + return *hdata; /* Return the decoded data */ + } + hdata++; + } + bl--; + } while (bl); + + return 0 - (int)JDR_FMT1; /* Err: code not found (may be collapted data) */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Apply Inverse-DCT in Arai Algorithm (see also aa_idct.png) */ +/*-----------------------------------------------------------------------*/ + +static +void block_idct ( + long* src, /* Input block data (de-quantized and pre-scaled for Arai Algorithm) */ + unsigned char* dst /* Pointer to the destination to store the block as byte array */ +) +{ + const long M13 = (long)(1.41421*4096), M2 = (long)(1.08239*4096), M4 = (long)(2.61313*4096), M5 = (long)(1.84776*4096); + long v0, v1, v2, v3, v4, v5, v6, v7; + long t10, t11, t12, t13; + unsigned int i; + + /* Process columns */ + for (i = 0; i < 8; i++) { + v0 = src[8 * 0]; /* Get even elements */ + v1 = src[8 * 2]; + v2 = src[8 * 4]; + v3 = src[8 * 6]; + + t10 = v0 + v2; /* Process the even elements */ + t12 = v0 - v2; + t11 = (v1 - v3) * M13 >> 12; + v3 += v1; + t11 -= v3; + v0 = t10 + v3; + v3 = t10 - v3; + v1 = t11 + t12; + v2 = t12 - t11; + + v4 = src[8 * 7]; /* Get odd elements */ + v5 = src[8 * 1]; + v6 = src[8 * 5]; + v7 = src[8 * 3]; + + t10 = v5 - v4; /* Process the odd elements */ + t11 = v5 + v4; + t12 = v6 - v7; + v7 += v6; + v5 = (t11 - v7) * M13 >> 12; + v7 += t11; + t13 = (t10 + t12) * M5 >> 12; + v4 = t13 - (t10 * M2 >> 12); + v6 = t13 - (t12 * M4 >> 12) - v7; + v5 -= v6; + v4 -= v5; + + src[8 * 0] = v0 + v7; /* Write-back transformed values */ + src[8 * 7] = v0 - v7; + src[8 * 1] = v1 + v6; + src[8 * 6] = v1 - v6; + src[8 * 2] = v2 + v5; + src[8 * 5] = v2 - v5; + src[8 * 3] = v3 + v4; + src[8 * 4] = v3 - v4; + + src++; /* Next column */ + } + + /* Process rows */ + src -= 8; + for (i = 0; i < 8; i++) { + v0 = src[0] + (128L << 8); /* Get even elements (remove DC offset (-128) here) */ + v1 = src[2]; + v2 = src[4]; + v3 = src[6]; + + t10 = v0 + v2; /* Process the even elements */ + t12 = v0 - v2; + t11 = (v1 - v3) * M13 >> 12; + v3 += v1; + t11 -= v3; + v0 = t10 + v3; + v3 = t10 - v3; + v1 = t11 + t12; + v2 = t12 - t11; + + v4 = src[7]; /* Get odd elements */ + v5 = src[1]; + v6 = src[5]; + v7 = src[3]; + + t10 = v5 - v4; /* Process the odd elements */ + t11 = v5 + v4; + t12 = v6 - v7; + v7 += v6; + v5 = (t11 - v7) * M13 >> 12; + v7 += t11; + t13 = (t10 + t12) * M5 >> 12; + v4 = t13 - (t10 * M2 >> 12); + v6 = t13 - (t12 * M4 >> 12) - v7; + v5 -= v6; + v4 -= v5; + + dst[0] = BYTECLIP((v0 + v7) >> 8); /* Descale the transformed values 8 bits and output */ + dst[7] = BYTECLIP((v0 - v7) >> 8); + dst[1] = BYTECLIP((v1 + v6) >> 8); + dst[6] = BYTECLIP((v1 - v6) >> 8); + dst[2] = BYTECLIP((v2 + v5) >> 8); + dst[5] = BYTECLIP((v2 - v5) >> 8); + dst[3] = BYTECLIP((v3 + v4) >> 8); + dst[4] = BYTECLIP((v3 - v4) >> 8); + dst += 8; + + src += 8; /* Next row */ + } +} + + + + +/*-----------------------------------------------------------------------*/ +/* Load all blocks in the MCU into working buffer */ +/*-----------------------------------------------------------------------*/ + +static +JRESULT mcu_load ( + JDEC* jd /* Pointer to the decompressor object */ +) +{ + long *tmp = (long*)jd->workbuf; /* Block working buffer for de-quantize and IDCT */ + unsigned int blk, nby, nbc, i, z, id, cmp; + int b, d, e; + unsigned char *bp; + const unsigned char *hb, *hd; + const unsigned short *hc; + const long *dqf; + + + nby = jd->msx * jd->msy; /* Number of Y blocks (1, 2 or 4) */ + nbc = 2; /* Number of C blocks (2) */ + bp = jd->mcubuf; /* Pointer to the first block */ + + for (blk = 0; blk < nby + nbc; blk++) { + cmp = (blk < nby) ? 0 : blk - nby + 1; /* Component number 0:Y, 1:Cb, 2:Cr */ + id = cmp ? 1 : 0; /* Huffman table ID of the component */ + + /* Extract a DC element from input stream */ + hb = jd->huffbits[id][0]; /* Huffman table for the DC element */ + hc = jd->huffcode[id][0]; + hd = jd->huffdata[id][0]; + b = huffext(jd, hb, hc, hd); /* Extract a huffman coded data (bit length) */ + if (b < 0) return 0 - b; /* Err: invalid code or input */ + d = jd->dcv[cmp]; /* DC value of previous block */ + if (b) { /* If there is any difference from previous block */ + e = bitext(jd, b); /* Extract data bits */ + if (e < 0) return 0 - e; /* Err: input */ + b = 1 << (b - 1); /* MSB position */ + if (!(e & b)) e -= (b << 1) - 1; /* Restore sign if needed */ + d += e; /* Get current value */ + jd->dcv[cmp] = (short)d; /* Save current DC value for next block */ + } + dqf = jd->qttbl[jd->qtid[cmp]]; /* De-quantizer table ID for this component */ + tmp[0] = d * dqf[0] >> 8; /* De-quantize, apply scale factor of Arai algorithm and descale 8 bits */ + + /* Extract following 63 AC elements from input stream */ + for (i = 1; i < 64; i++) tmp[i] = 0; /* Clear rest of elements */ + hb = jd->huffbits[id][1]; /* Huffman table for the AC elements */ + hc = jd->huffcode[id][1]; + hd = jd->huffdata[id][1]; + i = 1; /* Top of the AC elements */ + do { + b = huffext(jd, hb, hc, hd); /* Extract a huffman coded value (zero runs and bit length) */ + if (b == 0) break; /* EOB? */ + if (b < 0) return 0 - b; /* Err: invalid code or input error */ + z = (unsigned int)b >> 4; /* Number of leading zero elements */ + if (z) { + i += z; /* Skip zero elements */ + if (i >= 64) return JDR_FMT1; /* Too long zero run */ + } + if (b &= 0x0F) { /* Bit length */ + d = bitext(jd, b); /* Extract data bits */ + if (d < 0) return 0 - d; /* Err: input device */ + b = 1 << (b - 1); /* MSB position */ + if (!(d & b)) d -= (b << 1) - 1;/* Restore negative value if needed */ + z = ZIG(i); /* Zigzag-order to raster-order converted index */ + tmp[z] = d * dqf[z] >> 8; /* De-quantize, apply scale factor of Arai algorithm and descale 8 bits */ + } + } while (++i < 64); /* Next AC element */ + + if (JD_USE_SCALE && jd->scale == 3) + *bp = (*tmp / 256) + 128; /* If scale ratio is 1/8, IDCT can be ommited and only DC element is used */ + else + block_idct(tmp, bp); /* Apply IDCT and store the block to the MCU buffer */ + + bp += 64; /* Next block */ + } + + return JDR_OK; /* All blocks have been loaded successfully */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Output an MCU: Convert YCrCb to RGB and output it in RGB form */ +/*-----------------------------------------------------------------------*/ + +static +JRESULT mcu_output ( + JDEC* jd, /* Pointer to the decompressor object */ + unsigned int (*outfunc)(JDEC*, void*, JRECT*), /* RGB output function */ + unsigned int x, /* MCU position in the image (left of the MCU) */ + unsigned int y /* MCU position in the image (top of the MCU) */ +) +{ + const int CVACC = (sizeof (int) > 2) ? 1024 : 128; + unsigned int ix, iy, mx, my, rx, ry; + int yy, cb, cr; + unsigned char *py, *pc, *rgb24; + JRECT rect; + + + mx = jd->msx * 8; my = jd->msy * 8; /* MCU size (pixel) */ + rx = (x + mx <= jd->width) ? mx : jd->width - x; /* Output rectangular size (it may be clipped at right/bottom end) */ + ry = (y + my <= jd->height) ? my : jd->height - y; + if (JD_USE_SCALE) { + rx >>= jd->scale; ry >>= jd->scale; + if (!rx || !ry) return JDR_OK; /* Skip this MCU if all pixel is to be rounded off */ + x >>= jd->scale; y >>= jd->scale; + } + rect.left = x; rect.right = x + rx - 1; /* Rectangular area in the frame buffer */ + rect.top = y; rect.bottom = y + ry - 1; + + + if (!JD_USE_SCALE || jd->scale != 3) { /* Not for 1/8 scaling */ + + /* Build an RGB MCU from discrete comopnents */ + rgb24 = (unsigned char*)jd->workbuf; + for (iy = 0; iy < my; iy++) { + pc = jd->mcubuf; + py = pc + iy * 8; + if (my == 16) { /* Double block height? */ + pc += 64 * 4 + (iy >> 1) * 8; + if (iy >= 8) py += 64; + } else { /* Single block height */ + pc += mx * 8 + iy * 8; + } + for (ix = 0; ix < mx; ix++) { + cb = pc[0] - 128; /* Get Cb/Cr component and restore right level */ + cr = pc[64] - 128; + if (mx == 16) { /* Double block width? */ + if (ix == 8) py += 64 - 8; /* Jump to next block if double block heigt */ + pc += ix & 1; /* Increase chroma pointer every two pixels */ + } else { /* Single block width */ + pc++; /* Increase chroma pointer every pixel */ + } + yy = *py++; /* Get Y component */ + + /* Convert YCbCr to RGB */ + *rgb24++ = /* R */ BYTECLIP(yy + ((int)(1.402 * CVACC) * cr) / CVACC); + *rgb24++ = /* G */ BYTECLIP(yy - ((int)(0.344 * CVACC) * cb + (int)(0.714 * CVACC) * cr) / CVACC); + *rgb24++ = /* B */ BYTECLIP(yy + ((int)(1.772 * CVACC) * cb) / CVACC); + } + } + + /* Descale the MCU rectangular if needed */ + if (JD_USE_SCALE && jd->scale) { + unsigned int x, y, r, g, b, s, w, a; + unsigned char *op; + + /* Get averaged RGB value of each square correcponds to a pixel */ + s = jd->scale * 2; /* Bumber of shifts for averaging */ + w = 1 << jd->scale; /* Width of square */ + a = (mx - w) * 3; /* Bytes to skip for next line in the square */ + op = (unsigned char*)jd->workbuf; + for (iy = 0; iy < my; iy += w) { + for (ix = 0; ix < mx; ix += w) { + rgb24 = (unsigned char*)jd->workbuf + (iy * mx + ix) * 3; + r = g = b = 0; + for (y = 0; y < w; y++) { /* Accumulate RGB value in the square */ + for (x = 0; x < w; x++) { + r += *rgb24++; + g += *rgb24++; + b += *rgb24++; + } + rgb24 += a; + } /* Put the averaged RGB value as a pixel */ + *op++ = (unsigned char)(r >> s); + *op++ = (unsigned char)(g >> s); + *op++ = (unsigned char)(b >> s); + } + } + } + + } else { /* For only 1/8 scaling (left-top pixel in each block are the DC value of the block) */ + + /* Build a 1/8 descaled RGB MCU from discrete comopnents */ + rgb24 = (unsigned char*)jd->workbuf; + pc = jd->mcubuf + mx * my; + cb = pc[0] - 128; /* Get Cb/Cr component and restore right level */ + cr = pc[64] - 128; + for (iy = 0; iy < my; iy += 8) { + py = jd->mcubuf; + if (iy == 8) py += 64 * 2; + for (ix = 0; ix < mx; ix += 8) { + yy = *py; /* Get Y component */ + py += 64; + + /* Convert YCbCr to RGB */ + *rgb24++ = /* R */ BYTECLIP(yy + ((int)(1.402 * CVACC) * cr / CVACC)); + *rgb24++ = /* G */ BYTECLIP(yy - ((int)(0.344 * CVACC) * cb + (int)(0.714 * CVACC) * cr) / CVACC); + *rgb24++ = /* B */ BYTECLIP(yy + ((int)(1.772 * CVACC) * cb / CVACC)); + } + } + } + + /* Squeeze up pixel table if a part of MCU is to be truncated */ + mx >>= jd->scale; + if (rx < mx) { + unsigned char *s, *d; + unsigned int x, y; + + s = d = (unsigned char*)jd->workbuf; + for (y = 0; y < ry; y++) { + for (x = 0; x < rx; x++) { /* Copy effective pixels */ + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + } + s += (mx - rx) * 3; /* Skip truncated pixels */ + } + } + + /* Convert RGB888 to RGB565 if needed */ + if (JD_FORMAT == 1) { + unsigned char *s = (unsigned char*)jd->workbuf; + unsigned short w, *d = (unsigned short*)s; + unsigned int n = rx * ry; + + do { + w = (*s++ & 0xF8) << 8; /* RRRRR----------- */ + w |= (*s++ & 0xFC) << 3; /* -----GGGGGG----- */ + w |= *s++ >> 3; /* -----------BBBBB */ + *d++ = w; + } while (--n); + } + + /* Output the RGB rectangular */ + return outfunc(jd, jd->workbuf, &rect) ? JDR_OK : JDR_INTR; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Process restart interval */ +/*-----------------------------------------------------------------------*/ + +static +JRESULT restart ( + JDEC* jd, /* Pointer to the decompressor object */ + unsigned short rstn /* Expected restert sequense number */ +) +{ + unsigned int i, dc; + unsigned short d; + unsigned char *dp; + + + /* Discard padding bits and get two bytes from the input stream */ + dp = jd->dptr; dc = jd->dctr; + d = 0; + for (i = 0; i < 2; i++) { + if (!dc) { /* No input data is available, re-fill input buffer */ + dp = jd->inbuf; + dc = jd->infunc(jd, dp, JD_SZBUF); + if (!dc) return JDR_INP; + } else { + dp++; + } + dc--; + d = (d << 8) | *dp; /* Get a byte */ + } + jd->dptr = dp; jd->dctr = dc; jd->dmsk = 0; + + /* Check the marker */ + if ((d & 0xFFD8) != 0xFFD0 || (d & 7) != (rstn & 7)) + return JDR_FMT1; /* Err: expected RSTn marker is not detected (may be collapted data) */ + + /* Reset DC offset */ + jd->dcv[2] = jd->dcv[1] = jd->dcv[0] = 0; + + return JDR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Analyze the JPEG image and Initialize decompressor object */ +/*-----------------------------------------------------------------------*/ + +#define LDB_WORD(ptr) (unsigned short)(((unsigned short)*((unsigned char*)(ptr))<<8)|(unsigned short)*(unsigned char*)((ptr)+1)) + + +JRESULT jd_prepare ( + JDEC* jd, /* Blank decompressor object */ + unsigned int (*infunc)(JDEC*, unsigned char*, unsigned int), /* JPEG strem input function */ + void* pool, /* Working buffer for the decompression session */ + unsigned int sz_pool, /* Size of working buffer */ + void* dev /* I/O device identifier for the session */ +) +{ + unsigned char *seg, b; + unsigned short marker; + unsigned long ofs; + unsigned int n, i, j, len; + JRESULT rc; + + + if (!pool) return JDR_PAR; + + jd->pool = pool; /* Work memroy */ + jd->sz_pool = sz_pool; /* Size of given work memory */ + jd->infunc = infunc; /* Stream input function */ + jd->device = dev; /* I/O device identifier */ + jd->nrst = 0; /* No restart interval (default) */ + + for (i = 0; i < 2; i++) { /* Nulls pointers */ + for (j = 0; j < 2; j++) { + jd->huffbits[i][j] = 0; + jd->huffcode[i][j] = 0; + jd->huffdata[i][j] = 0; + } + } + for (i = 0; i < 4; i++) jd->qttbl[i] = 0; + + jd->inbuf = seg = alloc_pool(jd, JD_SZBUF); /* Allocate stream input buffer */ + if (!seg) return JDR_MEM1; + + if (jd->infunc(jd, seg, 2) != 2) return JDR_INP;/* Check SOI marker */ + if (LDB_WORD(seg) != 0xFFD8) return JDR_FMT1; /* Err: SOI is not detected */ + ofs = 2; + + for (;;) { + /* Get a JPEG marker */ + if (jd->infunc(jd, seg, 4) != 4) return JDR_INP; + marker = LDB_WORD(seg); /* Marker */ + len = LDB_WORD(seg + 2); /* Length field */ + if (len <= 2 || (marker >> 8) != 0xFF) return JDR_FMT1; + len -= 2; /* Content size excluding length field */ + ofs += 4 + len; /* Number of bytes loaded */ + + switch (marker & 0xFF) { + case 0xC0: /* SOF0 (baseline JPEG) */ + /* Load segment data */ + if (len > JD_SZBUF) return JDR_MEM2; + if (jd->infunc(jd, seg, len) != len) return JDR_INP; + + jd->width = LDB_WORD(seg+3); /* Image width in unit of pixel */ + jd->height = LDB_WORD(seg+1); /* Image height in unit of pixel */ + if (seg[5] != 3) return JDR_FMT3; /* Err: Supports only Y/Cb/Cr format */ + + /* Check three image components */ + for (i = 0; i < 3; i++) { + b = seg[7 + 3 * i]; /* Get sampling factor */ + if (!i) { /* Y component */ + if (b != 0x11 && b != 0x22 && b != 0x21)/* Check sampling factor */ + return JDR_FMT3; /* Err: Supports only 4:4:4, 4:2:0 or 4:2:2 */ + jd->msx = b >> 4; jd->msy = b & 15; /* Size of MCU [blocks] */ + } else { /* Cb/Cr component */ + if (b != 0x11) return JDR_FMT3; /* Err: Sampling factor of Cr/Cb must be 1 */ + } + b = seg[8 + 3 * i]; /* Get dequantizer table ID for this component */ + if (b > 3) return JDR_FMT3; /* Err: Invalid ID */ + jd->qtid[i] = b; + } + break; + + case 0xDD: /* DRI */ + /* Load segment data */ + if (len > JD_SZBUF) return JDR_MEM2; + if (jd->infunc(jd, seg, len) != len) return JDR_INP; + + /* Get restart interval (MCUs) */ + jd->nrst = LDB_WORD(seg); + break; + + case 0xC4: /* DHT */ + /* Load segment data */ + if (len > JD_SZBUF) return JDR_MEM2; + if (jd->infunc(jd, seg, len) != len) return JDR_INP; + + /* Create huffman tables */ + rc = create_huffman_tbl(jd, seg, len); + if (rc) return rc; + break; + + case 0xDB: /* DQT */ + /* Load segment data */ + if (len > JD_SZBUF) return JDR_MEM2; + if (jd->infunc(jd, seg, len) != len) return JDR_INP; + + /* Create de-quantizer tables */ + rc = create_qt_tbl(jd, seg, len); + if (rc) return rc; + break; + + case 0xDA: /* SOS */ + /* Load segment data */ + if (len > JD_SZBUF) return JDR_MEM2; + if (jd->infunc(jd, seg, len) != len) return JDR_INP; + + if (!jd->width || !jd->height) return JDR_FMT1; /* Err: Invalid image size */ + + if (seg[0] != 3) return JDR_FMT3; /* Err: Supports only three color components format */ + + /* Check if all tables corresponding to each components have been loaded */ + for (i = 0; i < 3; i++) { + b = seg[2 + 2 * i]; /* Get huffman table ID */ + if (b != 0x00 && b != 0x11) return JDR_FMT3; /* Err: Different table number for DC/AC element */ + b = i ? 1 : 0; + if (!jd->huffbits[b][0] || !jd->huffbits[b][1]) /* Check huffman table for this component */ + return JDR_FMT1; /* Err: Huffman table not loaded */ + if (!jd->qttbl[jd->qtid[i]]) return JDR_FMT1; /* Err: Dequantizer table not loaded */ + } + + /* Allocate working buffer for MCU and RGB */ + n = jd->msy * jd->msx; /* Number of Y blocks in the MCU */ + if (!n) return JDR_FMT1; /* Err: SOF0 has not been loaded */ + len = n * 64 * 2 + 64; /* Allocate buffer for IDCT and RGB output */ + if (len < 256) len = 256; /* but at least 256 byte is required for IDCT */ + jd->workbuf = alloc_pool(jd, len); /* and it may occupy a part of following MCU working buffer for RGB output */ + if (!jd->workbuf) return JDR_MEM1; /* Err: not enough memory */ + jd->mcubuf = alloc_pool(jd, (n + 2) * 64); /* Allocate MCU working buffer */ + if (!jd->mcubuf) return JDR_MEM1; /* Err: not enough memory */ + + /* Pre-load the JPEG data to extract it from the bit stream */ + jd->dptr = seg; jd->dctr = 0; jd->dmsk = 0; /* Prepare to read bit stream */ + if (ofs %= JD_SZBUF) { /* Align read offset to JD_SZBUF */ + jd->dctr = jd->infunc(jd, seg + ofs, JD_SZBUF - (unsigned int)ofs); + jd->dptr = seg + ofs - 1; + } + + return JDR_OK; /* Initialization succeeded. Ready to decompress the JPEG image. */ + + case 0xC1: /* SOF1 */ + case 0xC2: /* SOF2 */ + case 0xC3: /* SOF3 */ + case 0xC5: /* SOF5 */ + case 0xC6: /* SOF6 */ + case 0xC7: /* SOF7 */ + case 0xC9: /* SOF9 */ + case 0xCA: /* SOF10 */ + case 0xCB: /* SOF11 */ + case 0xCD: /* SOF13 */ + case 0xCE: /* SOF14 */ + case 0xCF: /* SOF15 */ + case 0xD9: /* EOI */ + return JDR_FMT3; /* Unsuppoted JPEG standard (may be progressive JPEG) */ + + default: /* Unknown segment (comment, exif or etc..) */ + /* Skip segment data */ + if (jd->infunc(jd, 0, len) != len) /* Null pointer specifies to skip bytes of stream */ + return JDR_INP; + } + } +} + + + + +/*-----------------------------------------------------------------------*/ +/* Start to decompress the JPEG picture */ +/*-----------------------------------------------------------------------*/ + +JRESULT jd_decomp ( + JDEC* jd, /* Initialized decompression object */ + unsigned int (*outfunc)(JDEC*, void*, JRECT*), /* RGB output function */ + unsigned char scale /* Output de-scaling factor (0 to 3) */ +) +{ + unsigned int x, y, mx, my; + unsigned short rst, rsc; + JRESULT rc; + + + if (scale > (JD_USE_SCALE ? 3 : 0)) return JDR_PAR; + jd->scale = scale; + + mx = jd->msx * 8; my = jd->msy * 8; /* Size of the MCU (pixel) */ + + jd->dcv[2] = jd->dcv[1] = jd->dcv[0] = 0; /* Initialize DC values */ + rst = rsc = 0; + + rc = JDR_OK; + for (y = 0; y < jd->height; y += my) { /* Vertical loop of MCUs */ + for (x = 0; x < jd->width; x += mx) { /* Horizontal loop of MCUs */ + if (jd->nrst && rst++ == jd->nrst) { /* Process restart interval if enabled */ + rc = restart(jd, rsc++); + if (rc != JDR_OK) return rc; + rst = 1; + } + rc = mcu_load(jd); /* Load an MCU (decompress huffman coded stream and apply IDCT) */ + if (rc != JDR_OK) return rc; + rc = mcu_output(jd, outfunc, x, y); /* Output the MCU (color space conversion, scaling and output) */ + if (rc != JDR_OK) return rc; + } + } + + return rc; +} + + + diff --git a/Firmware_V2/src/libraries/tjpgd/tjpgd.h b/Firmware_V2/src/libraries/tjpgd/tjpgd.h new file mode 100644 index 0000000..8febb7b --- /dev/null +++ b/Firmware_V2/src/libraries/tjpgd/tjpgd.h @@ -0,0 +1,79 @@ +/*----------------------------------------------------------------------------/ +/ TJpgDec - Tiny JPEG Decompressor include file (C)ChaN, 2012 +/----------------------------------------------------------------------------*/ +#ifndef _TJPGDEC +#define _TJPGDEC +/*---------------------------------------------------------------------------*/ +/* System Configurations */ + +#define JD_SZBUF 512 /* Size of stream input buffer */ +#define JD_FORMAT 1 /* Output pixel format 0:RGB888 (3 unsigned char/pix), 1:RGB565 (1 unsigned short/pix) */ +#define JD_USE_SCALE 0 /* Use descaling feature for output */ +#define JD_TBLCLIP 1 /* Use table for saturation (might be a bit faster but increases 1K bytes of code size) */ + +/*---------------------------------------------------------------------------*/ + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Error code */ +typedef enum { + JDR_OK = 0, /* 0: Succeeded */ + JDR_INTR, /* 1: Interrupted by output function */ + JDR_INP, /* 2: Device error or wrong termination of input stream */ + JDR_MEM1, /* 3: Insufficient memory pool for the image */ + JDR_MEM2, /* 4: Insufficient stream input buffer */ + JDR_PAR, /* 5: Parameter error */ + JDR_FMT1, /* 6: Data format error (may be damaged data) */ + JDR_FMT2, /* 7: Right format but not supported */ + JDR_FMT3 /* 8: Not supported JPEG standard */ +} JRESULT; + + + +/* Rectangular structure */ +typedef struct { + unsigned short left, right, top, bottom; +} JRECT; + + + +/* Decompressor object structure */ +typedef struct JDEC JDEC; +struct JDEC { + unsigned int dctr; /* Number of bytes available in the input buffer */ + unsigned char* dptr; /* Current data read ptr */ + unsigned char* inbuf; /* Bit stream input buffer */ + unsigned char dmsk; /* Current bit in the current read byte */ + unsigned char scale; /* Output scaling ratio */ + unsigned char msx, msy; /* MCU size in unit of block (width, height) */ + unsigned char qtid[3]; /* Quantization table ID of each component */ + short dcv[3]; /* Previous DC element of each component */ + unsigned short nrst; /* Restart inverval */ + unsigned int width, height; /* Size of the input image (pixel) */ + unsigned char* huffbits[2][2]; /* Huffman bit distribution tables [id][dcac] */ + unsigned short* huffcode[2][2]; /* Huffman code word tables [id][dcac] */ + unsigned char* huffdata[2][2]; /* Huffman decoded data tables [id][dcac] */ + long* qttbl[4]; /* Dequaitizer tables [id] */ + void* workbuf; /* Working buffer for IDCT and RGB output */ + unsigned char* mcubuf; /* Working buffer for the MCU */ + void* pool; /* Pointer to available memory pool */ + unsigned int sz_pool; /* Size of momory pool (bytes available) */ + unsigned int (*infunc)(JDEC*, unsigned char*, unsigned int);/* Pointer to jpeg stream input function */ + void* device; /* Pointer to I/O device identifiler for the session */ +}; + + + +/* TJpgDec API functions */ +JRESULT jd_prepare (JDEC*, unsigned int(*)(JDEC*,unsigned char*,unsigned int), void*, unsigned int, void*); +JRESULT jd_decomp (JDEC*, unsigned int(*)(JDEC*,void*,JRECT*), unsigned char); + + +#ifdef __cplusplus +} +#endif + +#endif /* _TJPGDEC */ diff --git a/Firmware_V2/src/main.cpp b/Firmware_V2/src/main.cpp new file mode 100644 index 0000000..44e7d41 --- /dev/null +++ b/Firmware_V2/src/main.cpp @@ -0,0 +1,60 @@ +/* +* +* MAIN SKETCH - Main entry point for the firmware +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Main entry point */ +void setup() +{ + //Init the hardware components + initHardware(); + + //Check for firmware upgrade done + checkFWUpgrade(); + + //Enter USB connection if no display attached + if (checkNoDisplay()) + serialInit(); + + //Check for hardware issues + checkHardware(); + + //Do the first start setup if required + if (checkFirstStart()) + firstStart(); + + //Read all settings from EEPROM + readEEPROM(); + + //Show the live mode helper if required + if (checkLiveModeHelper()) + liveModeHelper(); + + //Go to the live Mode + liveMode(); +} + +/* Loop not used */ +void loop() +{ +} diff --git a/Firmware_V2/src/thermal/calibration.cpp b/Firmware_V2/src/thermal/calibration.cpp new file mode 100644 index 0000000..64f24e7 --- /dev/null +++ b/Firmware_V2/src/thermal/calibration.cpp @@ -0,0 +1,368 @@ +/* + * + * CALIBRATION - Functions to convert Lepton raw values to absolute values + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*###################### PRIVATE FUNCTION BODIES ##############################*/ + +/* Help function for least suqare fit */ +inline static double sqr(double x) { + return x*x; +} + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Converts a given Temperature in Celcius to Fahrenheit */ +float celciusToFahrenheit(float Tc) { + float Tf = ((float) 9.0 / 5.0) * Tc + 32.0; + return (Tf); +} + +/* Converts a given temperature in Fahrenheit to Celcius */ +float fahrenheitToCelcius(float Tf) { + float Tc = (Tf - 32.0) * ((float) 5.0 / 9.0); + return Tc; +} + +/* Function to calculate temperature out of Lepton value */ +float calFunction(uint16_t rawValue) { + //For radiometric Lepton, set offset to fixed value + if ((leptonVersion == leptonVersion_2_5_shutter) || (leptonVersion == leptonVersion_3_5_shutter)) + calOffset = -273.15; + + //For non-radiometric Lepton + else { + //Refresh MLX90614 ambient temp + ambTemp = mlx90614_getAmb(); + + //Calculate offset out of ambient temp and compensation factor + calOffset = ambTemp - (calSlope * 8192) + calComp; + } + + //Calculate the temperature in Celcius out of the coefficients + float temp = (calSlope * rawValue) + calOffset; + + //Convert to Fahrenheit if needed + if (tempFormat == tempFormat_fahrenheit) + temp = celciusToFahrenheit(temp); + return temp; +} + +/* Calculate the lepton value out of an absolute temperature */ +uint16_t tempToRaw(float temp) { + //Convert to Celcius if needed + if (tempFormat == tempFormat_fahrenheit) + temp = fahrenheitToCelcius(temp); + + //For radiometric Lepton, set offset to fixed value + if ((leptonVersion == leptonVersion_2_5_shutter) || (leptonVersion == leptonVersion_3_5_shutter)) + calOffset = -273.15; + + //For non-radiometric Lepton + else { + //Refresh MLX90614 ambient temp + ambTemp = mlx90614_getAmb(); + + //Calculate offset out of ambient temp and compensation factor + calOffset = ambTemp - (calSlope * 8192) + calComp; + } + + //Calcualte the raw value + uint16_t rawValue = (temp - calOffset) / calSlope; + return rawValue; +} + +/* Calculates the average of the 196 (14x14) pixels in the middle */ +uint16_t calcAverage() { + int sum = 0; + uint16_t val; + for (byte vert = 52; vert < 66; vert++) { + for (byte horiz = 72; horiz < 86; horiz++) { + val = smallBuffer[(vert * 160) + horiz]; + sum += val; + } + } + uint16_t avg = (uint16_t)(sum / 196.0); + return avg; +} + +/* Compensate the calibration with MLX90614 values */ +void compensateCalib() { + //Calculate compensation if auto mode enabled and no limits locked + if ((autoMode) && (!limitsLocked)) { + //Calculate min & max + int16_t min = round(calFunction(minValue)); + int16_t max = round(calFunction(maxValue)); + + //If spot temp is lower than current minimum by one degree, lower minimum + if (spotTemp < (min - 1)) + calComp = spotTemp - min; + + //If spot temp is higher than current maximum by one degree, raise maximum + else if (spotTemp > (max + 1)) + calComp = spotTemp - max; + } +} + +/* Checks if the calibration warmup is done */ +void checkWarmup() { + //Activate the calibration after a warmup time of 30s + if (((calStatus == cal_warmup) && (millis() - calTimer > 30000))) { + //Set calibration status to standard + calStatus = cal_standard; + + //Disable auto FFC when isotherm mode + if (hotColdMode != hotColdMode_disabled) + lepton_ffcMode(false); + + //Read temperature limits + readTempLimits(); + + //If we loaded one + if (!autoMode) + { + //Switch to manual FFC mode + if (leptonShutter == leptonShutter_auto) + lepton_ffcMode(false); + + //Disable limits locked + limitsLocked = false; + } + } +} + +/* Least square fit */ +int linreg(int n, const uint16_t x[], const float y[], float* m, float* b, float* r) +{ + double sumx = 0.0; + double sumx2 = 0.0; + double sumxy = 0.0; + double sumy = 0.0; + double sumy2 = 0.0; + for (int i = 0; i < n; i++) + { + sumx += x[i]; + sumx2 += sqr(x[i]); + sumxy += x[i] * y[i]; + sumy += y[i]; + sumy2 += sqr(y[i]); + } + double denom = (n * sumx2 - sqr(sumx)); + if (denom == 0) { + //singular matrix. can't solve the problem. + *m = 0; + *b = 0; + *r = 0; + return 1; + } + *m = (n * sumxy - sumx * sumy) / denom; + *b = (sumy * sumx2 - sumx * sumxy) / denom; + if (r != NULL) { + *r = (sumxy - sumx * sumy / n) / + sqrt((sumx2 - sqr(sumx) / n) * + (sumy2 - sqr(sumy) / n)); + } + return 0; +} + +/* Run the calibration process */ +void calibrationProcess(bool serial, bool firstStart) { + //When in serial mode and using radiometric Lepton, return invalid command + if (serial && ((leptonVersion == leptonVersion_2_5_shutter) || (leptonVersion == leptonVersion_3_5_shutter))) { + Serial.write(CMD_INVALID); + return; + } + + //Variables + float calMLX90614[100]; + uint16_t calLepton[100]; + float calCorrelation; + char result[30]; + uint16_t average; + uint16_t average_old = 0; + float temp; + float temp_old = 0; + maxValue = 0; + minValue = 65535; + + //Repeat as long as there is a good calibration + do { + //Show the screen when not using serial mode + if (!serial) + calibrationScreen(firstStart); + + //Reset counter to zero + int counter = 0; + + //Get 100 different calibration samples + while (counter < 100) { + //Store time elapsed + long timeElapsed = millis(); + + //Repeat measurement as long as there is no new average + do { + //Safe delay for bad PCB routing + delay(10); + //Get temperatures + lepton_getRawValues(); + //Calculate the average + average = calcAverage(); + } while ((average == average_old) || (average == 0)); + + //Store old average + average_old = average; + + //Measure new value from MLX90614 + temp = mlx90614_getTemp(); + + //If the temperature changes too much, do not take that measurement + if (abs(temp - temp_old) < 10) { + //Store values + calLepton[counter] = average; + calMLX90614[counter] = temp; + + //Find minimum and maximum value + if (average > maxValue) + maxValue = average; + if (average < minValue) + minValue = average; + + //Refresh status on screen in steps of 10, not for serial + if (((counter % 10) == 0) && !serial) { + char buffer[20]; + sprintf(buffer, "Status: %2d%%", counter); + display_print(buffer, CENTER, 140); + } + + //When doing this in serial mode, print counter + if (serial) + Serial.write(counter); + + //Raise counter + counter++; + } + + //Store old spot temperature + temp_old = temp; + + //Wait at least 111ms between two measurements (9Hz) + while ((millis() - timeElapsed) < 111); + + //If the user wants to abort and is not in first start or serial mode + if (touch_touched() && !firstStart && !serial) { + int pressedButton = buttons_checkButtons(true); + //Abort + if (pressedButton == 0) + return; + } + } + + //Calculate the calibration formula with least square fit + linreg(100, calLepton, calMLX90614, &calSlope, &calOffset, &calCorrelation); + + //Refresh MLX90614 ambient temp + ambTemp = mlx90614_getAmb(); + + //Calculate compensation + calComp = calOffset - ambTemp + (calSlope * 8192); + + //When in serial mode, store and send ACK + if (serial) + { + //Set shutter mode back to auto + lepton_ffcMode(true); + + //Save calibration to EEPROM + storeCalibration(); + + //Send ACK + Serial.write(CMD_SET_CALIBRATION); + + //Leave + return; + } + + //In case the calibration was not good, ask to repeat + if (calCorrelation < 0.5) { + + //When in first start mode + if (firstStart) { + showFullMessage((char*) "Bad calibration, try again", true); + delay(1000); + } + + //If the user does not want to repeat, discard + else if (!calibrationRepeat()) { + calSlope = cal_stdSlope; + calStatus = cal_standard; + break; + } + } + } while (calCorrelation < 0.5); + + //Show the result + sprintf(result, "Slope: %1.4f, offset: %.1f", calSlope, calOffset); + showFullMessage(result); + delay(2000); + + //Save calibration to EEPROM + storeCalibration(); + + //Show message if not in first start menu + if (firstStart == false) { + showFullMessage((char*) "Calibration written to EEPROM", true); + delay(1000); + } + + //Restore old font + display_setFont(smallFont); +} + +/* Calibration */ +bool calibration() { + //Still in warmup + if (calStatus == cal_warmup) { + showFullMessage((char*) "Please wait for sensor warmup", true); + delay(1000); + return false; + } + + //Radiometric Lepton, no calibration needed + if ((leptonVersion == leptonVersion_2_5_shutter) || (leptonVersion == leptonVersion_3_5_shutter)) { + showFullMessage((char*) "Not required for radiometric Lepton", true); + delay(1000); + return false; + } + + //Do a new one + calibrationProcess(); + + return true; +} diff --git a/Firmware_V2/src/thermal/calibration.h b/Firmware_V2/src/thermal/calibration.h new file mode 100644 index 0000000..0bb593a --- /dev/null +++ b/Firmware_V2/src/thermal/calibration.h @@ -0,0 +1,32 @@ +/* +* +* CALIBRATION - Functions to convert Lepton raw values to absolute values +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef CALIBRATION_H +#define CALIBRATION_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +uint16_t calcAverage(); +float calFunction(uint16_t rawValue); +bool calibration(); +void calibrationProcess(bool serial = false, bool firstStart = false); +float celciusToFahrenheit(float Tc); +void checkWarmup(); +void compensateCalib(); +float fahrenheitToCelcius(float Tf); +int linreg(int n, const uint16_t x[], const float y[], float* m, float* b, float* r); +uint16_t tempToRaw(float temp); + +#endif /* CALIBRATION_H */ diff --git a/Firmware_V2/src/thermal/create.cpp b/Firmware_V2/src/thermal/create.cpp new file mode 100644 index 0000000..7d47d6b --- /dev/null +++ b/Firmware_V2/src/thermal/create.cpp @@ -0,0 +1,779 @@ +/* +* +* CREATE - Functions to create and display the thermal frameBuffer +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Filter a 160x120 smallBuffer with 3x3 gaussian kernel */ +void gaussianFilter() { + byte gaussianKernel[3][3] = { + { 1, 2, 1 }, + { 2, 4, 2 }, + { 1, 2, 1 } + }; + long sum; + + for (int y = 1; y < 119; y++) { + for (int x = 1; x < 159; x++) { + sum = 0; + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + sum += gaussianKernel[j + 1][k + 1] * smallBuffer[(y - j) * 160 + (x - k)]; + } + } + smallBuffer[(y * 160) + x] = (unsigned short)(sum / 16.0); + } + } +} + +/* Filter a 160x120 smallBuffer with a 3x3 box kernel */ +void boxFilter() { + byte boxKernel[3][3] = { + { 1, 1, 1 }, + { 1, 1, 1 }, + { 1, 1, 1 } + }; + long sum; + + for (int y = 1; y < 119; y++) { + for (int x = 1; x < 159; x++) { + sum = 0; + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + sum += boxKernel[j + 1][k + 1] * smallBuffer[(y - j) * 160 + (x - k)]; + } + } + smallBuffer[(y * 160) + x] = (unsigned short)(sum / 9.0); + } + } +} + +//Resize the pixels of thermal smallBuffer +void resizePixels(unsigned short* pixels, int w1, int h1, int w2, int h2) { + //Calculate the ratio + int x_ratio = (int)((w1 << 16) / w2) + 1; + int y_ratio = (int)((h1 << 16) / h2) + 1; + int x2, y2; + for (int i = 0; i < h2; i++) { + for (int j = 0; j < w2; j++) { + x2 = ((j * x_ratio) >> 16); + y2 = ((i * y_ratio) >> 16); + pixels[(i * w1) + j] = pixels[(y2 * w1) + x2]; + } + } + //Set the other pixels to zero + for (int j = 0; j < h2; j++) { + for (int i = w2; i < w1; i++) { + pixels[i + (j * w1)] = 65535; + } + } + for (int j = h2; j < h1; j++) { + for (int i = 0; i < w1; i++) { + pixels[i + (j * w1)] = 65535; + } + } +} + +/* Resize the thermal smallBuffer */ +void resizeImage() { + uint16_t xmax, ymax; + unsigned short* pixelBuffer; + + //For 160x120 + if ((teensyVersion == teensyVersion_old) || (!hqRes)) + { + xmax = 160; + ymax = 120; + pixelBuffer = smallBuffer; + } + //For 320x240 + else + { + xmax = 320; + ymax = 240; + pixelBuffer = bigBuffer; + } + + uint16_t newWidth = round(adjCombFactor * xmax); + uint16_t newHeight = round(adjCombFactor * ymax); + + //Resize pixels + resizePixels(pixelBuffer, xmax, ymax, newWidth, newHeight); + + //Move the smallBuffer right + uint16_t rightMove = round((xmax - newWidth) / 2.0); + for (int i = 0; i < rightMove; i++) { + //First for all pixels + for (int col = (xmax - 1); col > 0; col--) + for (int row = 0; row < ymax; row++) + pixelBuffer[col + (row * xmax)] = pixelBuffer[col - 1 + (row * xmax)]; + //And then fill the last one with white + for (int row = 0; row < ymax; row++) + pixelBuffer[row * xmax] = 65535; + } + //Move the smallBuffer down + uint16_t downMove = round((ymax - newHeight) / 2.0); + for (int i = 0; i < downMove; i++) { + //First for all pixels + for (int col = 0; col < xmax; col++) + for (int row = (ymax - 1); row > 0; row--) + pixelBuffer[col + (row * xmax)] = pixelBuffer[col + ((row - 1) * xmax)]; + //And then fill the last one with white + for (int col = 0; col < xmax; col++) + pixelBuffer[col] = 65535; + } +} + +/* Calculates the fill pixel for visual/combined */ +void calcFillPixel(uint16_t x, uint16_t y) { + uint16_t pixel; + byte red, green, blue; + + //Combined - set to darker thermal + if (displayMode == displayMode_combined) { + //Get the thermal image color + pixel = smallBuffer[x + (y * 160)]; + //And extract the RGB values out of it + byte redT = (pixel & 0xF800) >> 8; + byte greenT = (pixel & 0x7E0) >> 3; + byte blueT = (pixel & 0x1F) << 3; + //Mix both + red = (byte)redT * (1 - adjCombAlpha) + 127 * adjCombAlpha; + green = (byte)greenT * (1 - adjCombAlpha) + 127 * adjCombAlpha; + blue = (byte)blueT * (1 - adjCombAlpha) + 127 * adjCombAlpha; + } + //Visual - set to black + else { + red = 0; + green = 0; + blue = 0; + } + + //Set image to that calculated RGB565 value + pixel = (((red & 248) | green >> 5) << 8) + | ((green & 28) << 3 | blue >> 3); + //Save + smallBuffer[x + (y * 160)] = pixel; +} + +/* Fill out the edges in combined or visual mode */ +void fillEdges() { + //Fill the edges + uint16_t x, y; + + //Top & Bottom edges + for (x = 0; x < 160; x++) { + //Top edge + for (y = 0; y < (5 * adjCombDown); y++) { + calcFillPixel(x, y); + } + + //Bottom edge + for (y = 119; y > (119 - (5 * adjCombUp)); y--) { + calcFillPixel(x, y); + } + } + + //Left & right edges + for (y = 5 * adjCombDown; y < (120 - (5 * adjCombUp)); y++) { + //Left edge + for (x = 0; x < (5 * adjCombRight); x++) { + calcFillPixel(x, y); + } + + //Right edge + for (x = 159; x > (159 - (5 * adjCombLeft)); x--) { + calcFillPixel(x, y); + } + } +} + + +/* Write the smallBuffer to the bigBuffer by resizing, eventually add transparency */ +void smallToBigBuffer(bool trans) +{ + unsigned short A, B, C, D, outVal; + int x, y, index; + float x_ratio = 159.0 / 320; + float y_ratio = 119.0 / 240; + float x_diff, y_diff; + //For transparency + byte redV, greenV, blueV, redT, greenT, blueT, red, green, blue; + uint16_t value; + float scale = (colorElements - 1.0) / (maxValue - minValue); + + int offset = 0; + for (int i = 0; i < 240; i++) { + for (int j = 0; j < 320; j++) { + + x = (int)(x_ratio * j); + y = (int)(y_ratio * i); + x_diff = (x_ratio * j) - x; + y_diff = (y_ratio * i) - y; + index = y * 160 + x; + + A = smallBuffer[index]; + B = smallBuffer[index + 1]; + C = smallBuffer[index + 160]; + D = smallBuffer[index + 160 + 1]; + + outVal = (unsigned short)( + A*(1 - x_diff)*(1 - y_diff) + B*(x_diff)*(1 - y_diff) + + C*(y_diff)*(1 - x_diff) + D*(x_diff*y_diff) + ); + + //No alpha transparency, just write into the framebuffer + if (trans == false) + bigBuffer[offset] = outVal; + + //We want to create a combined bigBuffer + else + { + //Limit values + if (outVal > maxValue) + outVal = maxValue; + if (outVal < minValue) + outVal = minValue; + + //Calc color position + value = (outVal - minValue) * scale; + + //Calc RGB color values for thermal smallBuffer + redT = colorMap[3 * value]; + greenT = colorMap[3 * value + 1]; + blueT = colorMap[3 * value + 2]; + + //Get the RGB565 color of the visual smallBuffer + value = bigBuffer[offset]; + + //And extract the RGB values out of it + redV = (value & 0xF800) >> 8; + greenV = (value & 0x7E0) >> 3; + blueV = (value & 0x1F) << 3; + + //Mix both + red = redT * (1 - adjCombAlpha) + redV * adjCombAlpha; + green = greenT * (1 - adjCombAlpha) + greenV * adjCombAlpha; + blue = blueT * (1 - adjCombAlpha) + blueV * adjCombAlpha; + + //Convert to RGB565 + bigBuffer[offset] = (((red & 248) | green >> 5) << 8) | ((green & 28) << 3 | blue >> 3); + } + + //Raise counter + offset++; + } + } +} + +/* Clears the temperature points array */ +void clearTempPoints() { + //Go through the array + for (byte i = 0; i < 96; i++) { + //Set the index to zero + tempPoints[i][0] = 0; + //Set the value to zero + tempPoints[i][1] = 0; + } +} + +/* Shows the temperatures over the smallBuffer on the screen */ +void showTemperatures() { + int16_t xpos, ypos; + + //Go through the array + for (byte i = 0; i < 96; i++) { + //Get index + uint16_t index = tempPoints[i][0]; + + //Check if the tempPoint is active + if (index != 0) { + //Index goes from 1 to max + index -= 1; + + //Calculate x and y position + calculatePointPos(&xpos, &ypos, index); + + //Draw the marker + display_drawLine(xpos, ypos, xpos, ypos); + + //Calc x position for the text + xpos -= 20; + if (xpos < 0) + xpos = 0; + if (xpos > 279) + xpos = 279; + + //Calc y position for the text + ypos += 15; + if (ypos > 229) + ypos = 229; + + //Display the absolute temperature + display_printNumF(calFunction(tempPoints[i][1]), 2, xpos, ypos); + } + } +} + +/* Read the x and y coordinates when touch screen is pressed for Add Point */ +void getTouchPos(uint16_t* x, uint16_t* y) { + int iter = 0; + TS_Point point; + unsigned long tx = 0; + unsigned long ty = 0; + //Wait for touch press + while (!touch_touched()); + //While touch pressed, iterate over readings + while (touch_touched() == true) { + point = touch_getPoint(); + if ((point.x >= 0) && (point.x <= 320) && (point.y >= 0) + && (point.y <= 240)) { + tx += point.x; + ty += point.y; + iter++; + } + } + *x = tx / iter; + *y = ty / iter; +} + +/* Function to add or remove a measurement point */ +void tempPointFunction(bool remove) { + uint16_t xpos, ypos; + byte pos = 0; + bool removed = false; + + //If remove points, check if there are some first + if (remove) { + //Go through the array + for (byte i = 0; i < 96; i++) { + if (tempPoints[i][0] != 0) + { + removed = true; + break; + } + } + //No points available to remove + if (!removed) { + showFullMessage((char*) "No points available", true); + delay(1000); + return; + } + } + //If add points, check if we have space left + else + { + //Go through the array + byte i; + for (i = 0; i < 96; i++) { + if (tempPoints[i][0] == 0) + { + pos = i; + break; + } + } + //Maximum number of points added + if (i == 96) { + showFullMessage((char*) "Remove a point first", true); + delay(1000); + return; + } + } + +redraw: + //Safe delay + delay(10); + + //Create thermal smallBuffer + if (displayMode == displayMode_thermal) + createThermalImg(); + //Create visual or combined smallBuffer + else + createVisCombImg(); + + //Show it on the screen + displayBuffer(); + + //Set text color, font and background + changeTextColor(); + display_setBackColor(VGA_TRANSPARENT); + display_setFont(smallFont); + //Show current temperature points + showTemperatures(); + //Display title + display_setFont(bigFont); + display_print((char*) "Select position", CENTER, 210); + + //Get touched coordinates + getTouchPos(&xpos, &ypos); + + //Divide through 2 to match array size + xpos /= 2; + ypos /= 2; + + //Remove point + if (remove) { + //Set marker to false + removed = false; + + //Check for 10 pixels around the touch position + for (uint16_t x = xpos - 10; x <= xpos + 10; x++) { + for (uint16_t y = ypos - 10; y <= ypos + 10; y++) { + //Calculate index number + uint16_t index = x + (y * 160) + 1; + //If index is valid + if ((index >= 1) && (index <= 19200)) { + //Check for all 96 points + for (byte i = 0; i < 96; i++) + { + //We found a valid temperature point + if (tempPoints[i][0] == index) + { + //Set to invalid + tempPoints[i][0] = 0; + //Reset value + tempPoints[i][1] = 0; + //Set markter to true + removed = true; + } + } + } + } + } + //Show border + drawMainMenuBorder(); + //Show removed message + if (removed) + showFullMessage((char*) "Point removed", true); + //Invalid position, redraw + else { + showFullMessage((char*) "Invalid position", true); + delay(1000); + goto redraw; + } + } + + //Add point + else { + //Add index + tempPoints[pos][0] = xpos + (ypos * 160) + 1; + //Set raw value to zero + tempPoints[pos][1] = 0; + //Show border + drawMainMenuBorder(); + //Show message + showFullMessage((char*) "Point added", true); + } + + //Wait some time + delay(1000); +} + +/* Go through the array of temperatures and find min and max temp */ +void limitValues() { + maxValue = 0; + minValue = 65535; + uint16_t temp; + for (int i = 0; i < 19200; i++) { + //Get value + temp = smallBuffer[i]; + //Find maximum temp + if (temp > maxValue) + maxValue = temp; + //Find minimum temp + if (temp < minValue) + minValue = temp; + } +} + +/* Get the colors for hot / cold mode selection */ +void getHotColdColors(byte* red, byte* green, byte* blue) { + switch (hotColdColor) { + //White + case hotColdColor_white: + *red = 255; + *green = 255; + *blue = 255; + break; + //Black + case hotColdColor_black: + *red = 0; + *green = 0; + *blue = 0; + break; + //White + case hotColdColor_red: + *red = 255; + *green = 0; + *blue = 0; + break; + //White + case hotColdColor_green: + *red = 0; + *green = 255; + *blue = 0; + break; + //White + case hotColdColor_blue: + *red = 0; + *green = 0; + *blue = 255; + break; + } +} + +/* Convert the lepton values to RGB colors */ +void convertColors(bool small) { + uint8_t red = 0; + uint8_t green = 0; + uint8_t blue = 0; + uint16_t value; + + //Calculate the scale + float scale = (colorElements - 1.0) / (maxValue - minValue); + + //For hot and cold mode, calculate rawlevel + float hotColdRawLevel = 0.0; + if ((hotColdMode != hotColdMode_disabled) && (displayMode != displayMode_combined)) + hotColdRawLevel = tempToRaw(hotColdLevel); + + //Size of the array & buffer + int size; + unsigned short* frameBuffer; + //Teensy 3.6, not for preview + if ((teensyVersion == teensyVersion_new) && (!small) && (hqRes)) { + size = 76800; + frameBuffer = bigBuffer; + } + //Teensy 3.1 / 3.2 or preview + else { + size = 19200; + frameBuffer = smallBuffer; + } + + //Repeat for 160x120 data + for (int i = 0; i < size; i++) { + + value = frameBuffer[i]; + + //Limit values + if (value > maxValue) + value = maxValue; + if (value < minValue) + value = minValue; + + //Hot + if ((hotColdMode == hotColdMode_hot) && (value >= hotColdRawLevel) && (calStatus != cal_warmup) && (displayMode != displayMode_combined)) + getHotColdColors(&red, &green, &blue); + //Cold + else if ((hotColdMode == hotColdMode_cold) && (value <= hotColdRawLevel) && (calStatus != cal_warmup) && (displayMode != displayMode_combined)) + getHotColdColors(&red, &green, &blue); + //Apply colorscheme + else { + value = (value - minValue) * scale; + red = colorMap[3 * value]; + green = colorMap[3 * value + 1]; + blue = colorMap[3 * value + 2]; + } + + //Convert to RGB565 + frameBuffer[i] = (((red & 248) | green >> 5) << 8) | ((green & 28) << 3 | blue >> 3); + } +} + +/* Refresh the position and value of the min / max value */ +void refreshMinMax() +{ + //Reset values + minTempVal = 65535; + maxTempVal = 0; + + //Go through the smallBuffer + for (int i = 0; i < 19200; i++) + { + //We found a new min + if (smallBuffer[i] < minTempVal) + { + //Save position and value + minTempPos = i; + minTempVal = smallBuffer[i]; + } + + //We found a new max + if (smallBuffer[i] > maxTempVal) + { + maxTempPos = i; + maxTempVal = smallBuffer[i]; + } + } +} + +/* Refresh the temperature points*/ +void refreshTempPoints() { + //Go through the array + for (byte i = 0; i < 96; i++) { + //Get index + uint16_t index = tempPoints[i][0]; + + //Check if point is active + if (index != 0) { + //Index goes from 1 to max + index -= 1; + + //Calculate x and y position + uint16_t xpos = index % 160; + uint16_t ypos = index / 160; + + //Update value + tempPoints[i][1] = smallBuffer[xpos + (ypos * 160)]; + } + } +} + +/* Calculate the x and y position out of the pixel index */ +void calculatePointPos(int16_t* xpos, int16_t* ypos, uint16_t pixelIndex) { + //Get xpos and ypos + *xpos = (pixelIndex % 160) * 2; + *ypos = (pixelIndex / 160) * 2; + + //Limit position + if (*ypos > 238) + *ypos = 228; + if (*xpos > 318) + *xpos = 318; +} + +/* Creates a thermal smallBuffer and stores it in the array */ +void createThermalImg(bool small) { + //Receive the temperatures over SPI + lepton_getRawValues(); + + //Get the spot temperature + getSpotTemp(); + + //Compensate calibration with MLX90614 for non-radiometric Lepton + if ((leptonVersion != leptonVersion_2_5_shutter) && (leptonVersion != leptonVersion_3_5_shutter)) + compensateCalib(); + + //Refresh the temp points if required + refreshTempPoints(); + + //Find min / max position + if (minMaxPoints != minMaxPoints_disabled) + refreshMinMax(); + + //Find min and max if not in manual mode and limits not locked + if ((autoMode) && (!limitsLocked)) + limitValues(); + + //If smallBuffer save, save the raw data + if (imgSave == imgSave_create) + saveRawData(true, saveFilename); + + //Apply low-pass filter + if (filterType == filterType_box) + boxFilter(); + else if (filterType == filterType_gaussian) + gaussianFilter(); + + //Teensy 3.6 - Resize to big buffer when HQRes and not preview + if ((teensyVersion == teensyVersion_new) && (!small) && (hqRes)) + smallToBigBuffer(); + + //Convert lepton data to RGB565 colors + if (!videoSave) + convertColors(small); +} + +/* Create the visual or combined smallBuffer display */ +void createVisCombImg() { + //Capture new frame from camera + camera_capture(); + + //Receive the temperatures over SPI + lepton_getRawValues(); + + //Get the spot temperature + getSpotTemp(); + + //Compensate calibration with MLX90614 for non-radiometric Lepton + if ((leptonVersion != leptonVersion_2_5_shutter) && (leptonVersion != leptonVersion_3_5_shutter)) + compensateCalib(); + + //Refresh the temp points if required + refreshTempPoints(); + + //Find min / max position + if (minMaxPoints != minMaxPoints_disabled) + refreshMinMax(); + + //Find min and max if not in manual mode and limits not locked + if ((autoMode) && (!limitsLocked)) + limitValues(); + + //For 320x240 resolution, decompress visual image before thermal + if ((teensyVersion == teensyVersion_new) && (hqRes)) + { + //Fill array white + for (uint32_t i = 0; i < 76800; i++) + bigBuffer[i] = 65535; + //Get image from cam + camera_get(camera_stream); + //Resize the image + resizeImage(); + } + + //For combined only + if (displayMode == displayMode_combined) { + //Apply low-pass filter + if (filterType == filterType_box) + boxFilter(); + else if (filterType == filterType_gaussian) + gaussianFilter(); + + //Teensy 3.6 with HQRes - Resize to big buffer and create transparency + if ((teensyVersion == teensyVersion_new) && (hqRes)) + smallToBigBuffer(true); + //Teensy 3.1 / 3.2 - Convert raw values to RGB565 + else + convertColors(); + } + + //For low resolution, decompress visual image after thermal image + if ((teensyVersion == teensyVersion_old) || (!hqRes)) + { + //Resize the thermal image + resizeImage(); + //Fill the edges of the thermal image + fillEdges(); + //Get the visual image and decompress it combined + camera_get(camera_stream); + } +} diff --git a/Firmware_V2/src/thermal/create.h b/Firmware_V2/src/thermal/create.h new file mode 100644 index 0000000..cfaea6d --- /dev/null +++ b/Firmware_V2/src/thermal/create.h @@ -0,0 +1,41 @@ +/* +* +* CREATE - Functions to create and display the thermal frameBuffer +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef CREATE_H +#define CREATE_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void boxFilter(); +void calcFillPixel(uint16_t x, uint16_t y); +void calculatePointPos(int16_t* xpos, int16_t* ypos, uint16_t pixelIndex); +void clearTempPoints(); +void convertColors(bool small = false); +void createThermalImg(bool small = false); +void createVisCombImg(); +void fillEdges(); +void gaussianFilter(); +void getHotColdColors(byte* red, byte* green, byte* blue); +void getTouchPos(uint16_t* x, uint16_t* y); +void limitValues(); +void refreshMinMax(); +void refreshTempPoints(); +void resizeImage(); +void resizePixels(unsigned short* pixels, int w1, int h1, int w2, int h2); +void showTemperatures(); +void smallToBigBuffer(bool trans = false); +void tempPointFunction(bool remove = false); + +#endif /* CREATE_H */ diff --git a/Firmware_V2/src/thermal/load.cpp b/Firmware_V2/src/thermal/load.cpp new file mode 100644 index 0000000..a457889 --- /dev/null +++ b/Firmware_V2/src/thermal/load.cpp @@ -0,0 +1,1323 @@ +/* +* +* LOAD - Load images and videos from the internal storage +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*################# DATA TYPES, CONSTANTS & MACRO DEFINITIONS #################*/ + +#define lepton2_small 9621 +#define lepton2_big 10005 +#define lepton3_small 38421 +#define lepton3_big 38805 +#define bitmap 614466 +#define maxFiles 500 +#define loadMode_image 0 +#define loadMode_video 1 + +/*######################### STATIC DATA DECLARATIONS ##########################*/ + +//Storage for up to maxFiles images/videos +static uint16_t* yearStorage; +static byte* monthStorage; +static byte* dayStorage; +static byte* hourStorage; +static byte* minuteStorage; +static byte* secondStorage; + +//Buffer for the single elements +static char yearBuf[] = "2016"; +static char monthBuf[] = "12"; +static char dayBuf[] = "31"; +static char hourBuf[] = "23"; +static char minuteBuf[] = "60"; +static char secondBuf[] = "60"; + +//Save how many different elements we have +static byte yearnum = 0; +static byte monthnum = 0; +static byte daynum = 0; +static byte hournum = 0; +static byte minutenum = 0; +static byte secondnum = 0; + +//Keep track how many images are on the SDCard +static int imgCount = 0; + +//Decide if we load videos or images +static bool loadMode; + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Clear all previous data */ +void clearData() { + for (int i = 0; i < maxFiles; i++) { + yearStorage[i] = 0; + monthStorage[i] = 0; + dayStorage[i] = 0; + hourStorage[i] = 0; + minuteStorage[i] = 0; + secondStorage[i] = 0; + } + strcpy(yearBuf, "2016"); + strcpy(monthBuf, "12"); + strcpy(dayBuf, "31"); + strcpy(hourBuf, "23"); + strcpy(minuteBuf, "60"); + strcpy(secondBuf, "60"); + yearnum = 0; + monthnum = 0; + daynum = 0; + hournum = 0; + minutenum = 0; + secondnum = 0; + imgCount = 0; + clearTempPoints(); +} + +/* Display the image on the screen */ +void displayRawData() { + //Select Color Scheme + selectColorScheme(); + + //Apply low-pass filter + if (filterType == filterType_box) + boxFilter(); + else if (filterType == filterType_gaussian) + gaussianFilter(); + + //Find min / max position + if (minMaxPoints != minMaxPoints_disabled) + refreshMinMax(); + + //Teensy 3.6 - Resize to big buffer + if (teensyVersion == teensyVersion_new) + smallToBigBuffer(); + + //Convert lepton data to RGB565 colors + convertColors(); + + //Display additional information + displayInfos(); + + //Display on screen + displayBuffer(); +} + +/* Loads a 640x480 BMP image from the SDCard and prints it on screen */ +void loadBMPImage(char* filename) { + //Help variables + byte low, high; + //Switch Clock to Alternative + startAltClockline(); + // Open the file for reading + sdFile.open(filename, O_READ); + + //Skip the 66 bytes BMP header + for (int i = 0; i < 66; i++) + sdFile.read(); + + //For Teensy 3.1 / 3.2, repeat the procedure 4 times to fill all the buffers + if (teensyVersion == teensyVersion_old) + { + for (int i = 3; i >= 0; i--) { + //Save those 20 lines to the SD card. Ascending to mirror vertically + for (int y = 119; y >= 0; y--) { + for (int x = 0; x < 640; x++) { + low = sdFile.read(); + high = sdFile.read(); + //Get the image color + smallBuffer[(x / 2) + ((y / 2) * 320)] = (high << 8) | low; + } + } + //End alternative clock line + endAltClockline(); + //Draw on the screen + display_drawBitmap(0, i * 60, 320, 60, smallBuffer); + //Start alternative clock line + startAltClockline(); + } + //Close data file + sdFile.close(); + //Switch clock back + endAltClockline(); + } + //For Teensy 3.6, use the big buffer + else + { + for (int y = 479; y >= 0; y--) + { + for (int x = 0; x < 640; x++) { + low = sdFile.read(); + high = sdFile.read(); + //Get the image color + bigBuffer[(x/2) + ((y / 2) * 320)] = (high << 8) | low; + } + } + //Close data file + sdFile.close(); + //Switch clock back + endAltClockline(); + //Draw it on the screen + display_drawBitmap(0, 0, 320, 240, bigBuffer); + } +} + +/* Checks if the file is an image*/ +bool isImage(char* filename) { + bool isImg; + + //Switch Clock to Alternative + startAltClockline(true); + //Open file + sdFile.open(filename, O_READ); + + //Check if it is a file + if (sdFile.isFile()) + isImg = true; + + //Otherwise it is a video + else { + //Delete the ending + filename[14] = '\0'; + isImg = false; + } + + sdFile.close(); + //Switch Clock back to Standard + endAltClockline(); + + return isImg; +} + +/* Read the temperature points from the file, new or old format */ +void readTempPoints() +{ + uint16_t tempArray[192]; + bool oldFormat = false; + + //Read the array + for (byte i = 0; i < 192; i++) + { + //Read from SD file + tempArray[i] = (sdFile.read() << 8) + sdFile.read(); + + //Correct old not_set marker + if (tempArray[i] == 65535) + tempArray[i] = 0; + } + + //Check for old format + for (byte i = 1; i < 191; i++) + { + //Valid value found + if (tempArray[i] != 0) + { + //If the value before and after is zero, it is the old format + if ((tempArray[i - 1] == 0) && (tempArray[i + 1] == 0)) + oldFormat = true; + } + } + + //Load the old format + if (oldFormat) + { + //Position counter + byte pos = 0; + + //Go through the array + for (byte i = 0; i < 192; i++) + { + //Valid value found + if (tempArray[i] != 0) + { + //Calculate x and y position + uint16_t xpos = (i % 16) * 10; + uint16_t ypos = (i / 16) * 10; + + //Calculate index + tempPoints[pos][0] = xpos + (ypos * 160) + 1; + //Save value + tempPoints[pos][1] = tempArray[i]; + + //Raise position + pos++; + //When maximum number of temp points reached, exit + if (pos == 96) + return; + } + } + } + + //Load the new format + else + { + //Go through the array + for (byte i = 0; i < 96; i++) { + //Read index + tempPoints[i][0] = tempArray[(i * 2)]; + //Read value + tempPoints[i][1] = tempArray[(i * 2) + 1]; + } + } +} + +/* Loads raw data from the internal storage*/ +void loadRawData(char* filename, char* dirname) { + byte msb, lsb; + uint16_t result; + uint32_t fileSize; + + //Switch Clock to Alternative + startAltClockline(); + + //Go into the video folder if video + if (dirname != NULL) + sd.chdir(dirname); + + // Open the file for reading + sdFile.open(filename, O_READ); + + //Get file size + fileSize = sdFile.fileSize(); + + //For the Lepton2 sensor, read 4800 raw values + if ((fileSize == lepton2_small) || (fileSize == lepton2_big)) { + for (int line = 0; line < 60; line++) { + for (int column = 0; column < 80; column++) { + msb = sdFile.read(); + lsb = sdFile.read(); + result = (((msb) << 8) + lsb); + smallBuffer[(line * 2 * 160) + (column * 2)] = result; + smallBuffer[(line * 2 * 160) + (column * 2) + 1] = result; + smallBuffer[(line * 2 * 160) + 160 + (column * 2)] = result; + smallBuffer[(line * 2 * 160) + 160 + (column * 2) + 1] = result; + } + } + } + + //For the Lepton3 sensor, read 19200 raw values + else if ((fileSize == lepton3_small) || (fileSize == lepton3_big)) { + for (int i = 0; i < 19200; i++) { + msb = sdFile.read(); + lsb = sdFile.read(); + smallBuffer[i] = (((msb) << 8) + lsb); + } + } + //Invalid data + else { + showFullMessage((char*) "Invalid file size"); + delay(1000); + sdFile.close(); + endAltClockline(); + return; + } + + //Read Min + msb = sdFile.read(); + lsb = sdFile.read(); + minValue = (((msb) << 8) + lsb); + //Read Max + msb = sdFile.read(); + lsb = sdFile.read(); + maxValue = (((msb) << 8) + lsb); + + //Read object temperature + uint8_t farray[4]; + for (int i = 0; i < 4; i++) + farray[i] = sdFile.read(); + spotTemp = bytesToFloat(farray); + + //Read color scheme + colorScheme = sdFile.read(); + //Read temp format + tempFormat = sdFile.read(); + //Read spot enabled + spotEnabled = sdFile.read(); + //Read colorbar enabled + colorbarEnabled = sdFile.read(); + //Read min max enabled + minMaxPoints = sdFile.read(); + + //Read calibration offset + for (int i = 0; i < 4; i++) + farray[i] = sdFile.read(); + calOffset = bytesToFloat(farray); + //Read calibration slope + for (int i = 0; i < 4; i++) + farray[i] = sdFile.read(); + calSlope = bytesToFloat(farray); + + //Lepton2.x + if ((fileSize == lepton2_small) || (fileSize == lepton2_big)) { + //Radiometric Lepton2.5 + if ((calOffset == -273.15) && (calSlope == 0.01)) + { + leptonVersion = leptonVersion_2_5_shutter; + } + //Non-radiometric Lepton2.0 + else + { + leptonVersion = leptonVersion_2_0_shutter; + } + } + + //Lepton3.x + if ((fileSize == lepton3_small) || (fileSize == lepton3_big)) { + //Radiometric Lepton3.5 + if ((calOffset == -273.15) && (calSlope == 0.01)) + { + leptonVersion = leptonVersion_3_5_shutter; + } + //Non-radiometric Lepton3.5 + else + { + leptonVersion = leptonVersion_3_0_shutter; + } + } + + //Clear temperature points array + clearTempPoints(); + + //Read temperatures if they are included + if ((fileSize == lepton3_big) || (fileSize == lepton2_big)) + readTempPoints(); + + //Close data file + sdFile.close(); + //Switch clock back + endAltClockline(); +} + +/* A method to choose the right yearStorage */ +bool yearChoose(char* filename) { + //Can imgCount up to 50 years + bool years[50] = { 0 }; + //Go through all images + for (int i = 0; i < imgCount; i++) { + int yearCheck = yearStorage[i] - 2016; + //Check if the yearStorage is at least 2016 + if (yearCheck < 0) { + //if it is not, return to main menu with error message + showFullMessage((char*) "The year must be >= 2016"); + delay(1000); + return true; + //Check if yearStorage is smaller than 2064 - unlikely the Thermocam is still in use then ! + } + else if (yearCheck > 49) { + //if it is not, return to main menu with error message + showFullMessage((char*) "The year must be < 2064"); + delay(1000); + return true; + //Add yearStorage to the array if passes the checks + } + else { + years[yearCheck] = true; + } + } + for (int i = 0; i < 50; i++) { + if (years[i]) + yearnum = yearnum + 1; + } + //Create an array for those years + int Years[yearnum]; + yearnum = 0; + //Add them in descending order + for (int i = 49; i >= 0; i--) { + if (years[i]) { + Years[yearnum] = 2016 + i; + yearnum = yearnum + 1; + } + } + //If there is only one yearStorage, choose it directly + if (yearnum == 1) { + itoa(Years[0], yearBuf, 10); + } + //Otherwise open the yearStorage chooser + else { + int res = loadMenu((char*) "Load file: Year", Years, yearnum); + //User want to return to the start menu + if (res == -1) { + return true; + //Save the chosen yearStorage + } + else { + itoa(Years[res], yearBuf, 10); + } + } + //Copy the chosen yearStorage to the filename + strncpy(&filename[0], yearBuf, 4); + return false; +} + +/* A method to choose the right monthStorage */ +bool monthChoose(bool* months, char* filename) { + for (int i = 0; i < 12; i++) { + if (months[i]) + monthnum = monthnum + 1; + } + //Add them to the array in descending order + int Months[monthnum]; + monthnum = 0; + for (int i = 11; i >= 0; i--) { + if (months[i]) { + //Add one as monthStorage start with January + Months[monthnum] = 1 + i; + monthnum = monthnum + 1; + } + } + //If there is only one, chose it directly + if (monthnum == 1) { + itoa(Months[0], monthBuf, 10); + } + //If not, open the image chooser + else { + int res = loadMenu((char*) "Load file: Month", Months, + monthnum); + //the user wants to go back to the years + if (res == -1) { + return true; + } + else { + itoa(Months[res], monthBuf, 10); + } + } + //Add a zero if monthStorage is smaller than 10 + if (atoi(monthBuf) < 10) { + filename[4] = '0'; + strncpy(&filename[5], monthBuf, 1); + } + //Else copy those two digits + else + strncpy(&filename[4], monthBuf, 2); + return false; +} + +/* A method to choose the right dayStorage */ +bool dayChoose(bool* days, char* filename) { + for (int i = 0; i < 31; i++) { + if (days[i]) + daynum = daynum + 1; + } + //Sort them descending + int Days[daynum]; + daynum = 0; + for (int i = 30; i >= 0; i--) { + if (days[i]) { + Days[daynum] = 1 + i; + daynum = daynum + 1; + } + } + //We only have one + if (daynum == 1) { + itoa(Days[0], dayBuf, 10); + + } + //Choose dayStorage + else { + int res = loadMenu((char*) "Load file: Day", Days, daynum); + if (res == -1) { + return true; + } + else { + itoa(Days[res], dayBuf, 10); + } + } + //Add dayStorage to the filename + if (atoi(dayBuf) < 10) { + filename[6] = '0'; + strncpy(&filename[7], dayBuf, 1); + } + else + strncpy(&filename[6], dayBuf, 2); + return false; +} + +/* A method to choose the right hourStorage */ +bool hourChoose(bool* hours, char* filename) { + for (int i = 0; i < 24; i++) { + if (hours[i]) + hournum = hournum + 1; + } + //Sort them descending + int Hours[hournum]; + hournum = 0; + for (int i = 23; i >= 0; i--) { + if (hours[i]) { + Hours[hournum] = i; + hournum = hournum + 1; + } + } + //If there is only one + if (hournum == 1) { + itoa(Hours[0], hourBuf, 10); + + } + //Otherwise choose + else { + int res = loadMenu((char*) "Load file: Hour", Hours, hournum); + if (res == -1) { + return true; + } + else { + itoa(Hours[res], hourBuf, 10); + } + } + //Add hourStorage to the filename + if (atoi(hourBuf) < 10) { + filename[8] = '0'; + strncpy(&filename[9], hourBuf, 1); + } + else + strncpy(&filename[8], hourBuf, 2); + return false; +} + +/* A method to choose the right minuteStorage */ +bool minuteChoose(bool* minutes, char* filename) { + for (int i = 0; i < 60; i++) { + if (minutes[i]) + minutenum = minutenum + 1; + } + //Sort them descending + int Minutes[minutenum]; + minutenum = 0; + for (int i = 59; i >= 0; i--) { + if (minutes[i]) { + Minutes[minutenum] = i; + minutenum = minutenum + 1; + } + } + //if there is only one + if (minutenum == 1) { + itoa(Minutes[0], minuteBuf, 10); + + } + //Otherwise choose + else { + int res = loadMenu((char*) "Load file: Minute", Minutes, + minutenum); + if (res == -1) { + return true; + } + else { + itoa(Minutes[res], minuteBuf, 10); + } + } + //Copy minutes to the filename + if (atoi(minuteBuf) < 10) { + filename[10] = '0'; + strncpy(&filename[11], minuteBuf, 1); + } + else + strncpy(&filename[10], minuteBuf, 2); + return false; +} + +/* A method to choose the right secondStorage */ +bool secondChoose(bool* seconds, char* filename) { + for (int i = 0; i < 60; i++) { + if (seconds[i]) + secondnum = secondnum + 1; + } + //Sort them descending + int Seconds[secondnum]; + secondnum = 0; + for (int i = 59; i >= 0; i--) { + if (seconds[i]) { + Seconds[secondnum] = i; + secondnum = secondnum + 1; + } + } + //if there is only one + if (secondnum == 1) { + itoa(Seconds[0], secondBuf, 10); + } + //Otherwise choose the right + else { + int res = loadMenu((char*) "Load file: Second", Seconds, + secondnum); + if (res == -1) { + return true; + } + else { + itoa(Seconds[res], secondBuf, 10); + } + } + //Add secondStorage to the filename + if (atoi(secondBuf) < 10) { + filename[12] = '0'; + strncpy(&filename[13], secondBuf, 1); + } + else + strncpy(&filename[12], secondBuf, 2); + return false; +} + +/* Extract the filename into the buffers */ +void copyIntoBuffers(char* filename) { + //Save filename into the buffer + sdFile.getName(filename, 19); + //Extract the time and date components into extra buffer + strncpy(yearBuf, &filename[0], 4); + strncpy(monthBuf, &filename[4], 2); + strncpy(dayBuf, &filename[6], 2); + strncpy(hourBuf, &filename[8], 2); + strncpy(minuteBuf, &filename[10], 2); + strncpy(secondBuf, &filename[12], 2); +} + +/* Check if the file is a valid one */ +bool checkFileValidity() { + //Load images + if (loadMode == loadMode_image) + { + uint32_t fileSize = sdFile.fileSize(); + return (sdFile.isFile() && ((fileSize == lepton2_small) || (fileSize == lepton2_big) || + (fileSize == lepton3_small) || (fileSize == lepton3_big) || (fileSize == bitmap))); + } + //Load videos + return sdFile.isDir(); +} + +/* Check if the name matches the criterion */ +void checkFileStructure(bool* check) { + //Check if yearStorage is 4 digit + for (int i = 0; i < 4; i++) { + if (!(isdigit(yearBuf[i]))) + *check = false; + } + //Check if the other elements are two digits each + for (int i = 0; i < 2; i++) { + if (!(isdigit(monthBuf[i]))) + *check = false; + if (!(isdigit(dayBuf[i]))) + *check = false; + if (!(isdigit(hourBuf[i]))) + *check = false; + if (!(isdigit(minuteBuf[i]))) + *check = false; + if (!(isdigit(secondBuf[i]))) + *check = false; + } +} + +/* Check if the filename ends with .DAT or .BMP if the file is a single image */ +void checkFileEnding(bool* check, char* filename) { + char ending_dat[] = ".DAT"; + //Check for DAT first + if (((filename[14] != ending_dat[0]) || (filename[15] != ending_dat[1]) + || (filename[16] != ending_dat[2]) + || (filename[17] != ending_dat[3]))) { + + //If it is not DAT, it could be BMP + char ending_bmp[] = ".BMP"; + if ((filename[14] != ending_bmp[0]) || (filename[15] != ending_bmp[1]) + || (filename[16] != ending_bmp[2]) + || (filename[17] != ending_bmp[3])) + //None of both + *check = false; + + //If bitmap, check if the file has a corresponding DAT + else { + strcpy(&filename[14], ".DAT"); + sdFile.close(); + //Check if it is a file + sdFile.open(filename, O_READ); + if (sdFile.isFile()) + *check = false; + //Open the old file + strcpy(&filename[14], ".BMP"); + sdFile.close(); + sdFile.open(filename, O_READ); + } + } +} + +/* Find the next or previous file/folder on the SD card or the position */ +bool findFile(char* filename, bool next, bool restart, int* position, char* compare) { + bool found = false; + int counter = 0; + + //Start SD Transmission + startAltClockline(restart); + + //Get filenames from SD Card - one after another and a maximum of maxFiles + while (sdFile.openNext(sd.vwd(), O_READ)) { + + //Either folder for video or file with specific size for single image + if (checkFileValidity()) { + //Extract the filename into the buffers + copyIntoBuffers(filename); + //Check if the name matches the criterion + bool check = true; + //Check if the other elements are two digits each + checkFileStructure(&check); + //Check if the filename ends with .DAT or .BMP if the file is a single image + if (loadMode == loadMode_image) + checkFileEnding(&check, filename); + //If all checks were successfull, add image to the results + if (check) { + //If we want to get the next image + if (next) { + found = true; + break; + } + //If we want to get the previous image or get the position + if (compare != NULL) { + if (strcmp(compare, filename) == 0) { + *position = counter; + found = true; + break; + } + counter++; + } + //In case we found it + else if (*position == counter) { + found = true; + break; + } + //If not, raise the counter + else + counter++; + } + } + //Close the file + sdFile.close(); + } + + //End SD Transmission + endAltClockline(); + //Return result + return found; +} + +/* Search for files and videos */ +void searchFiles() { + char filename[20]; + //Start SD Transmission + startAltClockline(true); + + //Get filenames from SD Card - one after another and a maximum of maxFiles + while ((imgCount < maxFiles) && (sdFile.openNext(sd.vwd(), O_READ))) { + //Either folder for video or file with specific size for single image + if (checkFileValidity()) { + //Extract the filename into the buffers + copyIntoBuffers(filename); + //Check if the name matches the criterion + bool check = true; + //Check if the other elements are two digits each + checkFileStructure(&check); + //Check if the filename ends with .DAT or .BMP if the file is a single image + if (loadMode == loadMode_image) + checkFileEnding(&check, filename); + //If all checks were successfull, add image to the results + if (check) { + //If the size of images is not too high + if (imgCount < maxFiles) { + yearStorage[imgCount] = atoi(yearBuf); + monthStorage[imgCount] = atoi(monthBuf); + dayStorage[imgCount] = atoi(dayBuf); + hourStorage[imgCount] = atoi(hourBuf); + minuteStorage[imgCount] = atoi(minuteBuf); + secondStorage[imgCount] = atoi(secondBuf); + //And raise imgCount by one + imgCount++; + } + //If there are maxFiles images or more + else { + //Close the file + sdFile.close(); + //End SD Transmission + endAltClockline(); + //Display an error message + showFullMessage((char*) "Maximum number of files exceeded"); + delay(1000); + //And return to the main menu + mainMenu(); + return; + } + } + } + //Close the file + sdFile.close(); + } + + //End SD Transmission + endAltClockline(); +} + +/* Choose file */ +void chooseFile(char* filename) { + //Look for Years +YearLabel: + //If the user wants to return to the main menu + if (yearnum == 1 + || yearChoose(filename)) { + return; + } + //Look for monthStorage +MonthLabel: + //We have twelve months + bool months[12] = { 0 }; + for (int i = 0; i < imgCount; i++) { + //Add monthStorage if it belongs to the chosen yearStorage + if (yearStorage[i] == atoi(yearBuf)) + //Substract one to start array by zero + months[monthStorage[i] - 1] = true; + } + //If the user wants to go back to the years + if (monthChoose(months, filename)) + goto YearLabel; + //Look for dayStorage +DayLabel: + //We have 31 days + bool days[31] = { 0 }; + for (int i = 0; i < imgCount; i++) { + //The dayStorage has to match the yearStorage and the monthStorage chosen + if ((yearStorage[i] == atoi(yearBuf)) && (monthStorage[i] == atoi(monthBuf))) + //Substract one to start array by zero + days[dayStorage[i] - 1] = true; + } + //If the user wants to go back to the months + if (dayChoose(days, filename)) { + if (monthnum > 1) + goto MonthLabel; + else + goto YearLabel; + } + //Look for hourStorage +HourLabel: + //We have 24 hours + bool hours[24] = { 0 }; + for (int i = 0; i < imgCount; i++) { + //Look for match at years, monthStorage and dayStorage + if ((yearStorage[i] == atoi(yearBuf)) && (monthStorage[i] == atoi(monthBuf)) + && (dayStorage[i] == atoi(dayBuf))) + hours[hourStorage[i]] = true; + } + //If the user wants to go back to the days + if (hourChoose(hours, filename)) { + if (daynum > 1) + goto DayLabel; + else if (monthnum > 1) + goto MonthLabel; + else + goto YearLabel; + } + //Look for minuteStorage +MinuteLabel: + //We have 60 minutes + bool minutes[60] = { 0 }; + for (int i = 0; i < imgCount; i++) { + //Watch for yearStorage, monthStorage, dayStorage and hourStorage + if ((yearStorage[i] == atoi(yearBuf)) && (monthStorage[i] == atoi(monthBuf)) + && (dayStorage[i] == atoi(dayBuf)) && (hourStorage[i] == atoi(hourBuf))) + minutes[minuteStorage[i]] = true; + } + //If the user wants to go back to the hours + if (minuteChoose(minutes, filename)) { + if (hournum > 1) + goto HourLabel; + else if (daynum > 1) + goto DayLabel; + else if (monthnum > 1) + goto MonthLabel; + else + goto YearLabel; + } + //Look for secondStorage + //We have 60 seconds + bool seconds[60] = { 0 }; + for (int i = 0; i < imgCount; i++) { + //Watch for yearStorage, monthStorage, dayStorage, hourStorage and minuteStorage + if ((yearStorage[i] == atoi(yearBuf)) && (monthStorage[i] == atoi(monthBuf)) + && (dayStorage[i] == atoi(dayBuf)) && (hourStorage[i] == atoi(hourBuf)) + && (minuteStorage[i] == atoi(minuteBuf))) + seconds[secondStorage[i]] = true; + } + //If the user wants to go back to the minutes + if (secondChoose(seconds, filename)) { + if (minutenum > 1) + goto MinuteLabel; + else if (hournum > 1) + goto HourLabel; + else if (daynum > 1) + goto DayLabel; + else if (monthnum > 1) + goto MonthLabel; + else + goto YearLabel; + } + + //For an image, add the ending + if (loadMode == loadMode_image) + { + //Start SD Transmission + startAltClockline(); + //Try to add the DAT file + strcpy(&filename[14], ".DAT"); + //Check if it is a DAT file + sdFile.open(filename, O_READ); + if (sdFile.isFile()) { + sdFile.close(); + return; + } + //If not, use bitmap + strcpy(&filename[14], ".BMP"); + //Close the file + sdFile.close(); + //End SD Transmission + endAltClockline(); + } + + //Video ending + else + //Delete the ending for a video + filename[14] = '\0'; +} + +/* Delete image / video function */ +bool loadDelete(char* filename, int* pos) { + //Image + if (isImage(filename)) + deleteImage(filename); + //Video + else + deleteVideo(filename); + //Clear all previous data + clearData(); + //Search files + searchFiles(); + //If there are no files left, return + if (imgCount == 0) { + if (loadMode == loadMode_image) + showFullMessage((char*) "No images found"); + else + showFullMessage((char*) "No videos found"); + delay(1000); + return false; + } + //Decrease by one if the last image/video was deleted + if (*pos > (imgCount - 1)) + *pos = imgCount - 1; + //Find the name of the next file + findFile(filename, false, true, pos); + return true; +} + +/* Find image / video function */ +void loadFind(char* filename, int* pos) { + //If there is only one image + if (imgCount == 1) { + if (loadMode == loadMode_image) + showFullMessage((char*) "Only one image available"); + else + showFullMessage((char*) "Only one video available"); + delay(1000); + return; + } + //Clear all previous data + clearData(); + //Search files + searchFiles(); + //Fill screen + display_fillScr(200, 200, 200); + //Let the user choose a new file + chooseFile(filename); + //Copy the filename for a compare value + char compare[20]; + strncpy(compare, filename, 20); + //Find the new file position + findFile(filename, false, true, pos, compare); +} + +/* Alloc space for the different arrays*/ +void loadAlloc() { + yearStorage = (uint16_t*)calloc(maxFiles, sizeof(uint16_t)); + monthStorage = (byte*)calloc(maxFiles, sizeof(byte)); + dayStorage = (byte*)calloc(maxFiles, sizeof(byte)); + hourStorage = (byte*)calloc(maxFiles, sizeof(byte)); + minuteStorage = (byte*)calloc(maxFiles, sizeof(byte)); + secondStorage = (byte*)calloc(maxFiles, sizeof(byte)); +} + +/* Change settings for load menu */ +void loadSettings() { + //Do not show additional information that are not required + batteryEnabled = false; + dateEnabled = false; + timeEnabled = false; + storageEnabled = false; + minMaxPoints = minMaxPoints_disabled; + hotColdMode = hotColdMode_disabled; + //For Teensy 3.6, use HQ resolution for loading + if (teensyVersion == teensyVersion_new) + hqRes = true; +} + +/* De-Alloc space for the different arrays*/ +void loadDeAlloc() { + free(yearStorage); + free(monthStorage); + free(dayStorage); + free(hourStorage); + free(minuteStorage); + free(secondStorage); +} + +/* Interrupt handler for the load touch menu */ +void loadTouchIRQ() { + //Get touch coordinates + TS_Point p = touch_getPoint(); + uint16_t x = p.x; + uint16_t y = p.y; + + //Find + if ((x >= 10) && (x <= 140) && (y >= 10) && (y <= 80)) + loadTouch = loadTouch_find; + + //Exit + else if ((x >= 180) && (x <= 310) && (y >= 160) && (y <= 230)) + loadTouch = loadTouch_exit; + + //Previous + else if ((x >= 10) && (x <= 140) && (y >= 90) && (y <= 150) && (imgCount != 1)) + loadTouch = loadTouch_previous; + + //Next + else if ((x >= 180) && (x <= 310) && (y >= 90) && (y <= 150) && (imgCount != 1)) + loadTouch = loadTouch_next; + + //Delete + else if ((x >= 180) && (x <= 310) && (y >= 10) && (y <= 80)) + loadTouch = loadTouch_delete; + + //Convert + else if ((x >= 10) && (x <= 140) && (y >= 160) && (y <= 230)) + loadTouch = loadTouch_convert; + + //Middle + else if ((x > 140) && (x < 180) && (y > 80) && (y < 160)) + loadTouch = loadTouch_middle; +} + +/* Main entry point for loading images/videos*/ +void loadFiles() { + //Old hardware + //ThermocamV4 or DIY-Thermocam V2, check SD card + if ((mlx90614Version == mlx90614Version_old) || + (teensyVersion == teensyVersion_new)) { + showFullMessage((char*) "Checking SD card..", true); + if (!checkSDCard()) { + showFullMessage((char*) "Insert SD card", true); + delay(1000); + mainMenu(); + return; + } + } + +redraw: + //Title & Background + mainMenuBackground(); + mainMenuTitle((char*)"Load Menu"); + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(15, 47, 140, 120, (char*) "Images"); + buttons_addButton(165, 47, 140, 120, (char*) "Videos"); + buttons_addButton(15, 188, 140, 40, (char*) "Back"); + buttons_drawButtons(); + //Touch handler + while (true) { + //If touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(true); + //IMAGES + if (pressedButton == 0) { + loadMode = loadMode_image; + break; + } + //VIDEOS + if (pressedButton == 1) { + loadMode = loadMode_video; + break; + } + //BACK + if (pressedButton == 2) + return; + } + } + + //Store the filename + char filename[20]; + + //Save old settings + uint16_t old_minValue = minValue; + uint16_t old_maxValue = maxValue; + byte old_leptonVersion = leptonVersion; + byte old_calStatus = calStatus; + float old_calOffset = calOffset; + float old_calSlope = calSlope; + + //Load message + showFullMessage((char*) "Please wait..", true); + //Alloc space + loadAlloc(); + //Clear all previous data + clearData(); + //Search files + searchFiles(); + + //If there are no images or videos, return + if (imgCount == 0) { + //Show message + if (loadMode == loadMode_image) + showFullMessage((char*) "No images found", true); + else + showFullMessage((char*) "No videos found", true); + delay(1000); + //Deallocate space + loadDeAlloc(); + //Redraw menu + goto redraw; + } + + //Change settings + loadSettings(); + + //Open the latest file + int pos = imgCount - 1; + findFile(filename, false, true, &pos); + bool exit = false; + + //New touch interrupt + detachInterrupt(pin_touch_irq); + + //Main loop + while (true) { + + //Disable decision marker + loadTouch = loadTouch_none; + + //Load image + if (isImage(filename) && (loadMode == loadMode_image)) + openImage(filename, imgCount); + + //Play video + else + playVideo(filename, imgCount); + + //Touch actions + switch (loadTouch) { + + //Find + case loadTouch_find: + loadFind(filename, &pos); + break; + + //Delete + case loadTouch_delete: + if (!loadDelete(filename, &pos)) + exit = true; + break; + + //Previous + case loadTouch_previous: + showFullMessage((char*) "Loading.."); + if (!findFile(filename, true, false)) { + findFile(filename, true, true); + pos = 0; + } + else + pos++; + break; + + //Next + case loadTouch_next: + showFullMessage((char*) "Loading.."); + if (pos == 0) + pos = imgCount - 1; + else + pos--; + findFile(filename, false, true, &pos); + break; + + //Exit + case loadTouch_exit: + exit = true; + break; + + //Convert + case loadTouch_convert: + //Image + if (isImage(filename) && (loadMode == loadMode_image)) + convertImage(filename); + //Video + else + convertVideo(filename); + break; + } + + //Wait for touch release + if (touch_capacitive) + while (touch_touched()); + else + while (!digitalRead(pin_touch_irq)); + + //Leave + if (exit) + break; + } + //Show message + drawMainMenuBorder(); + showFullMessage((char*) "Please wait..", true); + + //Deallocate space + loadDeAlloc(); + + //Restore old settings from variables + minValue = old_minValue; + maxValue = old_maxValue; + leptonVersion = old_leptonVersion; + calStatus = old_calStatus; + calOffset = old_calOffset; + calSlope = old_calSlope; + + //Restore the rest from EEPROM + readEEPROM(); + + //Refresh SD space + refreshFreeSpace(); + + //Restore old touch handler + attachInterrupt(pin_touch_irq, touchIRQ, FALLING); +} diff --git a/Firmware_V2/src/thermal/load.h b/Firmware_V2/src/thermal/load.h new file mode 100644 index 0000000..4a11090 --- /dev/null +++ b/Firmware_V2/src/thermal/load.h @@ -0,0 +1,48 @@ +/* +* +* LOAD - Load images and videos from the internal storage +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef LOAD_H +#define LOAD_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void checkFileEnding(bool* check, char* filename); +void checkFileStructure(bool* check); +bool checkFileValidity(); +void chooseFile(char* filename); +void clearData(); +void copyIntoBuffers(char* filename); +bool dayChoose(bool* days, char* filename); +void displayRawData(); +bool findFile(char* filename, bool next, bool restart, int* position = 0, char* compare = NULL); +bool hourChoose(bool* hours, char* filename); +bool isImage(char* filename); +void loadAlloc(); +void loadBMPImage(char* filename); +void loadDeAlloc(); +bool loadDelete(char* filename, int* pos); +void loadFiles(); +void loadFind(char* filename, int* pos); +void loadRawData(char* filename, char* dirname = NULL); +void loadSettings(); +void loadTouchIRQ(); +bool minuteChoose(bool* minutes, char* filename); +bool monthChoose(bool* months, char* filename); +void readTempPoints(); +void searchFiles(); +bool secondChoose(bool* seconds, char* filename); +bool yearChoose(char* filename); + +#endif /* LOAD_H */ diff --git a/Firmware_V2/src/thermal/save.cpp b/Firmware_V2/src/thermal/save.cpp new file mode 100644 index 0000000..7e0b38c --- /dev/null +++ b/Firmware_V2/src/thermal/save.cpp @@ -0,0 +1,520 @@ +/* +* +* SAVE - Save images and videos to the internal storage +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################### STATIC DATA DECLARATIONS ##########################*/ + +//160 x 120 bitmap header +static const char bmp_header_small[66] = { 0x42, 0x4D, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, +0x00, 0xA0, 0x00, 0x00, 0x00, 0x88, 0xFF, 0xFF, 0xFF, 0x01, +0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, +0x00, 0xE0, 0x07, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00 }; + +//320 x 240 bitmap header +static const char bmp_header_middle[66] = { 0x42, 0x4D, 0x36, 0x58, 0x02, 0x00, 0x00, +0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, +0x00, 0x40, 0x01, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x01, +0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x58, 0x02, +0x00, 0xC4, 0x0E, 0x00, 0x00, 0xC4, 0x0E, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, +0x00, 0xE0, 0x07, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00 }; + +//640 x 480 bitmap header +static const char bmp_header_large[66] = { 0x42, 0x4D, 0x36, 0x60, 0x09, 0x00, 0x00, +0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, +0x80, 0x02, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x01, 0x00, 0x10, +0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x60, 0x09, 0x00, 0xC4, 0x0E, +0x00, 0x00, 0xC4, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, +0x1F, 0x00, 0x00, 0x00 }; + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Creates a filename from the current time & date */ +void createSDName(char* filename, boolean folder) { + char buffer[5]; + //Year + itoa(year(), buffer, 10); + strncpy(&filename[0], buffer, 4); + //Month + itoa(month(), buffer, 10); + if (month() < 10) { + filename[4] = '0'; + strncpy(&filename[5], buffer, 1); + } + else { + strncpy(&filename[4], buffer, 2); + } + //Day + itoa(day(), buffer, 10); + if (day() < 10) { + filename[6] = '0'; + strncpy(&filename[7], buffer, 1); + } + else { + strncpy(&filename[6], buffer, 2); + } + //Hour + itoa(hour(), buffer, 10); + if (hour() < 10) { + filename[8] = '0'; + strncpy(&filename[9], buffer, 1); + } + else { + strncpy(&filename[8], buffer, 2); + } + //Minute + itoa(minute(), buffer, 10); + if (minute() < 10) { + filename[10] = '0'; + strncpy(&filename[11], buffer, 1); + } + else { + strncpy(&filename[10], buffer, 2); + } + //Second + itoa(second(), buffer, 10); + if (second() < 10) { + filename[12] = '0'; + if (!folder) + strncpy(&filename[13], buffer, 1); + else + strcpy(&filename[13], buffer); + } + else { + if (!folder) + strncpy(&filename[12], buffer, 2); + else + strcpy(&filename[12], buffer); + } +} + +/* Creates a bmp file for the thermal image */ +void createBMPFile(char* filename) { + //File extension and open + strcpy(&filename[14], ".BMP"); + sdFile.open(filename, O_RDWR | O_CREAT | O_AT_END); + //Write the BMP header + sdFile.write((uint8_t*)bmp_header_large, 66); +} + +/* Creates a JPEG file for the visual image */ +void createJPEGFile(char* dirname) { + //Go into the video folder and add extension + if (dirname != NULL) + { + sd.chdir(dirname); + strcpy(&saveFilename[5], ".JPG"); + } + else + strcpy(&saveFilename[14], ".JPG"); + //Create the file + sdFile.open(saveFilename, O_RDWR | O_CREAT | O_AT_END); +} + +/* Creates a folder for the video capture */ +void createVideoFolder(char* dirname) { + //Start alternative clock line + startAltClockline(); + + //Build the dir from the current time & date + createSDName(dirname, true); + + //Create folder + sd.mkdir(dirname); + + //Go into that folder + sd.chdir(dirname); + + //End alternative clock line + endAltClockline(); +} + +/* Start the image save procedure */ +void imgSaveStart() { + //ThermocamV4 or DIY-Thermocam V2 - check SD card + if (!checkSDCard()) { + //Show wait message + showFullMessage((char*) "Waiting for SD card.."); + + //Wait until card is inserted + while (!checkSDCard()); + + //Redraw the last screen content + displayBuffer(); + } + + //Check if there is at least 1MB of space left + if (getSDSpace() < 1000) { + //Show message + showFullMessage((char*) "The SD card is full"); + delay(1000); + + //Disable and return + imgSave = imgSave_disabled; + return; + } + + //Build save filename from the current time & date + createSDName(saveFilename); + + //Set text color + changeTextColor(); + //Set background transparent + display_setBackColor(VGA_TRANSPARENT); + //Display to screen in big font + display_setFont(bigFont); + + //Capture visual image if enabled and saving + if (visualEnabled && (displayMode == displayMode_thermal) && (checkDiagnostic(diag_camera))) + { + //Show hold steady message + if (spotEnabled) + display_print((char*) "HOLD STEADY", CENTER, 70); + else + display_print((char*) "HOLD STEADY", CENTER, 90); + + //Wait a second + delay(1000); + + //Capture visual frame + camera_capture(); + + //Show save message + if (spotEnabled) + display_print((char*) "SAVING", CENTER, 170); + else + display_print((char*) "SAVING", CENTER, 130); + + //Save visual image in full-res + camera_get(camera_save); + } + + //Show save message + else { + if (spotEnabled) + display_print((char*) "SAVING", CENTER, 70); + else + display_print((char*) "SAVING", CENTER, 110); + } + + //Set marker to create image + imgSave = imgSave_create; + //Switch back to small font + display_setFont(smallFont); +} + +/* Creates the filename for the video frames */ +void frameFilename(char* filename, uint16_t count) { + filename[0] = '0' + count / 10000 % 10; + filename[1] = '0' + count / 1000 % 10; + filename[2] = '0' + count / 100 % 10; + filename[3] = '0' + count / 10 % 10; + filename[4] = '0' + count % 10; +} + +/* Save video frame to image file */ +void saveVideoFrame(char* filename, char* dirname) { + //Begin SD Transmission + startAltClockline(); + + //Switch to video folder + sd.chdir(dirname); + // Open the file for writing + sdFile.open(filename, O_RDWR | O_CREAT | O_AT_END); + + //Write 160x120 BMP header + sdFile.write((uint8_t*)bmp_header_small, 66); + + //Write 160x120 data + for (int i = 0; i < 19200; i++) { + sdFile.write(smallBuffer[i] & 0x00FF); + sdFile.write((smallBuffer[i] & 0xFF00) >> 8); + } + + //Close the file + sdFile.close(); + //Switch Clock back to Standard + endAltClockline(); +} + +/* Proccess video frames */ +void processVideoFrames(int framesCaptured, char* dirname) { + char buffer[30]; + char filename[] = "00000.DAT"; + int framesConverted = 0; + + //Display title + display_fillScr(200, 200, 200); + display_setBackColor(200, 200, 200); + display_setFont(bigFont); + display_setColor(VGA_BLUE); + display_print((char*)"Video conversion", CENTER, 30); + + //Display info + display_setFont(smallFont); + display_setColor(VGA_BLACK); + display_print((char*)"Converts all .DAT to .BMP frames", CENTER, 80); + display_print((char*)"Press button to abort the process", CENTER, 120); + + //Display content + sprintf(buffer, "Frames converted: %5d / %5d", framesConverted, framesCaptured); + display_print(buffer, CENTER, 160); + sprintf(buffer, "Folder name: %s", dirname); + display_print(buffer, CENTER, 200); + + //Switch to processing mode + videoSave = videoSave_processing; + + //Go through all the frames in the folder + for (framesConverted = 0; framesConverted < framesCaptured; framesConverted++) { + //Button pressed, exit + if (videoSave != videoSave_processing) + break; + + //Get filename + frameFilename(filename, framesConverted); + strcpy(&filename[5], ".DAT"); + + //Load Raw data + loadRawData(filename, dirname); + + //Apply low-pass filter + if (filterType == filterType_box) + boxFilter(); + else if (filterType == filterType_gaussian) + gaussianFilter(); + + //Find min / max position + if (minMaxPoints != minMaxPoints_disabled) + refreshMinMax(); + + //Convert lepton data to RGB565 colors + convertColors(true); + + //Display additional information + bool hqRes_old = hqRes; + hqRes = false; + displayInfos(); + hqRes = hqRes_old; + + //Save frame to image file + strcpy(&filename[5], ".BMP"); + saveVideoFrame(filename, dirname); + + //Font color + display_setBackColor(200, 200, 200); + display_setFont(smallFont); + display_setColor(VGA_BLACK); + + //Update screen content + sprintf(buffer, "Frames converted: %5d / %5d", framesConverted + 1, framesCaptured); + display_print(buffer, CENTER, 160); + } + + //All images converted! + showFullMessage((char*) "Video converted"); + delay(1000); +} + +/* Saves raw data for an image or an video frame */ +void saveRawData(bool isImage, char* name, uint16_t framesCaptured) { + uint16_t result; + + //Start SD + startAltClockline(); + + //Create filename for image + if (isImage) { + strcpy(&name[14], ".DAT"); + sdFile.open(name, O_RDWR | O_CREAT | O_AT_END); + } + + //Create filename for video frame + else { + char filename[] = "00000.DAT"; + frameFilename(filename, framesCaptured); + sd.chdir(name); + sdFile.open(filename, O_RDWR | O_CREAT | O_AT_END); + } + + //For the Lepton2 sensor, write 4800 raw values + if ((leptonVersion != leptonVersion_3_0_shutter) && (leptonVersion != leptonVersion_3_5_shutter)) { + for (int line = 0; line < 60; line++) { + for (int column = 0; column < 80; column++) { + result = smallBuffer[(line * 2 * 160) + (column * 2)]; + sdFile.write((result & 0xFF00) >> 8); + sdFile.write(result & 0x00FF); + } + } + } + + //For the Lepton3 sensor, write 19200 raw values + else { + for (int i = 0; i < 19200; i++) { + sdFile.write((smallBuffer[i] & 0xFF00) >> 8); + sdFile.write(smallBuffer[i] & 0x00FF); + } + } + + //Write min and max + sdFile.write((minValue & 0xFF00) >> 8); + sdFile.write(minValue & 0x00FF); + sdFile.write((maxValue & 0xFF00) >> 8); + sdFile.write(maxValue & 0x00FF); + + //Write the object temp + uint8_t farray[4]; + floatToBytes(farray, spotTemp); + for (int i = 0; i < 4; i++) + sdFile.write(farray[i]); + + //Write the color scheme + sdFile.write(colorScheme); + //Write the temperature format + sdFile.write(tempFormat); + //Write the show spot attribute + sdFile.write(spotEnabled); + //Write the show colorbar attribute + if (calStatus == cal_warmup) + sdFile.write((byte)0); + else + sdFile.write(colorbarEnabled); + //Write the show hottest / coldest attribute + sdFile.write(minMaxPoints); + + //Write calibration offset + floatToBytes(farray, (float)calOffset); + for (int i = 0; i < 4; i++) + sdFile.write(farray[i]); + //Write calibration slope + floatToBytes(farray, (float)calSlope); + for (int i = 0; i < 4; i++) + sdFile.write(farray[i]); + + //Write temperature points + for (byte i = 0; i < 96; i++) { + //Write index + sdFile.write((tempPoints[i][0] & 0xFF00) >> 8); + sdFile.write(tempPoints[i][0] & 0x00FF); + //Write value + sdFile.write((tempPoints[i][1] & 0xFF00) >> 8); + sdFile.write(tempPoints[i][1] & 0x00FF); + } + + //Close the file + sdFile.close(); + //Switch Clock back to Standard + endAltClockline(); +} + +/* End the image save procedure */ +void imgSaveEnd() { + //Save Bitmap image if activated or in visual / combined mode + if (convertEnabled || (displayMode == displayMode_visual) || (displayMode == displayMode_combined)) + saveBuffer(saveFilename); + + //Refresh free space + refreshFreeSpace(); + + //Disable image save marker + imgSave = imgSave_disabled; +} + +/* Saves the content of the screen buffer to the sd card */ +void saveBuffer(char* filename) { + unsigned short pixel; + + //Otherwise switch to clockline + startAltClockline(); + + //Create file + createBMPFile(filename); + + //Teensy 3.1/3.2 - Save the 160x120 array as 640x480 + if (teensyVersion == teensyVersion_old) + { + //Allocate space for sd buffer + uint8_t* sdBuffer = (uint8_t*)calloc(320, sizeof(uint8_t)); + //Save 640x480 pixels + for (int16_t y = 119; y >= 0; y--) { + //Write them into the sd buffer + for (uint8_t x = 0; x < 160; x++) { + pixel = smallBuffer[(y * 160) + x]; + sdBuffer[x * 2] = pixel & 0x00FF; + sdBuffer[(x * 2) + 1] = (pixel & 0xFF00) >> 8; + } + //Write them to the sd card with 640x480 resolution + for (uint8_t i = 0; i < 4; i++) { + for (uint8_t x = 0; x < 160; x++) { + for (uint8_t j = 0; j < 4; j++) { + sdFile.write(sdBuffer[x * 2]); + sdFile.write(sdBuffer[(x * 2) + 1]); + } + } + } + } + //De-allocate space + free(sdBuffer); + } + + //Teensy 3.6 - Save the 320x240 array as 640x480 + else + { + //Allocate space for sd buffer + uint8_t* sdBuffer = (uint8_t*)calloc(1280, sizeof(uint8_t)); + //Save 640x480 pixels + for (int16_t y = 239; y >= 0; y--) { + //Write them into the sd buffer + for (uint16_t x = 0; x < 320; x++) { + pixel = bigBuffer[(y * 320) + x]; + sdBuffer[x * 4] = pixel & 0x00FF; + sdBuffer[(x * 4) + 1] = (pixel & 0xFF00) >> 8; + sdBuffer[(x * 4) + 2] = pixel & 0x00FF; + sdBuffer[(x * 4) + 3] = (pixel & 0xFF00) >> 8; + } + //Write them to the sd card with 640x480 resolution + sdFile.write(sdBuffer, 1280); + sdFile.write(sdBuffer, 1280); + } + //De-allocate space + free(sdBuffer); + } + + //Close file + sdFile.close(); + + //End SD Transmission + endAltClockline(); +} diff --git a/Firmware_V2/src/thermal/save.h b/Firmware_V2/src/thermal/save.h new file mode 100644 index 0000000..4c3c5b3 --- /dev/null +++ b/Firmware_V2/src/thermal/save.h @@ -0,0 +1,33 @@ +/* +* +* SAVE - Save images and videos to the internal storage +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef SAVE_H +#define SAVE_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void createBMPFile(char* filename); +void createJPEGFile(char* dirname); +void createSDName(char* filename, boolean folder = false); +void createVideoFolder(char* dirname); +void frameFilename(char* filename, uint16_t count); +void imgSaveEnd(); +void imgSaveStart(); +void processVideoFrames(int framesCaptured, char* dirname); +void saveBuffer(char* filename); +void saveRawData(bool isImage, char* name, uint16_t framesCaptured = 0); +void saveVideoFrame(char* filename, char* dirname); + +#endif /* SAVE_H */ diff --git a/Firmware_V2/src/thermal/thermal.cpp b/Firmware_V2/src/thermal/thermal.cpp new file mode 100644 index 0000000..c8be05d --- /dev/null +++ b/Firmware_V2/src/thermal/thermal.cpp @@ -0,0 +1,595 @@ +/* +* +* THERMAL - Main functions in the live mode +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Touch interrupt handler */ +void touchIRQ() { + //When not in menu, video save, image save, serial mode or lock/release limits + if ((!showMenu) && (!videoSave) && (!longTouch) && (!imgSave) && (!serialMode)) { + //Count the time to choose selection + long startTime = millis(); + delay(10); + long endTime = millis() - startTime; + + //Wait for touch release, but not longer than a second + if (touch_capacitive) { + while ((touch_touched()) && (endTime <= 1000)) + endTime = millis() - startTime; + } + else { + while ((!digitalRead(pin_touch_irq)) && (endTime <= 1000)) + endTime = millis() - startTime; + } + endTime = millis() - startTime; + + //Short press - show menu + if (endTime < 1000) + { + if (showMenu == showMenu_disabled) + showMenu = showMenu_desired; + } + //Long press not in visual - lock or release limits + else if (displayMode != displayMode_visual) + longTouch = true; + } +} + +/* Button interrupt handler */ +void buttonIRQ() { + //When not in menu, video save, image save, serial mode or lock/release limits + if ((!showMenu) && (!videoSave) && (!longTouch) && (!imgSave) && (!serialMode)) { + //Count the time to choose selection + long startTime = millis(); + delay(10); + long endTime = millis() - startTime; + + //As long as the button is pressed + while (extButtonPressed() && (endTime <= 1000)) + endTime = millis() - startTime; + + //Short press - save image to SD Card + if (endTime < 1000) + //Prepare image save but let screen refresh first + imgSave = imgSave_set; + + //Enable video mode + else + videoSave = videoSave_menu; + } + + //When in video save recording mode, go to processing + if (videoSave == videoSave_recording) { + videoSave = videoSave_processing; + while (extButtonPressed()); + } + + //When in video save processing, end it + else if (videoSave == videoSave_processing) { + videoSave = videoSave_menu; + while (extButtonPressed()); + } +} + +/* Handler for a long touch press */ +void longTouchHandler() { + //When in auto mode, toggle between locked & unlocked + if (autoMode) { + //Unlock limits and enable auto FFC + if (limitsLocked) { + showTransMessage((char*) "Limits unlocked"); + limitsLocked = false; + lepton_ffcMode(true); + } + + //Lock limits and disable auto FFC + else { + lepton_ffcMode(false); + showTransMessage((char*) "Limits locked"); + limitsLocked = true; + } + } + + //When in manual mode, toggle between presets + else { + //Read preset from EEPROM + byte minMaxPreset = EEPROM.read(eeprom_minMaxPreset); + + //When in temporary limits + if (minMaxPreset == minMax_temporary) { + if (EEPROM.read(eeprom_minMax1Set) == eeprom_setValue) + { + showTransMessage((char*) "Switch to Preset 1"); + minMaxPreset = minMax_preset1; + } + else if (EEPROM.read(eeprom_minMax2Set) == eeprom_setValue) + { + showTransMessage((char*) "Switch to Preset 2"); + minMaxPreset = minMax_preset2; + } + else if (EEPROM.read(eeprom_minMax3Set) == eeprom_setValue) + { + showTransMessage((char*) "Switch to Preset 3"); + minMaxPreset = minMax_preset3; + } + else + showTransMessage((char*) "No other Preset"); + } + + //When in preset 1 + else if (minMaxPreset == minMax_preset1) { + if (EEPROM.read(eeprom_minMax2Set) == eeprom_setValue) + { + showTransMessage((char*) "Switch to Preset 2"); + minMaxPreset = minMax_preset2; + } + else if (EEPROM.read(eeprom_minMax3Set) == eeprom_setValue) + { + showTransMessage((char*) "Switch to Preset 3"); + minMaxPreset = minMax_preset3; + } + else + showTransMessage((char*) "No other Preset"); + } + + //When in preset 2 + else if (minMaxPreset == minMax_preset2) { + if (EEPROM.read(eeprom_minMax3Set) == eeprom_setValue) + { + showTransMessage((char*) "Switch to Preset 3"); + minMaxPreset = minMax_preset3; + } + else if (EEPROM.read(eeprom_minMax1Set) == eeprom_setValue) + { + showTransMessage((char*) "Switch to Preset 1"); + minMaxPreset = minMax_preset1; + } + else + showTransMessage((char*) "No other Preset"); + } + + //When in preset 3 + else if (minMaxPreset == minMax_preset3) { + if (EEPROM.read(eeprom_minMax1Set) == eeprom_setValue) + { + showTransMessage((char*) "Switch to Preset 1"); + minMaxPreset = minMax_preset1; + } + else if (EEPROM.read(eeprom_minMax2Set) == eeprom_setValue) + { + showTransMessage((char*) "Switch to Preset 2"); + minMaxPreset = minMax_preset2; + } + else + showTransMessage((char*) "No other Preset"); + } + + //When not using temporary preset + if (minMaxPreset != minMax_temporary) + { + //Save new preset to EEPROM + EEPROM.write(eeprom_minMaxPreset, minMaxPreset); + + //Load the new limits + readTempLimits(); + } + } + + //Disable lock limits menu + longTouch = false; +} + +/* Show the color bar on screen */ +void showColorBar() { + //Help variables + char buffer[6]; + byte red, green, blue, fac; + byte count = 0; + + //Calculate color bar height corresponding on color elements + byte height; + //For 320x240 + if (teensyVersion == teensyVersion_new) + height = 240 - ((240 - (colorElements / 2)) / 2); + //For 160x120 + else + height = 120 - ((120 - (colorElements / 4)) / 2); + + //Calculate color level for hot and cold + float colorLevel = 0; + if ((hotColdMode != hotColdMode_disabled) && (displayMode != displayMode_combined)) + colorLevel = (tempToRaw(hotColdLevel) * 1.0 - minValue) / (maxValue * 1.0 - minValue); + + //Display color bar + for (int i = 0; i < (colorElements - 1); i++) { + //For 320x240, use every second + if (teensyVersion == teensyVersion_new) + fac = 2; + //For 160x120, use every forth + else + fac = 4; + if ((i % fac) == 0) { + //Hot + if ((hotColdMode == hotColdMode_hot) && (i >= (colorLevel * colorElements)) && (calStatus != cal_warmup) && (displayMode != displayMode_combined)) + getHotColdColors(&red, &green, &blue); + //Cold + else if ((hotColdMode == hotColdMode_cold) && (i <= (colorLevel * colorElements)) && (calStatus != cal_warmup) && (displayMode != displayMode_combined)) + getHotColdColors(&red, &green, &blue); + //Other + else { + red = colorMap[i * 3]; + green = colorMap[(i * 3) + 1]; + blue = colorMap[(i * 3) + 2]; + } + //Draw the line + display_setColor(red, green, blue); + //For 320x240 + if (teensyVersion == teensyVersion_new) + display_drawLine(298, height - count, 314, height - count); + //For 160x120 + else + display_drawLine(298, (height - count) * 2, 314, (height - count) * 2); + //Raise counter + count++; + } + } + + //Set text color + changeTextColor(); + + //Calculate min and max temp in celcius/fahrenheit + float min = calFunction(minValue); + float max = calFunction(maxValue); + //Calculate step + float step = (max - min) / 3.0; + + //For 320x240, set fac to one + if (teensyVersion == teensyVersion_new) + fac = 1; + //For 160x120, set fac to two + else + fac = 2; + + //Draw min temp + sprintf(buffer, "%d", (int)round(min)); + display_print(buffer, 270, (height * fac) - 5); + + //Draw temperatures after min before max + for (int i = 2; i >= 1; i--) { + float temp = min + (i*step); + sprintf(buffer, "%d", (int)round(temp)); + display_print(buffer, 270, (height * fac) - 5 - (i * (colorElements / 6))); + } + + //Draw max temp + sprintf(buffer, "%d", (int)round(max)); + display_print(buffer, 270, (height * fac) - 5 - (3 * (colorElements / 6))); +} + +/* Change the display options */ +void changeDisplayOptions(byte* pos) { + switch (*pos) { + //Battery + case 0: + batteryEnabled = !batteryEnabled; + EEPROM.write(eeprom_batteryEnabled, batteryEnabled); + break; + + //Time + case 1: + timeEnabled = !timeEnabled; + EEPROM.write(eeprom_timeEnabled, timeEnabled); + break; + + //Date + case 2: + dateEnabled = !dateEnabled; + EEPROM.write(eeprom_dateEnabled, dateEnabled); + break; + + //Spot + case 3: + spotEnabled = !spotEnabled; + EEPROM.write(eeprom_spotEnabled, spotEnabled); + break; + + //Colorbar + case 4: + colorbarEnabled = !colorbarEnabled; + EEPROM.write(eeprom_colorbarEnabled, colorbarEnabled); + break; + + //Storage + case 5: + storageEnabled = !storageEnabled; + EEPROM.write(eeprom_storageEnabled, storageEnabled); + break; + + //Filter + case 6: + if (filterType == filterType_box) + filterType = filterType_gaussian; + else if (filterType == filterType_gaussian) + filterType = filterType_none; + else + filterType = filterType_box; + EEPROM.write(eeprom_filterType, filterType); + break; + + //Text color + case 7: + if (textColor == textColor_white) + textColor = textColor_black; + else if (textColor == textColor_black) + textColor = textColor_red; + else if (textColor == textColor_red) + textColor = textColor_green; + else if (textColor == textColor_green) + textColor = textColor_blue; + else + textColor = textColor_white; + EEPROM.write(eeprom_textColor, textColor); + break; + + //Hottest or coldest display + case 8: + if (minMaxPoints == minMaxPoints_disabled) + minMaxPoints = minMaxPoints_min; + else if (minMaxPoints == minMaxPoints_min) + minMaxPoints = minMaxPoints_max; + else if (minMaxPoints == minMaxPoints_max) + minMaxPoints = minMaxPoints_both; + else + minMaxPoints = minMaxPoints_disabled; + EEPROM.write(eeprom_minMaxPoints, minMaxPoints); + break; + } +} + +/* Map to the right color scheme */ +void selectColorScheme() { + //Select the right color scheme + switch (colorScheme) { + //Arctic + case colorScheme_arctic: + colorMap = colorMap_arctic; + colorElements = 240; + break; + + //Black-Hot + case colorScheme_blackHot: + colorMap = colorMap_blackHot; + colorElements = 224; + break; + + //Blue-Red + case colorScheme_blueRed: + colorMap = colorMap_blueRed; + colorElements = 192; + break; + + //Coldest + case colorScheme_coldest: + colorMap = colorMap_coldest; + colorElements = 224; + break; + + //Contrast + case colorScheme_contrast: + colorMap = colorMap_contrast; + colorElements = 224; + break; + + //Double-Rainbow + case colorScheme_doubleRainbow: + colorMap = colorMap_doubleRainbow; + colorElements = 256; + break; + + //Gray-Red + case colorScheme_grayRed: + colorMap = colorMap_grayRed; + colorElements = 224; + break; + + //Glowbow + case colorScheme_glowBow: + colorMap = colorMap_glowBow; + colorElements = 224; + break; + + //Grayscale + case colorScheme_grayscale: + colorMap = colorMap_grayscale; + colorElements = 256; + break; + + //Hottest + case colorScheme_hottest: + colorMap = colorMap_hottest; + colorElements = 224; + break; + + //Ironblack + case colorScheme_ironblack: + colorMap = colorMap_ironblack; + colorElements = 256; + break; + + //Lava + case colorScheme_lava: + colorMap = colorMap_lava; + colorElements = 240; + break; + + //Medical + case colorScheme_medical: + colorMap = colorMap_medical; + colorElements = 224; + break; + + //Rainbow + case colorScheme_rainbow: + colorMap = colorMap_rainbow; + colorElements = 256; + break; + + //Wheel 1 + case colorScheme_wheel1: + colorMap = colorMap_wheel1; + colorElements = 256; + break; + + //Wheel 2 + case colorScheme_wheel2: + colorMap = colorMap_wheel2; + colorElements = 256; + break; + + //Wheel 3 + case colorScheme_wheel3: + colorMap = colorMap_wheel3; + colorElements = 256; + break; + + //White-Hot + case colorScheme_whiteHot: + colorMap = colorMap_whiteHot; + colorElements = 224; + break; + + //Yellow + case colorScheme_yellow: + colorMap = colorMap_yellow; + colorElements = 224; + break; + } +} + +/* Change the color scheme for the thermal image */ +void changeColorScheme(byte* pos) { + //Align position to color scheme + colorScheme = *pos; + //Map to the right color scheme + selectColorScheme(); + //Save to EEPROM + EEPROM.write(eeprom_colorScheme, colorScheme); +} + +/* Show the thermal/visual/combined image on the screen */ +void showImage() { + //Draw thermal image on screen if created previously and not in menu nor in video save + if ((!imgSave) && (!showMenu) && (!videoSave)) + displayBuffer(); + + //If the image has been created, set to save + if (imgSave == imgSave_create) + imgSave = imgSave_save; +} + +/* Init procedure for the live mode */ +void liveModeInit() { + //Activate laser if enabled on old HW + if (laserEnabled && (teensyVersion == teensyVersion_old)) + digitalWrite(pin_laser, HIGH); + + //Select color scheme + selectColorScheme(); + + //For visual / combined, change cam res and take firts shot + if (displayMode != displayMode_thermal) + camera_setDisplayRes(); + + //Attach the Button interrupt + attachInterrupt(pin_button, buttonIRQ, RISING); + //Attach the Touch interrupt + attachInterrupt(pin_touch_irq, touchIRQ, FALLING); + + //Disable showmenu + showMenu = showMenu_disabled; + + //Clear temperature points array + clearTempPoints(); +} + +/* Main entry point for the live mode */ +void liveMode() { + //Init + liveModeInit(); + + //Main Loop + while (true) { + //Check for serial connection + checkSerial(); + + //Check for screen sleep + screenOffCheck(); + + //If touch IRQ has been triggered, open menu + if (showMenu == showMenu_desired) + mainMenu(); + + //Start the image save procedure + if (imgSave == imgSave_set) + imgSaveStart(); + + //Create thermal image + if (displayMode == displayMode_thermal) + createThermalImg(); + //Create visual or combined image + else + createVisCombImg(); + + //Display additional information + displayInfos(); + + //Show the content on the screen + showImage(); + + //Save the converted / visual image + if (imgSave == imgSave_save) + imgSaveEnd(); + + //Go into video mode + if (videoSave == videoSave_menu) + videoMode(); + + //Long touch handler + if ((longTouch) && (calStatus == cal_standard)) + longTouchHandler(); + } +} diff --git a/Firmware_V2/src/thermal/thermal.h b/Firmware_V2/src/thermal/thermal.h new file mode 100644 index 0000000..a3e1ce7 --- /dev/null +++ b/Firmware_V2/src/thermal/thermal.h @@ -0,0 +1,32 @@ +/* +* +* THERMAL - Main functions in the live mode +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef THERMAL_H +#define THERMAL_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void buttonIRQ(); +void changeColorScheme(byte* pos); +void changeDisplayOptions(byte* pos); +void liveModeInit(); +void liveMode(); +void longTouchHandler(); +void selectColorScheme(); +void showColorBar(); +void showImage(); +void touchIRQ(); + +#endif /* THERMAL_H */ diff --git a/Firmware_V3/.gitignore b/Firmware_V3/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/Firmware_V3/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/Firmware_V3/.vscode/extensions.json b/Firmware_V3/.vscode/extensions.json new file mode 100644 index 0000000..e80666b --- /dev/null +++ b/Firmware_V3/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/Firmware_V3/.vscode/settings.json b/Firmware_V3/.vscode/settings.json new file mode 100644 index 0000000..f1fce88 --- /dev/null +++ b/Firmware_V3/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "files.associations": { + "*.tcc": "cpp", + "functional": "cpp" + } +} \ No newline at end of file diff --git a/Firmware_V3/README.MD b/Firmware_V3/README.MD new file mode 100644 index 0000000..6279ffa --- /dev/null +++ b/Firmware_V3/README.MD @@ -0,0 +1,36 @@ +# DIY-Thermocam V3 Firmware # + +GNU General Public License v3.0, copyright by Max Ritter + +**The following hardware is supported:** + +- **DIY-Thermocam V3** (2021) - Lepton2.5 / Lepton3.5 only + + + +---------- + +**THIS GUIDE IS ONLY REQUIRED, IF YOU WANT TO MAKE CHANGES TO THE FIRMWARE ON YOUR OWN!** + +If you just want to flash the newest version of the firmware to your device, follow the Firmware Update Guide in the Document section and use one of the pre-compiled versions (*.hex) from **[here](https://github.com/maxritter/diythermocam_firmware/releases)**. + +---------- + +This guide should work on **all common operating systems** (**Windows, Linux & Mac OSX**). + +**Download** and **install** the following programs: + +1. [VS Code](https://code.visualstudio.com/) +2. [PlatformIO for VS Code](https://platformio.org/install/ide?install=vscode) + +Then **clone this repo**: +`git clone https://github.com/maxritter/diythermocam_firmware.git`. + +The **Teensyduino** version that comes pre-installed with PlatformIO does not support Mass Storage Mode (MTP), so we **need to update it**. Extract the content of `other/platformio_teensy4.zip` to: `C:\Users\\.platformio\packages\framework-arduinoteensy` (filepath on Windows or Mac is different, I am using Windows) and overwrite any existing files. + +Now **start VS Code** and open the folder that you have just cloned with **File -> Open Folder**. + +**PlatformIO should initialize itself automatically** and you see the buttons to **Build, Upload and Clean** the project in the blue bar **at the bottom**. + +**Before you click "Upload"**, make sure the Thermocam is **connected to the PC** via a micro USB cable and **turned on**. The **Teensy CLI** should then automatically detect it and **flash the .hex file to the board**. After a restart the changes should be present on the device. + diff --git a/Firmware_V3/include/battery.h b/Firmware_V3/include/battery.h new file mode 100644 index 0000000..623e601 --- /dev/null +++ b/Firmware_V3/include/battery.h @@ -0,0 +1,24 @@ +/* +* +* BATTERY - Measure the lithium battery status +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef BATTERY_H +#define BATTERY_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void checkBattery(bool start = false, bool calibrate = false); +int getLipoPerc(float vol); + +#endif /* BATTERY_H */ diff --git a/Firmware_V3/include/bitmaps.h b/Firmware_V3/include/bitmaps.h new file mode 100644 index 0000000..56460d3 --- /dev/null +++ b/Firmware_V3/include/bitmaps.h @@ -0,0 +1,48 @@ +/* +* +* BITMAPS - Icons and graphics shown inside the GUI +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef BITMAPS_H +#define BITMAPS_H + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +extern const uint16_t iconChangeColorPalette[]; +extern const uint8_t iconChangeColorBMP[]; +extern const uint16_t iconTempLimitsPalette[]; +extern const uint8_t iconTempLimitsBMP[]; +extern const uint16_t iconLoadMenuPalette[]; +extern const uint8_t iconLoadMenuBMP[]; +extern const uint16_t iconShutterPalette[]; +extern const uint8_t iconShutterBMP[]; +extern const uint16_t iconSettingsMenuPalette[]; +extern const uint8_t iconSettingsMenuBMP[]; +extern const uint16_t iconDisplaySettingsPalette[]; +extern const uint8_t iconDisplaySettingsBMP[]; +extern const uint16_t iconDisplayOffPalette[]; +extern const uint8_t iconDisplayOffBMP[]; +extern const uint16_t iconHotColdPalette[]; +extern const uint8_t iconHotColdBMP[]; +extern const uint16_t iconTempPointsPalette[]; +extern const uint8_t iconTempPointsBMP[]; +extern const uint16_t iconBWColors[]; +extern const uint8_t iconBWBitmap[]; +extern const uint16_t iconReturnColors[]; +extern const uint8_t iconReturnBitmap[]; +extern const uint16_t iconFWColors[]; +extern const uint8_t iconFWBitmap[]; +extern const uint16_t logoColors[]; +extern const uint8_t logoBitmap[]; + +#endif /* BITMAPS_H */ diff --git a/Firmware_V3/include/buttons.h b/Firmware_V3/include/buttons.h new file mode 100644 index 0000000..1027798 --- /dev/null +++ b/Firmware_V3/include/buttons.h @@ -0,0 +1,39 @@ +/* +* +* Buttons - Touch buttons for the GUI +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef BUTTONS_H +#define BUTTONS_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +int buttons_addButton(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, const uint16_t* palette, uint16_t flags = 0); +int buttons_addButton(uint16_t x, uint16_t y, uint16_t width, uint16_t height, char *label, uint16_t flags = 0, boolean largetouch = 0); +boolean buttons_buttonEnabled(int buttonID); +int buttons_checkButtons(boolean timeout = 0, boolean fast = 0); +void buttons_deleteAllButtons(); +void buttons_deleteButton(int buttonID); +void buttons_disableButton(int buttonID, boolean redraw = 0); +void buttons_drawButtons(); +void buttons_drawButton(int buttonID); +void buttons_enableButton(int buttonID, boolean redraw = 0); +void buttons_init(); +void buttons_relabelButton(int buttonID, char *label, boolean redraw = 0); +void buttons_setActive(int buttonID); +void buttons_setButtonColors(word atxt, word iatxt, word brd, word brdhi, word back); +void buttons_setInactive(int buttonID); +void buttons_setSymbolFont(const uint8_t* font); +void buttons_setTextFont(const uint8_t* font); + +#endif /* BUTTONS_H */ diff --git a/Firmware_V3/include/colorschemes.h b/Firmware_V3/include/colorschemes.h new file mode 100644 index 0000000..76b187c --- /dev/null +++ b/Firmware_V3/include/colorschemes.h @@ -0,0 +1,41 @@ +/* +* +* COLOR SCHEMES - Contains 19 different color schemes to display the thermal image +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef COLORSCHEMES_H +#define COLORSCHEMES_H + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +extern const byte colorMap_arctic[]; +extern const byte colorMap_blackHot[]; +extern const byte colorMap_blueRed[]; +extern const byte colorMap_coldest[]; +extern const byte colorMap_contrast[]; +extern const byte colorMap_doubleRainbow[]; +extern const byte colorMap_grayRed[]; +extern const byte colorMap_glowBow[]; +extern const byte colorMap_grayscale[]; +extern const byte colorMap_hottest[]; +extern const byte colorMap_ironblack[]; +extern const byte colorMap_lava[]; +extern const byte colorMap_medical[]; +extern const byte colorMap_rainbow[]; +extern const byte colorMap_wheel1[]; +extern const byte colorMap_wheel2[]; +extern const byte colorMap_wheel3[]; +extern const byte colorMap_whiteHot[]; +extern const byte colorMap_yellow[]; + +#endif /* COLORSCHEMES_H */ diff --git a/Firmware_V3/include/connection.h b/Firmware_V3/include/connection.h new file mode 100644 index 0000000..293b0da --- /dev/null +++ b/Firmware_V3/include/connection.h @@ -0,0 +1,112 @@ +/* +* +* CONNECTION - Communication protocol for the USB serial data transmission +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef CONNECTION_H +#define CONNECTION_H + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +//Start & Stop command +#define CMD_START 100 +#define CMD_END 200 +#define CMD_INVALID 0 + +//Serial terminal commands +#define CMD_GET_RAWLIMITS 110 +#define CMD_GET_RAWDATA 111 +#define CMD_GET_CONFIGDATA 112 +#define CMD_GET_CALSTATUS 113 +#define CMD_GET_CALIBDATA 114 +#define CMD_GET_SPOTTEMP 115 +#define CMD_SET_TIME 116 +#define CMD_GET_TEMPPOINTS 117 +#define CMD_SET_LASER 118 +#define CMD_GET_LASER 119 +#define CMD_SET_SHUTTERRUN 120 +#define CMD_SET_SHUTTERMODE 121 +#define CMD_SET_FILTERTYPE 122 +#define CMD_GET_SHUTTERMODE 123 +#define CMD_GET_BATTERYSTATUS 124 +#define CMD_SET_CALSLOPE 125 +#define CMD_SET_CALOFFSET 126 +#define CMD_GET_DIAGNOSTIC 127 +#define CMD_GET_VISUALIMG 128 +#define CMD_GET_FWVERSION 129 +#define CMD_SET_LIMITS 130 +#define CMD_SET_TEXTCOLOR 131 +#define CMD_SET_COLORSCHEME 132 +#define CMD_SET_TEMPFORMAT 133 +#define CMD_SET_SHOWSPOT 134 +#define CMD_SET_SHOWCOLORBAR 135 +#define CMD_SET_SHOWMINMAX 136 +#define CMD_SET_TEMPPOINTS 137 +#define CMD_GET_HWVERSION 138 +#define CMD_SET_ROTATION 139 +#define CMD_SET_CALIBRATION 140 +#define CMD_GET_HQRESOLUTION 141 + +//Serial frame commands +#define CMD_FRAME_RAW 150 +#define CMD_FRAME_COLOR 151 +#define CMD_FRAME_DISPLAY 152 +#define CMD_FRAME_SAVE 153 + +//Types of raw frame responses +#define FRAME_CAPTURE_THERMAL 180 +#define FRAME_CAPTURE_VISUAL 181 +#define FRAME_CAPTURE_VIDEO 182 +#define FRAME_NORMAL 183 + +/*########################## PUBLIC PROCEDURES ################################*/ + +void buttonHandler(); +void checkForUpdater(); +bool checkNoDisplay(); +void checkSerial(); +int getInt(String text); +void saveFrame(); +void sendBatteryStatus(); +void sendCalibrationData(); +void sendConfigData(); +void sendDiagnostic(); +void sendDisplayFrame(); +void sendFramebuffer(); +void sendFrame(bool color); +void sendFWVersion(); +void sendHardwareVersion(); +void sendHQResolution(); +void sendRawData(bool color = false); +void sendRawLimits(); +void sendSpotTemp(); +void sendTempPoints(); +void serialConnect(); +bool serialHandler(); +void serialInit(); +void serialOutput(); +void setColorScheme(); +void setFilterType(); +void setLimits(); +void setMinMax(); +void setRotation(); +void setShowColorbar(); +void setShowSpot(); +void setShutterMode(); +void setTempFormat(); +void setTempPoints(); +void setTextColor(); +void setTime(); +bool touchHandler(); + +#endif /* CONNECTION_H */ diff --git a/Firmware_V3/include/create.h b/Firmware_V3/include/create.h new file mode 100644 index 0000000..7d265da --- /dev/null +++ b/Firmware_V3/include/create.h @@ -0,0 +1,37 @@ +/* +* +* CREATE - Functions to create and display the thermal frameBuffer +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef CREATE_H +#define CREATE_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void boxFilter(); +void calculatePointPos(int16_t* xpos, int16_t* ypos, uint16_t pixelIndex); +void clearTempPoints(); +void convertColors(bool small = false); +void createThermalImg(bool small = false); +void gaussianFilter(); +void getHotColdColors(byte* red, byte* green, byte* blue); +void getTouchPos(uint16_t* x, uint16_t* y); +void limitValues(); +void refreshMinMax(); +void refreshTempPoints(); +void resizePixels(unsigned short* pixels, int w1, int h1, int w2, int h2); +void showTemperatures(); +void smallToBigBuffer(); +void tempPointFunction(bool remove = false); + +#endif /* CREATE_H */ diff --git a/Firmware_V3/include/display.h b/Firmware_V3/include/display.h new file mode 100644 index 0000000..6a15cbe --- /dev/null +++ b/Firmware_V3/include/display.h @@ -0,0 +1,123 @@ +/* +* +* Display - ILI9341 SPI Display Module +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef DISPLAY_H +#define DISPLAY_H + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +#define LEFT 0 +#define RIGHT 9999 +#define CENTER 9998 + +#define PORTRAIT 0 +#define LANDSCAPE 1 + +#define VGA_BLACK 0x0000 +#define VGA_WHITE 0xFFFF +#define VGA_RED 0xF800 +#define VGA_GREEN 0x0400 +#define VGA_BLUE 0x001F +#define VGA_SILVER 0xC618 +#define VGA_GRAY 0x8410 +#define VGA_MAROON 0x8000 +#define VGA_YELLOW 0xFE40 +#define VGA_OLIVE 0x8400 +#define VGA_LIME 0x07E0 +#define VGA_AQUA 0xBE7F +#define VGA_TEAL 0x0410 +#define VGA_NAVY 0x0010 +#define VGA_FUCHSIA 0xF81F +#define VGA_PURPLE 0x8010 +#define VGA_TRANSPARENT 0xFFFFFFFF + +struct propFont +{ + byte charCode; + int adjYOffset; + int width; + int height; + int xOffset; + int xDelta; + byte* dataPtr; +}; + +extern boolean display_writeToImage; + +/*########################## PUBLIC PROCEDURES ################################*/ + +void display_clrScr(); +void display_clrXY(); +void display_convertFloat(char* buf, double num, int width, byte prec); +void display_drawBitmap(int x, int y, int w, int h, unsigned short *data); +void display_drawCircle(int x, int y, int radius); +void display_drawHLine(int x, int y, int l); +void display_drawLine(int x1, int y1, int x2, int y2); +void display_drawPixel(int x, int y); +void display_drawRect(int x1, int y1, int x2, int y2); +void display_drawRoundRect(int x1, int y1, int x2, int y2); +void display_drawVLine(int x, int y, int l); +void display_enterSleepMode(); +void display_exitSleepMode(); +void display_fillCircle(int x, int y, int radius); +void display_fillRect(int x1, int y1, int x2, int y2); +void display_fillRoundRect(int x1, int y1, int x2, int y2); +void display_fillScr(word color); +void display_fillScr(byte r, byte g, byte b); +word display_getBackColor(); +boolean display_getCharPtr(byte c, propFont& fontChar); +word display_getColor(); +int display_getFontHeight(); +uint8_t* display_getFont(); +uint8_t display_getFontXsize(); +uint8_t display_getFontYsize(); +int display_getStringWidth(char* str); +byte display_InitLCD(); +void display_init(); +void display_LCD_Write_DATA(char VH, char VL); +void display_printChar(byte c, int x, int y); +void display_printC(String st, int x, int y, uint32_t color = VGA_BLACK); +void display_printNumF(double num, byte dec, int x, int y, char divider = '.', int length = 0, char filler = ' '); +void display_printNumI(long num, int x, int y, int length = 0, char filler = ' '); +int display_printProportionalChar(byte c, int x, int y); +void display_print(char* st, int x, int y, int deg = 0); +void display_print(String st, int x, int y, int deg = 0); +uint8_t display_readcommand8(uint8_t c, uint8_t index = 0); +void display_rotateChar(byte c, int x, int y, int pos, int deg); +int display_rotatePropChar(byte c, int x, int y, int offset, int deg); +void display_setAddr(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1); +void display_setBackColor(byte r, byte g, byte b); +void display_setBackColor(uint32_t color); +void display_setColor(byte r, byte g, byte b); +void display_setColor(word color); +void display_setFont(const uint8_t* font); +void display_setPixel(word color); +void display_setRotation(uint8_t m); +void display_setXY(word x1, word y1, word x2, word y2); +void display_waitFifoEmpty(); +void display_waitFifoNotFull(); +void display_maybeUpdateTCR(uint32_t requested_tcr_state); +void display_waitTransmitComplete(); +void display_writecommand_cont(uint8_t c); +void display_writecommand_last(uint8_t c); +void display_writedata16_cont(uint16_t d); +void display_writedata16_last(uint16_t d); +void display_writedata8_cont(uint8_t c); +void display_writedata8_last(uint8_t c); +void display_writeRect2BPP(int16_t x, int16_t y, int16_t w, int16_t h, const uint8_t* pixels, const uint16_t* palette); +void display_writeRect4BPP(int16_t x, int16_t y, int16_t w, int16_t h, const uint8_t* pixels, const uint16_t* palette); +void display_writeScreen(unsigned short *pcolors, boolean small); + +#endif /* DISPLAY_H */ diff --git a/Firmware_V3/include/firststart.h b/Firmware_V3/include/firststart.h new file mode 100644 index 0000000..67eb07f --- /dev/null +++ b/Firmware_V3/include/firststart.h @@ -0,0 +1,34 @@ +/* +* +* FIRST START - Menu that is displayed on the first device start +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef FIRSTSTART_H +#define FIRSTSTART_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +boolean checkFirstStart(); +boolean checkLiveModeHelper(); +void firstFormat(); +void firstStartComplete(); +void firstStart(); +void infoScreen(String* text, bool cont = true); +void liveModeHelper(); +void stdEEPROMSet(); +void tempFormatScreen(); +void timeDateScreen(); +void welcomeScreen(); +void convertImageScreen(); + +#endif /* FIRSTSTART_H */ diff --git a/Firmware_V3/include/fonts.h b/Firmware_V3/include/fonts.h new file mode 100644 index 0000000..ff51081 --- /dev/null +++ b/Firmware_V3/include/fonts.h @@ -0,0 +1,24 @@ +/* +* +* Fonts +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef FONTS_H +#define FONTS_H + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +extern const uint8_t smallFont[]; +extern const uint8_t bigFont[]; + +#endif /* FONTS_H */ diff --git a/Firmware_V3/include/ft6206_touchscreen.h b/Firmware_V3/include/ft6206_touchscreen.h new file mode 100644 index 0000000..5ae0944 --- /dev/null +++ b/Firmware_V3/include/ft6206_touchscreen.h @@ -0,0 +1,55 @@ +/* + * + * FT6206 Touch controller + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +#ifndef FT6206_TOUCHSCREEN_H +#define FT6206_TOUCHSCREEN_H + +#define FT6206_ADDR 0x38 +#define FT6206_G_FT5201ID 0xA8 +#define FT6206_REG_NUMTOUCHES 0x02 + +#define FT6206_NUM_X 0x33 +#define FT6206_NUM_Y 0x34 +#define FT6206_REG_MODE 0x00 +#define FT6206_REG_CALIBRATE 0x02 +#define FT6206_REG_WORKMODE 0x00 +#define FT6206_REG_FACTORYMODE 0x40 +#define FT6206_REG_THRESHHOLD 0x80 +#define FT6206_REG_POINTRATE 0x88 +#define FT6206_REG_FIRMVERS 0xA6 +#define FT6206_REG_CHIPID 0xA3 +#define FT6206_REG_VENDID 0xA8 + +#define FT6206_DEFAULT_THRESSHOLD 128 + +/*########################## PUBLIC PROCEDURES ################################*/ + +class FT6206_Touchscreen { +public: + boolean begin(uint8_t thresh = FT6206_DEFAULT_THRESSHOLD); + void writeRegister8(uint8_t reg, uint8_t val); + uint8_t readRegister8(uint8_t reg); + void readData(uint16_t *x, uint16_t *y); + boolean touched(void); + TS_Point getPoint(void); + bool rotated = false; +private: + uint8_t touches; + uint16_t touchX[2], touchY[2], touchID[2]; +}; + +#endif /* FT6206_TOUCHSCREEN_H */ diff --git a/Firmware_V3/include/globaldefines.h b/Firmware_V3/include/globaldefines.h new file mode 100644 index 0000000..f2ee78f --- /dev/null +++ b/Firmware_V3/include/globaldefines.h @@ -0,0 +1,216 @@ +/* +* +* GLOBAL DEFINES - Global defines, that are used firmware-wide +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef GLOBALDEFINES_H +#define GLOBALDEFINES_H + +#ifdef __cplusplus +extern "C" { +#endif + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +//Pins +#define pin_button 2 +#define pin_lepton_vsync 3 +#define pin_touch_irq 5 +#define pin_touch_cs 9 +#define pin_lcd_dc 10 +#define pin_mosi 11 +#define pin_miso 12 +#define pin_sck 13 +#define pin_sda 18 +#define pin_scl 19 +#define pin_lcd_cs 21 +#define pin_lcd_backlight 22 +#define pin_bat_measure 23 +#define pin_mosi1 26 +#define pin_sck1 27 +#define pin_lepton_cs 38 +#define pin_miso1 39 +#define pin_usb_measure A16 + +//FLIR Lepton sensor version +#define leptonVersion_2_5_shutter 0 //FLIR Lepton 2.5 Shuttered +#define leptonVersion_3_5_shutter 1 //FLIR Lepton 3.5 Shuttered + +//Temperature format +#define tempFormat_celcius 0 +#define tempFormat_fahrenheit 1 + +//Filter type +#define filterType_none 0 +#define filterType_gaussian 1 +#define filterType_box 2 + +//Display Min/Max Points +#define minMaxPoints_disabled 0 +#define minMaxPoints_min 1 +#define minMaxPoints_max 2 +#define minMaxPoints_both 3 + +//Text color +#define textColor_white 0 +#define textColor_black 1 +#define textColor_red 2 +#define textColor_green 3 +#define textColor_blue 4 + +//Screen off time +#define screenOffTime_disabled 0 +#define screenOffTime_5min 1 +#define screenOffTime_20min 2 + +//Hot / cold +#define hotColdMode_disabled 0 +#define hotColdMode_cold 1 +#define hotColdMode_hot 2 + +//Hot / cold color +#define hotColdColor_white 0 +#define hotColdColor_black 1 +#define hotColdColor_red 2 +#define hotColdColor_green 3 +#define hotColdColor_blue 4 + +//Lepton Gain mode +#define lepton_gain_high 0 +#define lepton_gain_low 1 + +//EEPROM registers +#define eeprom_leptonVersion 100 +#define eeprom_tempFormat 101 +#define eeprom_colorScheme 102 +#define eeprom_convertEnabled 103 +#define eeprom_spotEnabled 105 +#define eeprom_colorbarEnabled 106 +#define eeprom_batteryEnabled 107 +#define eeprom_timeEnabled 108 +#define eeprom_dateEnabled 109 +#define eeprom_storageEnabled 111 +#define eeprom_rotationVert 112 +#define eeprom_textColor 114 +#define eeprom_filterType 115 +#define eeprom_minValue1Low 116 +#define eeprom_minValue1High 117 +#define eeprom_maxValue1Low 118 +#define eeprom_maxValue1High 119 +#define eeprom_minMax1Set 120 +#define eeprom_adjComb1Left 121 +#define eeprom_adjComb1Right 122 +#define eeprom_adjComb1Up 123 +#define eeprom_adjComb1Down 124 +#define eeprom_adjComb1Alpha 125 +#define eeprom_adjComb1Set 126 +#define eeprom_minMaxPoints 127 +#define eeprom_screenOffTime 128 +#define eeprom_massStorage 129 +#define eeprom_hotColdMode 135 +#define eeprom_hotColdLevelLow 136 +#define eeprom_hotColdLevelHigh 137 +#define eeprom_hotColdColor 138 +#define eeprom_firstStart 150 +#define eeprom_liveHelper 151 +#define eeprom_minValue2Low 154 +#define eeprom_minValue2High 155 +#define eeprom_maxValue2Low 156 +#define eeprom_maxValue2High 157 +#define eeprom_minMax2Set 158 +#define eeprom_minValue3Low 159 +#define eeprom_minValue3High 160 +#define eeprom_maxValue3Low 161 +#define eeprom_maxValue3High 162 +#define eeprom_minMax3Set 163 +#define eeprom_minMaxPreset 164 +#define eeprom_noShutter 169 +#define eeprom_batComp 170 +#define eeprom_rotationHorizont 171 +#define eeprom_minMax1Comp 172 //4 Byte (172-175) +#define eeprom_minMax2Comp 176 //4 Byte (176-179) +#define eeprom_minMax3Comp 180 //4 Byte (180-183) +#define eeprom_lepton_gain 184 +#define eeprom_fwVersionLow 250 +#define eeprom_fwVersionHigh 251 +#define eeprom_setValue 200 + +//Presets for min/max +#define minMax_temporary 0 +#define minMax_preset1 1 +#define minMax_preset2 2 +#define minMax_preset3 3 + +//Hardware diagnostic bit codes +#define diag_display 0 +#define diag_touch 1 +#define diag_sd 2 +#define diag_bat 3 +#define diag_lep_conf 4 +#define diag_lep_data 5 +#define diag_ok 255 + +//Color scheme numbers +#define colorSchemeTotal 19 +#define colorScheme_arctic 0 +#define colorScheme_blackHot 1 +#define colorScheme_blueRed 2 +#define colorScheme_coldest 3 +#define colorScheme_contrast 4 +#define colorScheme_doubleRainbow 5 +#define colorScheme_grayRed 6 +#define colorScheme_glowBow 7 +#define colorScheme_grayscale 8 +#define colorScheme_hottest 9 +#define colorScheme_ironblack 10 +#define colorScheme_lava 11 +#define colorScheme_medical 12 +#define colorScheme_rainbow 13 +#define colorScheme_wheel1 14 +#define colorScheme_wheel2 15 +#define colorScheme_wheel3 16 +#define colorScheme_whiteHot 17 +#define colorScheme_yellow 18 + +//Image save marker +#define imgSave_disabled 0 +#define imgSave_save 1 +#define imgSave_set 2 +#define imgSave_create 3 + +//Video save marker +#define videoSave_disabled 0 +#define videoSave_menu 1 +#define videoSave_recording 2 +#define videoSave_processing 3 + +//Show menu state +#define showMenu_disabled 0 +#define showMenu_desired 1 +#define showMenu_opened 2 + +//Load touch decision marker +#define loadTouch_none 0 +#define loadTouch_find 1 +#define loadTouch_delete 2 +#define loadTouch_previous 3 +#define loadTouch_next 4 +#define loadTouch_exit 5 +#define loadTouch_convert 6 +#define loadTouch_middle 7 + +#ifdef __cplusplus +} +#endif + +#endif /* GLOBALDEFINES_H */ diff --git a/Firmware_V3/include/globalvariables.h b/Firmware_V3/include/globalvariables.h new file mode 100644 index 0000000..d570612 --- /dev/null +++ b/Firmware_V3/include/globalvariables.h @@ -0,0 +1,89 @@ +/* +* +* GLOBAL VARIABLES - Global variable declarations, that are used firmware-wide +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef GLOBALVARIABLES_H +#define GLOBALVARIABLES_H + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +extern char versionString[]; +extern uint16_t fwVersion; +extern unsigned short* bigBuffer; +extern unsigned short* smallBuffer; +extern Metro screenOff; +extern boolean screenPressed; +extern byte screenOffTime; +extern Bounce buttonDebouncer; +extern Bounce touchDebouncer; +extern SdFat32 sd; +extern File32 sdFile; +extern String sdInfo; +extern File32 dir; +extern char saveFilename[20]; +extern ADC *batMeasure; +extern int8_t batPercentage; +extern long batTimer; +extern int8_t batComp; +extern bool convertEnabled; +extern bool autoMode; +extern bool limitsLocked; +extern bool rotationVert; +extern bool rotationHorizont; +extern bool batteryEnabled; +extern bool timeEnabled; +extern bool dateEnabled; +extern bool spotEnabled; +extern bool colorbarEnabled; +extern bool storageEnabled; +extern byte filterType; +extern byte minMaxPoints; +extern bool tempFormat; +extern byte textColor; +extern bool leptonGainMode; +extern byte leptonVersion; +extern byte diagnostic; +extern byte colorScheme; +extern const byte *colorMap; +extern int16_t colorElements; +extern uint16_t maxValue; +extern uint16_t minValue; +extern float spotTemp; +extern float ambTemp; +extern uint16_t minTempPos; +extern uint16_t minTempVal; +extern uint16_t maxTempPos; +extern uint16_t maxTempVal; +extern byte hotColdMode; +extern int16_t hotColdLevel; +extern byte hotColdColor; +extern uint16_t tempPoints[96][2]; +extern bool usbConnected; +extern float leptonCalSlope; +extern volatile byte imgSave; +extern volatile byte videoSave; +extern volatile byte showMenu; +extern volatile bool longTouch; +extern volatile bool serialMode; +extern volatile byte loadTouch; +extern volatile bool leptonBufferValid; + +#endif /* GLOBALVARIABLES_H */ diff --git a/Firmware_V3/include/gui.h b/Firmware_V3/include/gui.h new file mode 100644 index 0000000..9c36ce3 --- /dev/null +++ b/Firmware_V3/include/gui.h @@ -0,0 +1,31 @@ +/* +* +* GUI - Main Methods to lcd the Graphical-User-Interface +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef GUI_H +#define GUI_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void bootScreen(); +void changeTextColor(); +void drawCenterElement(int element); +void drawMainMenuBorder(); +void drawTitle(char* name, bool firstStart = false); +void floatToChar(char* buffer, float val); +void showDiagnostic(); +void showFullMessage(char* message, bool small = false); +void showTransMessage(char* msg); + +#endif /* GUI_H */ diff --git a/Firmware_V3/include/hardware.h b/Firmware_V3/include/hardware.h new file mode 100644 index 0000000..30490a6 --- /dev/null +++ b/Firmware_V3/include/hardware.h @@ -0,0 +1,53 @@ +/* +* +* HARDWARE - Main hardware functions +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef HARDWARE_H +#define HARDWARE_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void t4_direct_write_low(volatile uint32_t *base, uint32_t mask); +void t4_direct_write_high(volatile uint32_t *base, uint32_t mask); +bool isUSBConnected(); +float bytesToFloat(uint8_t* farray); +void checkHardware(); +bool checkDiagnostic(byte device); +void checkFWUpgrade(); +bool checkScreenLight(); +void clearEEPROM(); +void disableScreenLight(); +void displayBuffer(); +void enableScreenLight(); +boolean extButtonPressed(); +void floatToBytes(uint8_t* farray, float val); +void getSpotTemp(); +time_t getTeensy3Time(); +void initADC(); +void initBuffer(); +void initGPIO(); +void initHardware(); +void initI2C(); +void initRTC(); +void initScreenOffTimer(); +void initSPI(); +void readEEPROM(); +void readTempLimits(); +bool screenOffCheck(); +void setDiagnostic(byte device); +void setDisplayRotation(); +void toggleDisplay(); +boolean touchScreenPressed(); + +#endif /* HARDWARE_H */ diff --git a/Firmware_V3/include/lepton.h b/Firmware_V3/include/lepton.h new file mode 100644 index 0000000..cc1ca83 --- /dev/null +++ b/Firmware_V3/include/lepton.h @@ -0,0 +1,56 @@ +/* +* +* LEPTON - Access the FLIR Lepton LWIR module +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef LEPTON_H +#define LEPTON_H + +//Lepton frame error return +enum LeptonReadError +{ + NONE, + DISCARD, + SEGMENT_ERROR, + ROW_ERROR, + SEGMENT_INVALID +}; + +/*########################## PUBLIC PROCEDURES ################################*/ + +void lepton_begin(); +void lepton_end(); +bool lepton_ffc(bool message = false, bool switch_gain = false); +void lepton_ffcMode(bool automatic); +LeptonReadError lepton_getPacketSync(uint8_t line, uint8_t seg); +bool lepton_getPacketAsync(uint8_t *line, uint8_t *seg); +void lepton_getFrame(); +void lepton_getFrameAsync(); +void lepton_init(); +int lepton_readReg(byte reg); +void lepton_setReg(byte reg); +float lepton_spotTemp(); +bool lepton_version(); +void lepton_savePacket(uint8_t line, uint8_t segment = 0); +void lepton_setGpioMode(bool vsync_enabled); +void lepton_setSysGainHigh(); +void lepton_setSysGainLow(); +void lepton_setSysGainAuto(); +int lepton_getSysGainMode(); +float lepton_getResolution(); +void lepton_setLowGain(); +void lepton_setHighGain(); +void lepton_startFrame(); +void lepton_endFrame(); + +#endif /* LEPTON_H */ diff --git a/Firmware_V3/include/livemode.h b/Firmware_V3/include/livemode.h new file mode 100644 index 0000000..87b4ed6 --- /dev/null +++ b/Firmware_V3/include/livemode.h @@ -0,0 +1,30 @@ +/* +* +* LIVE MODE - GUI functions used in the live mode +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef LIVEMODE_H +#define LIVEMODE_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void displayBatteryStatus(); +void displayDate(); +void displayFreeSpace(); +void displayInfos(); +void displayMinMaxPoint(bool min); +void displayTempMode(); +void displayTime(); +void showSpot(); + +#endif /* LIVEMODE_H */ diff --git a/Firmware_V3/include/load.h b/Firmware_V3/include/load.h new file mode 100644 index 0000000..c6430a7 --- /dev/null +++ b/Firmware_V3/include/load.h @@ -0,0 +1,48 @@ +/* +* +* LOAD - Load images and videos from the internal storage +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef LOAD_H +#define LOAD_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void checkFileEnding(bool* check, char* filename); +void checkFileStructure(bool* check); +bool checkFileValidity(); +void chooseFile(char* filename); +void clearData(); +void copyIntoBuffers(char* filename); +bool dayChoose(bool* days, char* filename); +void displayRawData(); +bool findFile(char* filename, bool next, bool restart, int* position = 0, char* compare = NULL); +bool hourChoose(bool* hours, char* filename); +bool isImage(char* filename); +void loadAlloc(); +void loadBMPImage(char* filename); +void loadDeAlloc(); +bool loadDelete(char* filename, int* pos); +void loadFiles(); +void loadFind(char* filename, int* pos); +void loadRawData(char* filename); +void loadSettings(); +void loadTouchIRQ(); +bool minuteChoose(bool* minutes, char* filename); +bool monthChoose(bool* months, char* filename); +void readTempPoints(); +void searchFiles(); +bool secondChoose(bool* seconds, char* filename); +bool yearChoose(char* filename); + +#endif /* LOAD_H */ diff --git a/Firmware_V3/include/loadmenu.h b/Firmware_V3/include/loadmenu.h new file mode 100644 index 0000000..a1877eb --- /dev/null +++ b/Firmware_V3/include/loadmenu.h @@ -0,0 +1,33 @@ +/* +* +* LOAD MENU - Display the menu to load images and videos +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef LOADMENU_H +#define LOADMENU_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void convertImage(char* filename); +bool convertPrompt(); +void convertVideo(char* dirname); +void deleteImage(char* filename); +void deleteVideo(char* dirname); +void displayGUI(int imgCount, char* infoText); +void displayVideoFrame(int i); +uint16_t getVideoFrameNumber(); +int loadMenu(char* title, int* array, int length); +void openImage(char* filename, int imgCount); +void playVideo(char* dirname, int imgCount); + +#endif /* LOADMENU_H */ diff --git a/Firmware_V3/include/mainmenu.h b/Firmware_V3/include/mainmenu.h new file mode 100644 index 0000000..d77c8ee --- /dev/null +++ b/Firmware_V3/include/mainmenu.h @@ -0,0 +1,51 @@ +/* +* +* MAIN MENU - Display the main menu with icons +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef MAINMENU_H +#define MAINMENU_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +bool calibrationRepeat(); +void calibrationScreen(bool firstStart = false); +bool colorMenu(); +void colorMenuString(int pos); +void drawMainMenu(byte pos); +void drawSelectionMenu(); +void hotColdChooserHandler(); +void hotColdChooser(); +bool hotColdColorMenu(); +void hotColdColorMenuString(int pos); +bool hotColdMenu(); +bool liveDispMenu(); +void liveDispMenuString(int pos); +void mainMenuBackground(); +void mainMenuHandler(byte* pos); +bool mainMenuSelect(byte pos, byte page); +void mainMenuSelection(char* selection); +void mainMenuTitle(char* title); +void mainMenu(); +bool massStoragePrompt(); +bool tempLimits(); +bool tempLimitsManualHandler(); +void tempLimitsManual(); +bool tempLimitsPresetSaveMenu(); +void tempLimitsPresetSaveString(int pos); +bool tempLimitsPresets(); +void tempLimitsPresetsString(int pos); +bool tempPointsMenu(); + +#endif /* MAINMENU_H */ + diff --git a/Firmware_V3/include/massstorage.h b/Firmware_V3/include/massstorage.h new file mode 100644 index 0000000..d67ed8a --- /dev/null +++ b/Firmware_V3/include/massstorage.h @@ -0,0 +1,27 @@ +/* +* +* MASS STORAGE - Mass storage mode to connect the internal storage to the PC +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef MASSSTORAGE_H +#define MASSSTORAGE_H + +#include + +/*########################## PUBLIC PROCEDURES ################################*/ + +void enterMassStorage(); +void setMassStorage(); +void checkMassStorage(); + +#endif /* MASSSTORAGE_H */ diff --git a/Firmware_V3/include/save.h b/Firmware_V3/include/save.h new file mode 100644 index 0000000..0651b25 --- /dev/null +++ b/Firmware_V3/include/save.h @@ -0,0 +1,31 @@ +/* +* +* SAVE - Save images and videos to the internal storage +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef SAVE_H +#define SAVE_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void createBMPFile(char* filename); +void createSDName(char* filename, boolean folder = false); +void frameFilename(char* filename, uint16_t count); +void imgSaveEnd(); +void imgSaveStart(); +void processVideoFrames(int framesCaptured, char* dirname); +void saveBuffer(char* filename); +void saveRawData(bool isImage, char* name, uint16_t framesCaptured = 0); +void saveVideoFrame(char* filename); + +#endif /* SAVE_H */ diff --git a/Firmware_V3/include/sdcard.h b/Firmware_V3/include/sdcard.h new file mode 100644 index 0000000..bdb155e --- /dev/null +++ b/Firmware_V3/include/sdcard.h @@ -0,0 +1,39 @@ +/* +* +* SD Card - Methods to access the internal SD storage +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef SD_H +#define SD_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +bool beginSD(); +void clearCache(uint8_t addSig); +void clearFatDir(uint32_t bgn, uint32_t count); +void dateTime(uint16_t* date, uint16_t* time); +bool formatCard(); +void formatFAT16(); +void formatFAT32(); +uint32_t getCardSize(); +uint32_t getSDSpace(); +void initSD(); +uint16_t lbnToCylinder(uint32_t lbn); +uint8_t lbnToHead(uint32_t lbn); +uint8_t lbnToSector(uint32_t lbn); +void refreshFreeSpace(); +uint32_t volSerialNumber(); +uint8_t writeCache(uint32_t lbn); +void writeMbr(); + +#endif /* SD_H */ diff --git a/Firmware_V3/include/settingsmenu.h b/Firmware_V3/include/settingsmenu.h new file mode 100644 index 0000000..7a87fac --- /dev/null +++ b/Firmware_V3/include/settingsmenu.h @@ -0,0 +1,46 @@ +/* +* +* SETTINGS MENU - Adjust different on-device settings +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef SETTINGSMENU_H +#define SETTINGSMENU_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void batteryGauge(); +void convertImageMenu(bool firstStart = false); +void dateMenuHandler(bool firstStart = false); +void dateMenu(bool firstStart = false); +void dayMenu(bool firstStart); +void displayMenuHandler(); +void displayMenu(); +void formatStorage(); +void hourMenu(bool firstStart); +void minuteMenu(bool firstStart); +void monthMenu(bool firstStart); +void generalMenuHandler(); +void generalMenu(); +void rotateDisplayMenu(bool firstStart = false); +void screenTimeoutMenu(); +void secondMenu(bool firstStart); +void settingsMenuHandler(); +void settingsMenu(); +void hardwareMenuHandler(); +void hardwareMenu(); +void tempFormatMenu(bool firstStart = false); +void timeMenuHandler(bool firstStart = false); +void timeMenu(bool firstStart = false); +void yearMenu(bool firstStart); + +#endif /* SETTINGSMENU_H */ diff --git a/Firmware_V3/include/temperature.h b/Firmware_V3/include/temperature.h new file mode 100644 index 0000000..efa8828 --- /dev/null +++ b/Firmware_V3/include/temperature.h @@ -0,0 +1,29 @@ +/* +* +* TEMPERATURE - Functions to convert Lepton raw values to absolute temperatures and back +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef CALIBRATION_H +#define CALIBRATION_H + +#include + +/*########################## PUBLIC PROCEDURES ################################*/ + +uint16_t calcAverage(); +float rawToTemp(uint16_t rawValue); +float celciusToFahrenheit(float Tc); +float fahrenheitToCelcius(float Tf); +uint16_t tempToRaw(float temp); + +#endif /* CALIBRATION_H */ diff --git a/Firmware_V3/include/thermal.h b/Firmware_V3/include/thermal.h new file mode 100644 index 0000000..a3e1ce7 --- /dev/null +++ b/Firmware_V3/include/thermal.h @@ -0,0 +1,32 @@ +/* +* +* THERMAL - Main functions in the live mode +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef THERMAL_H +#define THERMAL_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void buttonIRQ(); +void changeColorScheme(byte* pos); +void changeDisplayOptions(byte* pos); +void liveModeInit(); +void liveMode(); +void longTouchHandler(); +void selectColorScheme(); +void showColorBar(); +void showImage(); +void touchIRQ(); + +#endif /* THERMAL_H */ diff --git a/Firmware_V3/include/touchscreen.h b/Firmware_V3/include/touchscreen.h new file mode 100644 index 0000000..f4833ba --- /dev/null +++ b/Firmware_V3/include/touchscreen.h @@ -0,0 +1,40 @@ +/* +* +* Touchscreen - FT6206 or XPT2046 controller +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef TOUCHSCREEN_H +#define TOUCHSCREEN_H + +/*################# PUBLIC CONSTANTS, VARIABLES & DATA TYPES ##################*/ + +class TS_Point { +public: + TS_Point(void) : x(0), y(0), z(0) {} + TS_Point(int16_t x, int16_t y, int16_t z) : x(x), y(y), z(z) {} + bool operator==(TS_Point p) { return ((p.x == x) && (p.y == y) && (p.z == z)); } + bool operator!=(TS_Point p) { return ((p.x != x) || (p.y != y) || (p.z != z)); } + int16_t x, y, z; +}; + +extern volatile bool touch_capacitive; + +/*########################## PUBLIC PROCEDURES ################################*/ + +TS_Point touch_getPoint(); +void touch_init(); +void touch_setRotation(bool rotated); +volatile bool touch_touched(); + + +#endif /* TOUCHSCREEN_H */ diff --git a/Firmware_V3/include/videomenu.h b/Firmware_V3/include/videomenu.h new file mode 100644 index 0000000..803b9d7 --- /dev/null +++ b/Firmware_V3/include/videomenu.h @@ -0,0 +1,29 @@ +/* +* +* VIDEO MENU - Record single frames or time interval videos +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +#ifndef VIDEOMENU_H +#define VIDEOMENU_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +void videoCaptureInterval(int16_t* remainingTime, int* framesCaptured); +void videoCaptureNormal(int* framesCaptured); +void videoCapture(); +bool videoIntervalChooser(); +bool videoIntervalHandler(byte* pos); +void videoIntervalString(int pos); +void videoMode(); + +#endif /* VIDEOMENU_H */ diff --git a/Firmware_V3/include/xpt2046_touchscreen.h b/Firmware_V3/include/xpt2046_touchscreen.h new file mode 100644 index 0000000..d0b4ece --- /dev/null +++ b/Firmware_V3/include/xpt2046_touchscreen.h @@ -0,0 +1,39 @@ +/* + * + * FT6206 Touch controller + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +#ifndef XPT2046_TOUCHSCREEN_H +#define XPT2046_TOUCHSCREEN_H + +/*########################## PUBLIC PROCEDURES ################################*/ + +class XPT2046_Touchscreen { +public: + XPT2046_Touchscreen(); + bool begin(); + TS_Point getPoint(); + bool touched(); + void readData(uint16_t *x, uint16_t *y, uint8_t *z); + bool bufferEmpty(); + uint8_t bufferSize() { return 1; } + bool isrWake; + bool rotated = false; +private: + void update(); + uint8_t csPin, tirqPin; + int16_t xraw, yraw, zraw; + uint32_t msraw; +}; + +#endif /* XPT2046_TOUCHSCREEN_H */ diff --git a/Firmware_V3/lib/ADC/ADC.cpp b/Firmware_V3/lib/ADC/ADC.cpp new file mode 100644 index 0000000..1980bbc --- /dev/null +++ b/Firmware_V3/lib/ADC/ADC.cpp @@ -0,0 +1,1105 @@ +/* Teensy 4.x, 3.x, LC ADC library + * https://github.com/pedvide/ADC + * Copyright (c) 2020 Pedro Villanueva + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* ADC.cpp: Implements the control of one or more ADC modules of Teensy 3.x, LC + * + */ + +#include "ADC.h" + +// translate pin number to SC1A nomenclature and viceversa +// we need to create this static const arrays so that we can assign the "normal arrays" to the correct one +// depending on which ADC module we will be. +/* channel2sc1aADCx converts a pin number to their value for the SC1A register, for the ADC0 and ADC1 +* numbers with +ADC_SC1A_PIN_MUX (128) means those pins use mux a, the rest use mux b. +* numbers with +ADC_SC1A_PIN_DIFF (64) means it's also a differential pin (treated also in the channel2sc1a_diff_ADCx) +* For diff_table_ADCx, +ADC_SC1A_PIN_PGA means the pin can use PGA on that ADC +*/ + +///////// ADC0 +#if defined(ADC_TEENSY_3_0) +const uint8_t ADC::channel2sc1aADC0[] = { + // new version, gives directly the sc1a number. 0x1F=31 deactivates the ADC. + 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, 0, 19, 3, 21, // 0-13, we treat them as A0-A13 + 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, // 14-23 (A0-A9) + 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, // 24-33 + 0 + ADC_SC1A_PIN_DIFF, 19 + ADC_SC1A_PIN_DIFF, 3 + ADC_SC1A_PIN_DIFF, 21 + ADC_SC1A_PIN_DIFF, // 34-37 (A10-A13) + 26, 22, 23, 27, 29, 30 // 38-43: temp. sensor, VREF_OUT, A14, bandgap, VREFH, VREFL. A14 isn't connected to anything in Teensy 3.0. +}; +#elif defined(ADC_TEENSY_3_1) // the only difference with 3.0 is that A13 is not connected to ADC0 and that T3.1 has PGA. +const uint8_t ADC::channel2sc1aADC0[] = { + // new version, gives directly the sc1a number. 0x1F=31 deactivates the ADC. + 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, 0, 19, 3, 31, // 0-13, we treat them as A0-A13 + 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, // 14-23 (A0-A9) + 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, // 24-33 + 0 + ADC_SC1A_PIN_DIFF, 19 + ADC_SC1A_PIN_DIFF, 3 + ADC_SC1A_PIN_DIFF, 31 + ADC_SC1A_PIN_DIFF, // 34-37 (A10-A13) + 26, 22, 23, 27, 29, 30 // 38-43: temp. sensor, VREF_OUT, A14, bandgap, VREFH, VREFL. A14 isn't connected to anything in Teensy 3.0. +}; +#elif defined(ADC_TEENSY_LC) +// Teensy LC +const uint8_t ADC::channel2sc1aADC0[] = { + // new version, gives directly the sc1a number. 0x1F=31 deactivates the ADC. + 5, 14, 8, 9, 13, 12, 6, 7, 15, 11, 0, 4 + ADC_SC1A_PIN_MUX, 23, 31, // 0-13, we treat them as A0-A12 + A13= doesn't exist + 5, 14, 8, 9, 13, 12, 6, 7, 15, 11, // 14-23 (A0-A9) + 0 + ADC_SC1A_PIN_DIFF, 4 + ADC_SC1A_PIN_MUX + ADC_SC1A_PIN_DIFF, 23, 31, 31, 31, 31, 31, 31, 31, // 24-33 ((A10-A12) + nothing), A11 uses mux a + 31, 31, 31, 31, // 34-37 nothing + 26, 27, 31, 27, 29, 30 // 38-43: temp. sensor, , , bandgap, VREFH, VREFL. +}; +#elif defined(ADC_TEENSY_3_5) +const uint8_t ADC::channel2sc1aADC0[] = { + // new version, gives directly the sc1a number. 0x1F=31 deactivates the ADC. + 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, 3, 31, 31, 31, // 0-13, we treat them as A0-A13 + 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, // 14-23 (A0-A9) + 26, 27, 29, 30, 31, 31, 31, // 24-30: Temp_Sensor, bandgap, VREFH, VREFL. + 31, 31, 17, 18, // 31-34 A12(ADC1), A13(ADC1), A14, A15 + 31, 31, 31, 31, 31, 31, 31, 31, 31, // 35-43 + 31, 31, 31, 31, 31, 31, 31, 31, 31, // 44-52 + 31, 31, 31, 31, 31, 31, 31, 31, 31, // 53-61 + 31, 31, 3 + ADC_SC1A_PIN_DIFF, 31 + ADC_SC1A_PIN_DIFF, 23, 31, 1, 31 // 62-69 64: A10, 65: A11 (NOT CONNECTED), 66: A21, 68: A25 (no diff) +}; +#elif defined(ADC_TEENSY_3_6) +const uint8_t ADC::channel2sc1aADC0[] = { + // new version, gives directly the sc1a number. 0x1F=31 deactivates the ADC. + 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, 3, 31, 31, 31, // 0-13, we treat them as A0-A13 + 5, 14, 8, 9, 13, 12, 6, 7, 15, 4, // 14-23 (A0-A9) + 26, 27, 29, 30, 31, 31, 31, // 24-30: Temp_Sensor, bandgap, VREFH, VREFL. + 31, 31, 17, 18, // 31-34 A12(ADC1), A13(ADC1), A14, A15 + 31, 31, 31, 31, 31, 31, 31, 31, 31, // 35-43 + 31, 31, 31, 31, 31, 31, 31, 31, 31, // 44-52 + 31, 31, 31, 31, 31, 31, 31, 31, 31, // 53-61 + 31, 31, 3 + ADC_SC1A_PIN_DIFF, 31 + ADC_SC1A_PIN_DIFF, 23, 31 // 62-67 64: A10, 65: A11 (NOT CONNECTED), 66: A21, 67: A22(ADC1) +}; +#elif defined(ADC_TEENSY_4_0) +const uint8_t ADC::channel2sc1aADC0[] = { + // new version, gives directly the sc1a number. 0x1F=31 deactivates the ADC. + 7, 8, 12, 11, 6, 5, 15, 0, 13, 14, 1, 2, 31, 31, // 0-13, we treat them as A0-A13 + 7, 8, 12, 11, 6, 5, 15, 0, 13, 14, // 14-23 (A0-A9) + 1, 2, 31, 31 // A10, A11, A12, A13 +}; +#elif defined(ADC_TEENSY_4_1) +const uint8_t ADC::channel2sc1aADC0[] = { + // new version, gives directly the sc1a number. 0x1F=31 deactivates the ADC. + 7, 8, 12, 11, 6, 5, 15, 0, 13, 14, 1, 2, 31, 31, // 0-13, we treat them as A0-A13 + 7, 8, 12, 11, 6, 5, 15, 0, 13, 14, // 14-23 (A0-A9) + 1, 2, 31, 31, // A10, A11, A12, A13 + 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, // + 31, 31, 9, 10 // A14, A15, A16, A17 +}; +#endif // defined + +///////// ADC1 +#if defined(ADC_TEENSY_3_1) +const uint8_t ADC::channel2sc1aADC1[] = { + // new version, gives directly the sc1a number. 0x1F=31 deactivates the ADC. + 31, 31, 8, 9, 31, 31, 31, 31, 31, 31, 3, 31, 0, 19, // 0-13, we treat them as A0-A13 + 31, 31, 8, 9, 31, 31, 31, 31, 31, 31, // 14-23 (A0-A9) + 31, 31, // 24,25 are digital only pins + 5 + ADC_SC1A_PIN_MUX, 5, 4, 6, 7, 4 + ADC_SC1A_PIN_MUX, 31, 31, // 26-33 26=5a, 27=5b, 28=4b, 29=6b, 30=7b, 31=4a, 32,33 are digital only + 3 + ADC_SC1A_PIN_DIFF, 31 + ADC_SC1A_PIN_DIFF, 0 + ADC_SC1A_PIN_DIFF, 19 + ADC_SC1A_PIN_DIFF, // 34-37 (A10-A13) A11 isn't connected. + 26, 18, 31, 27, 29, 30 // 38-43: temp. sensor, VREF_OUT, A14 (not connected), bandgap, VREFH, VREFL. +}; +#elif defined(ADC_TEENSY_3_5) +const uint8_t ADC::channel2sc1aADC1[] = { + // new version, gives directly the sc1a number. 0x1F=31 deactivates the ADC. + 31, 31, 8, 9, 31, 31, 31, 31, 31, 31, 31, 19, 14, 15, // 0-13, we treat them as A0-A13 + 31, 31, 8, 9, 31, 31, 31, 31, 31, 31, // 14-23 (A0-A9) + 26, 27, 29, 30, 18, 31, 31, // 24-30: Temp_Sensor, bandgap, VREFH, VREFL, VREF_OUT + 14, 15, 31, 31, 4, 5, 6, 7, 17, // 31-39 A12-A20 + 31, 31, 31, 31, // 40-43 + 31, 31, 31, 31, 31, 10, 11, 31, 31, // 44-52, 49: A23, 50: A24 + 31, 31, 31, 31, 31, 31, 31, 31, 31, // 53-61 + 31, 31, 0 + ADC_SC1A_PIN_DIFF, 19 + ADC_SC1A_PIN_DIFF, 31, 23, 31, 1 // 62-69 64: A10, 65: A11, 67: A22, 69: A26 (not diff) +}; +#elif defined(ADC_TEENSY_3_6) +const uint8_t ADC::channel2sc1aADC1[] = { + // new version, gives directly the sc1a number. 0x1F=31 deactivates the ADC. + 31, 31, 8, 9, 31, 31, 31, 31, 31, 31, 31, 19, 14, 15, // 0-13, we treat them as A0-A13 + 31, 31, 8, 9, 31, 31, 31, 31, 31, 31, // 14-23 (A0-A9) + 26, 27, 29, 30, 18, 31, 31, // 24-30: Temp_Sensor, bandgap, VREFH, VREFL, VREF_OUT + 14, 15, 31, 31, 4, 5, 6, 7, 17, // 31-39 A12-A20 + 31, 31, 31, 23, // 40-43: A10(ADC0), A11(ADC0), A21, A22 + 31, 31, 31, 31, 31, 10, 11, 31, 31, // 44-52, 49: A23, 50: A24 + 31, 31, 31, 31, 31, 31, 31, 31, 31, // 53-61 + 31, 31, 0 + ADC_SC1A_PIN_DIFF, 19 + ADC_SC1A_PIN_DIFF, 31, 23 // 61-67 64: A10, 65: A11, 66: A21(ADC0), 67: A22 +}; +#elif defined(ADC_TEENSY_4_0) +const uint8_t ADC::channel2sc1aADC1[] = { + // new version, gives directly the sc1a number. 0x1F=31 deactivates the ADC. + 7, 8, 12, 11, 6, 5, 15, 0, 13, 14, 31, 31, 3, 4, // 0-13, we treat them as A0-A13 + 7, 8, 12, 11, 6, 5, 15, 0, 13, 14, // 14-23 (A0-A9) + 31, 31, 3, 4 // A10, A11, A12, A13 +}; +#elif defined(ADC_TEENSY_4_1) +const uint8_t ADC::channel2sc1aADC1[] = { + // new version, gives directly the sc1a number. 0x1F=31 deactivates the ADC. + 7, 8, 12, 11, 6, 5, 15, 0, 13, 14, 31, 31, 3, 4, // 0-13, we treat them as A0-A13 + 7, 8, 12, 11, 6, 5, 15, 0, 13, 14, // 14-23 (A0-A9) + 31, 31, 3, 4, // A10, A11, A12, A13 + 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, // + 1, 2, 9, 10 // A14, A15, A16, A17 +}; +#endif + +#if defined(ADC_TEENSY_3_1) // Teensy 3.1 +const ADC_Module::ADC_NLIST ADC::diff_table_ADC0[] = { + {A10, 0 + ADC_SC1A_PIN_PGA}, {A12, 3}}; +const ADC_Module::ADC_NLIST ADC::diff_table_ADC1[] = { + {A10, 3}, {A12, 0 + ADC_SC1A_PIN_PGA}}; +#elif defined(ADC_TEENSY_3_0) // Teensy 3.0 +const ADC_Module::ADC_NLIST ADC::diff_table_ADC0[] = { + {A10, 0}, {A12, 3}}; +#elif defined(ADC_TEENSY_LC) // Teensy LC +const ADC_Module::ADC_NLIST ADC::diff_table_ADC0[] = { + {A10, 0}}; +#elif defined(ADC_TEENSY_3_5) || defined(ADC_TEENSY_3_6) // Teensy 3.6// Teensy 3.5 +const ADC_Module::ADC_NLIST ADC::diff_table_ADC0[] = { + {A10, 3}}; +const ADC_Module::ADC_NLIST ADC::diff_table_ADC1[] = { + {A10, 0}}; +#elif defined(ADC_TEENSY_4) +#endif + +// translate SC1A to pin number +///////// ADC0 +#if defined(ADC_TEENSY_3_0) || defined(ADC_TEENSY_3_1) +const uint8_t ADC::sc1a2channelADC0[] = { + // new version, gives directly the pin number + 34, 0, 0, 36, 23, 14, 20, 21, 16, 17, 0, 0, 19, 18, // 0-13 + 15, 22, 23, 0, 0, 35, 0, 37, // 14-21 + 39, 40, 0, 0, 38, 41, 42, 43, // VREF_OUT, A14, temp. sensor, bandgap, VREFH, VREFL. + 0 // 31 means disabled, but just in case +}; +#elif defined(ADC_TEENSY_LC) +// Teensy LC +const uint8_t ADC::sc1a2channelADC0[] = { + // new version, gives directly the pin number + 24, 0, 0, 0, 25, 14, 20, 21, 16, 17, 0, 23, 19, 18, // 0-13 + 15, 22, 23, 0, 0, 0, 0, 0, // 14-21 + 26, 0, 0, 0, 38, 41, 0, 42, 43, // A12, temp. sensor, bandgap, VREFH, VREFL. + 0 // 31 means disabled, but just in case +}; +#elif defined(ADC_TEENSY_3_5) || defined(ADC_TEENSY_3_6) +const uint8_t ADC::sc1a2channelADC0[] = { + // new version, gives directly the pin number + 0, 68, 0, 64, 23, 14, 20, 21, 16, 17, 0, 0, 19, 18, // 0-13 + 15, 22, 0, 33, 34, 0, 0, 0, // 14-21 + 0, 66, 0, 0, 70, 0, 0, 0, // 22-29 + 0 // 31 means disabled, but just in case +}; +#elif defined(ADC_TEENSY_4_0) +const uint8_t ADC::sc1a2channelADC0[] = { + // new version, gives directly the pin number + 21, 24, 25, 0, 0, 19, 18, 14, 15, 0, 0, 17, 16, 22, + 23, 20, 0, 0, 0, 0, 0, 0, //14-21 + 0, 0, 0, 0, 0, 0 //22-27 +}; +#elif defined(ADC_TEENSY_4_1) +const uint8_t ADC::sc1a2channelADC0[] = { + // new version, gives directly the pin number + 21, 24, 25, 0, 0, 19, 18, 14, 15, 0, 0, 17, 16, 22, + 23, 20, 0, 0, 0, 0, 0, 0, //14-21 + 0, 0, 0, 0, 0, 0 //22-27 +}; +#endif // defined + +///////// ADC1 +#if defined(ADC_TEENSY_3_1) +const uint8_t ADC::sc1a2channelADC1[] = { // new version, gives directly the pin number + 36, 0, 0, 34, 28, 26, 29, 30, 16, 17, 0, 0, 0, 0, // 0-13. 5a=26, 5b=27, 4b=28, 4a=31 + 0, 0, 0, 0, 39, 37, 0, 0, // 14-21 + 0, 0, 0, 0, 38, 41, 0, 42, // 22-29. VREF_OUT, A14, temp. sensor, bandgap, VREFH, VREFL. + 43}; +#elif defined(ADC_TEENSY_3_5) || defined(ADC_TEENSY_3_6) +const uint8_t ADC::sc1a2channelADC1[] = { // new version, gives directly the pin number + 0, 69, 0, 0, 35, 36, 37, 38, 0, 0, 49, 50, 0, 0, // 0-13. + 31, 32, 0, 39, 71, 65, 0, 0, // 14-21 + 0, 67, 0, 0, 0, 0, 0, 0, // 22-29. + 0}; +#elif defined(ADC_TEENSY_4_0) +const uint8_t ADC::sc1a2channelADC1[] = { + // new version, gives directly the pin number + 21, 0, 0, 26, 27, 19, 18, 14, 15, 0, 0, 17, 16, 22, // 0-13 + 23, 20, 0, 0, 0, 0, 0, 0, //14-21 + 0, 0, 0, 0, 0, 0 //22-27 +}; +#elif defined(ADC_TEENSY_4_1) +const uint8_t ADC::sc1a2channelADC1[] = { + // new version, gives directly the pin number + 21, 0, 0, 26, 27, 19, 18, 14, 15, 0, 0, 17, 16, 22, // 0-13 + 23, 20, 0, 0, 0, 0, 0, 0, //14-21 + 0, 0, 0, 0, 0, 0 //22-27 +}; +#endif + +// Constructor +ADC::ADC() : // awkward initialization so there are no -Wreorder warnings +#if ADC_DIFF_PAIRS > 0 + adc0_obj(0, channel2sc1aADC0, diff_table_ADC0, ADC0_START) +#ifdef ADC_DUAL_ADCS + , + adc1_obj(1, channel2sc1aADC1, diff_table_ADC1, ADC1_START) +#endif +#else + adc0_obj(0, channel2sc1aADC0, ADC0_START) +#ifdef ADC_DUAL_ADCS + , + adc1_obj(1, channel2sc1aADC1, ADC1_START) +#endif +#endif +{ + //ctor + + //digitalWriteFast(LED_BUILTIN, HIGH); +} + +/* Returns the analog value of the pin. +* It waits until the value is read and then returns the result. +* If a comparison has been set up and fails, it will return ADC_ERROR_VALUE. +* This function is interrupt safe, so it will restore the adc to the state it was before being called +* If more than one ADC exists, it will select the module with less workload, you can force a selection using +* adc_num. If you select ADC1 in Teensy 3.0 it will return ADC_ERROR_VALUE. +*/ +int ADC::analogRead(uint8_t pin, int8_t adc_num) +{ +#ifdef ADC_SINGLE_ADC + return adc0->analogRead(pin); // use ADC0 +#else + /* Teensy 3.1 + */ + if (adc_num == -1) + { // use no ADC in particular + // check which ADC can read the pin + bool adc0Pin = adc0->checkPin(pin); + bool adc1Pin = adc1->checkPin(pin); + + if (adc0Pin && adc1Pin) + { // Both ADCs + if ((adc0->num_measurements) > (adc1->num_measurements)) + { // use the ADC with less workload + return adc1->analogRead(pin); + } + else + { + return adc0->analogRead(pin); + } + } + else if (adc0Pin) + { // ADC0 + return adc0->analogRead(pin); + } + else if (adc1Pin) + { // ADC1 + return adc1->analogRead(pin); + } + else + { // pin not valid in any ADC + adc0->fail_flag |= ADC_ERROR::WRONG_PIN; + adc1->fail_flag |= ADC_ERROR::WRONG_PIN; + return ADC_ERROR_VALUE; // all others are invalid + } + } + else if (adc_num == 0) + { // user wants ADC0 + return adc0->analogRead(pin); + } + else if (adc_num == 1) + { // user wants ADC 1 + return adc1->analogRead(pin); + } + adc0->fail_flag |= ADC_ERROR::OTHER; + return ADC_ERROR_VALUE; +#endif +} + +#if ADC_DIFF_PAIRS > 0 +/* Reads the differential analog value of two pins (pinP - pinN). +* It waits until the value is read and then returns the result. +* If a comparison has been set up and fails, it will return ADC_ERROR_VALUE. +* \param pinP must be A10 or A12. +* \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12). +* Other pins will return ADC_ERROR_VALUE. +* This function is interrupt safe, so it will restore the adc to the state it was before being called +* If more than one ADC exists, it will select the module with less workload, you can force a selection using +* adc_num. If you select ADC1 in Teensy 3.0 it will return ADC_ERROR_VALUE. +*/ +int ADC::analogReadDifferential(uint8_t pinP, uint8_t pinN, int8_t adc_num) +{ + +#ifdef ADC_SINGLE_ADC + return adc0->analogReadDifferential(pinP, pinN); // use ADC0 +#else + /* Teensy 3.1 + */ + if (adc_num == -1) + { // use no ADC in particular + // check which ADC can read the pin + bool adc0Pin = adc0->checkDifferentialPins(pinP, pinN); + bool adc1Pin = adc1->checkDifferentialPins(pinP, pinN); + + if (adc0Pin && adc1Pin) + { // Both ADCs + if ((adc0->num_measurements) > (adc1->num_measurements)) + { // use the ADC with less workload + return adc1->analogReadDifferential(pinP, pinN); + } + else + { + return adc0->analogReadDifferential(pinP, pinN); + } + } + else if (adc0Pin) + { // ADC0 + return adc0->analogReadDifferential(pinP, pinN); + } + else if (adc1Pin) + { // ADC1 + return adc1->analogReadDifferential(pinP, pinN); + } + else + { // pins not valid in any ADC + adc0->fail_flag |= ADC_ERROR::WRONG_PIN; + adc1->fail_flag |= ADC_ERROR::WRONG_PIN; + return ADC_ERROR_VALUE; // all others are invalid + } + } + else if (adc_num == 0) + { // user wants ADC0 + return adc0->analogReadDifferential(pinP, pinN); + } + else if (adc_num == 1) + { // user wants ADC 1 + return adc1->analogReadDifferential(pinP, pinN); + } + adc0->fail_flag |= ADC_ERROR::OTHER; + return ADC_ERROR_VALUE; +#endif +} +#endif + +// Starts an analog measurement on the pin and enables interrupts. +/* It returns immediately, get value with readSingle(). +* If the pin is incorrect it returns ADC_ERROR_VALUE +* This function is interrupt safe. The ADC interrupt will restore the adc to its previous settings and +* restart the adc if it stopped a measurement. If you modify the adc_isr then this won't happen. +*/ +bool ADC::startSingleRead(uint8_t pin, int8_t adc_num) +{ +#ifdef ADC_SINGLE_ADC + return adc0->startSingleRead(pin); // use ADC0 +#else + /* Teensy 3.1 + */ + if (adc_num == -1) + { // use no ADC in particular + // check which ADC can read the pin + bool adc0Pin = adc0->checkPin(pin); + bool adc1Pin = adc1->checkPin(pin); + + if (adc0Pin && adc1Pin) + { // Both ADCs + + if ((adc0->num_measurements) > (adc1->num_measurements)) + { // use the ADC with less workload + return adc1->startSingleRead(pin); + } + else + { + return adc0->startSingleRead(pin); + } + } + else if (adc0Pin) + { // ADC0 + return adc0->startSingleRead(pin); + } + else if (adc1Pin) + { // ADC1 + return adc1->startSingleRead(pin); + } + else + { // pin not valid in any ADC + adc0->fail_flag |= ADC_ERROR::WRONG_PIN; + adc1->fail_flag |= ADC_ERROR::WRONG_PIN; + return false; // all others are invalid + } + } + else if (adc_num == 0) + { // user wants ADC0 + return adc0->startSingleRead(pin); + } + else if (adc_num == 1) + { // user wants ADC 1 + return adc1->startSingleRead(pin); + } + adc0->fail_flag |= ADC_ERROR::OTHER; + return false; +#endif +} + +#if ADC_DIFF_PAIRS > 0 +// Start a differential conversion between two pins (pinP - pinN) and enables interrupts. +/* It returns inmediately, get value with readSingle(). +* \param pinP must be A10 or A12. +* \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12). +* Other pins will return ADC_ERROR_DIFF_VALUE. +* This function is interrupt safe. The ADC interrupt will restore the adc to its previous settings and +* restart the adc if it stopped a measurement. If you modify the adc_isr then this won't happen. +*/ +bool ADC::startSingleDifferential(uint8_t pinP, uint8_t pinN, int8_t adc_num) +{ +#ifdef ADC_SINGLE_ADC + return adc0->startSingleDifferential(pinP, pinN); // use ADC0 +#else + /* Teensy 3.1 + */ + if (adc_num == -1) + { // use no ADC in particular + // check which ADC can read the pin + bool adc0Pin = adc0->checkDifferentialPins(pinP, pinN); + bool adc1Pin = adc1->checkDifferentialPins(pinP, pinN); + + if (adc0Pin && adc1Pin) + { // Both ADCs + if ((adc0->num_measurements) > (adc1->num_measurements)) + { // use the ADC with less workload + return adc1->startSingleDifferential(pinP, pinN); + } + else + { + return adc0->startSingleDifferential(pinP, pinN); + } + } + else if (adc0Pin) + { // ADC0 + return adc0->startSingleDifferential(pinP, pinN); + } + else if (adc1Pin) + { // ADC1 + return adc1->startSingleDifferential(pinP, pinN); + } + else + { // pins not valid in any ADC + adc0->fail_flag |= ADC_ERROR::WRONG_PIN; + adc1->fail_flag |= ADC_ERROR::WRONG_PIN; + return false; // all others are invalid + } + } + else if (adc_num == 0) + { // user wants ADC0 + return adc0->startSingleDifferential(pinP, pinN); + } + else if (adc_num == 1) + { // user wants ADC 1 + return adc1->startSingleDifferential(pinP, pinN); + } + adc0->fail_flag |= ADC_ERROR::OTHER; + return false; +#endif +} +#endif + +// Reads the analog value of a single conversion. +/* Set the conversion with with startSingleRead(pin) or startSingleDifferential(pinP, pinN). +* \return the converted value. +*/ +int ADC::readSingle(int8_t adc_num) +{ +#ifdef ADC_SINGLE_ADC + return adc0->readSingle(); +#else + if (adc_num == 1) + { // user wants ADC 1, do nothing if it's a Teensy 3.0 + return adc1->readSingle(); + } + return adc0->readSingle(); +#endif +} + +// Starts continuous conversion on the pin. +/* It returns as soon as the ADC is set, use analogReadContinuous() to read the value. +*/ +bool ADC::startContinuous(uint8_t pin, int8_t adc_num) +{ +#ifdef ADC_SINGLE_ADC + return adc0->startContinuous(pin); // use ADC0 +#else + /* Teensy 3.1 + */ + if (adc_num == -1) + { // use no ADC in particular + // check which ADC can read the pin + bool adc0Pin = adc0->checkPin(pin); + bool adc1Pin = adc1->checkPin(pin); + + if (adc0Pin && adc1Pin) + { // Both ADCs + if ((adc0->num_measurements) > (adc1->num_measurements)) + { // use the ADC with less workload + return adc1->startContinuous(pin); + } + else + { + return adc0->startContinuous(pin); + } + } + else if (adc0Pin) + { // ADC0 + return adc0->startContinuous(pin); + } + else if (adc1Pin) + { // ADC1 + return adc1->startContinuous(pin); + } + else + { // pin not valid in any ADC + adc0->fail_flag |= ADC_ERROR::WRONG_PIN; + adc1->fail_flag |= ADC_ERROR::WRONG_PIN; + return false; // all others are invalid + } + } + else if (adc_num == 0) + { // user wants ADC0 + return adc0->startContinuous(pin); + } + else if (adc_num == 1) + { // user wants ADC 1 + return adc1->startContinuous(pin); + } + adc0->fail_flag |= ADC_ERROR::OTHER; + return false; +#endif +} + +#if ADC_DIFF_PAIRS > 0 +// Starts continuous conversion between the pins (pinP-pinN). +/* It returns as soon as the ADC is set, use analogReadContinuous() to read the value. +* \param pinP must be A10 or A12. +* \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12). +* Other pins will return ADC_ERROR_DIFF_VALUE. +*/ +bool ADC::startContinuousDifferential(uint8_t pinP, uint8_t pinN, int8_t adc_num) +{ +#ifdef ADC_SINGLE_ADC + return adc0->startContinuousDifferential(pinP, pinN); // use ADC0 +#else + /* Teensy 3.1 + */ + if (adc_num == -1) + { // use no ADC in particular + // check which ADC can read the pin + bool adc0Pin = adc0->checkDifferentialPins(pinP, pinN); + bool adc1Pin = adc1->checkDifferentialPins(pinP, pinN); + + if (adc0Pin && adc1Pin) + { // Both ADCs + if ((adc0->num_measurements) > (adc1->num_measurements)) + { // use the ADC with less workload + return adc1->startContinuousDifferential(pinP, pinN); + } + else + { + return adc0->startContinuousDifferential(pinP, pinN); + } + } + else if (adc0Pin) + { // ADC0 + return adc0->startContinuousDifferential(pinP, pinN); + } + else if (adc1Pin) + { // ADC1 + return adc1->startContinuousDifferential(pinP, pinN); + } + else + { // pins not valid in any ADC + adc0->fail_flag |= ADC_ERROR::WRONG_PIN; + adc1->fail_flag |= ADC_ERROR::WRONG_PIN; + return false; // all others are invalid + } + } + else if (adc_num == 0) + { // user wants ADC0 + return adc0->startContinuousDifferential(pinP, pinN); + } + else if (adc_num == 1) + { // user wants ADC 1 + return adc1->startContinuousDifferential(pinP, pinN); + } + adc0->fail_flag |= ADC_ERROR::OTHER; + return false; +#endif +} +#endif + +//! Reads the analog value of a continuous conversion. +/** Set the continuous conversion with with analogStartContinuous(pin) or startContinuousDifferential(pinP, pinN). +* \return the last converted value. +* If single-ended and 16 bits it's necessary to typecast it to an unsigned type (like uint16_t), +* otherwise values larger than 3.3/2 V are interpreted as negative! +*/ +int ADC::analogReadContinuous(int8_t adc_num) +{ +#ifdef ADC_SINGLE_ADC + return adc0->analogReadContinuous(); +#else + if (adc_num == 1) + { // user wants ADC 1, do nothing if it's a Teensy 3.0 + return adc1->analogReadContinuous(); + } + return adc0->analogReadContinuous(); +#endif +} + +//! Stops continuous conversion +void ADC::stopContinuous(int8_t adc_num) +{ +#ifdef ADC_SINGLE_ADC + adc0->stopContinuous(); +#else + if (adc_num == 1) + { // user wants ADC 1, do nothing if it's a Teensy 3.0 + adc1->stopContinuous(); + return; + } + adc0->stopContinuous(); + return; +#endif +} + +//////////////// SYNCHRONIZED BLOCKING METHODS ////////////////// +///// ONLY FOR BOARDS WITH MORE THAN ONE ADC ///// +///////////////////////////////////////////////////////////////// + +#ifdef ADC_DUAL_ADCS + +/*Returns the analog values of both pins, measured at the same time by the two ADC modules. +* It waits until the value is read and then returns the result as a struct Sync_result, +* use Sync_result.result_adc0 and Sync_result.result_adc1. +* If a comparison has been set up and fails, it will return ADC_ERROR_VALUE in both fields of the struct. +*/ +ADC::Sync_result ADC::analogSynchronizedRead(uint8_t pin0, uint8_t pin1) +{ + Sync_result res = {ADC_ERROR_VALUE, ADC_ERROR_VALUE}; + + // check pins + if (!adc0->checkPin(pin0)) + { + adc0->fail_flag |= ADC_ERROR::WRONG_PIN; + return res; + } + if (!adc1->checkPin(pin1)) + { + adc1->fail_flag |= ADC_ERROR::WRONG_PIN; + return res; + } + + // check if we are interrupting a measurement, store setting if so. + // vars to save the current state of the ADC in case it's in use + ADC_Module::ADC_Config old_adc0_config = {}; + uint8_t wasADC0InUse = adc0->isConverting(); // is the ADC running now? + if (wasADC0InUse) + { // this means we're interrupting a conversion + // save the current conversion config, the adc isr will restore the adc + __disable_irq(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc0->saveConfig(&old_adc0_config); + __enable_irq(); + } + ADC_Module::ADC_Config old_adc1_config = {}; + uint8_t wasADC1InUse = adc1->isConverting(); // is the ADC running now? + if (wasADC1InUse) + { // this means we're interrupting a conversion + // save the current conversion config, the adc isr will restore the adc + __disable_irq(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc1->saveConfig(&old_adc1_config); + __enable_irq(); + } + + // no continuous mode + adc0->singleMode(); + adc1->singleMode(); + + // start both measurements + adc0->startReadFast(pin0); + adc1->startReadFast(pin1); + + // wait for both ADCs to finish + while ((adc0->isConverting()) || (adc1->isConverting())) + { // wait for both to finish + yield(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + } + + __disable_irq(); // make sure nothing interrupts this part + if (adc0->isComplete()) + { // conversion succeded + res.result_adc0 = adc0->readSingle(); + } + else + { // comparison was false + adc0->fail_flag |= ADC_ERROR::COMPARISON; + } + if (adc1->isComplete()) + { // conversion succeded + res.result_adc1 = adc1->readSingle(); + } + else + { // comparison was false + adc1->fail_flag |= ADC_ERROR::COMPARISON; + } + __enable_irq(); + + // if we interrupted a conversion, set it again + if (wasADC0InUse) + { + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc0->loadConfig(&old_adc0_config); + } + if (wasADC1InUse) + { + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc1->loadConfig(&old_adc1_config); + } + + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + + return res; +} + +#if ADC_DIFF_PAIRS > 0 +/*Returns the diff analog values of both sets of pins, measured at the same time by the two ADC modules. +* It waits until the value is read and then returns the result as a struct Sync_result, +* use Sync_result.result_adc0 and Sync_result.result_adc1. +* If a comparison has been set up and fails, it will return ADC_ERROR_VALUE in both fields of the struct. +*/ +ADC::Sync_result ADC::analogSynchronizedReadDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N) +{ + Sync_result res = {ADC_ERROR_VALUE, ADC_ERROR_VALUE}; + ; + + // check pins + if (!adc0->checkDifferentialPins(pin0P, pin0N)) + { + adc0->fail_flag |= ADC_ERROR::WRONG_PIN; + return res; // all others are invalid + } + if (!adc1->checkDifferentialPins(pin1P, pin1N)) + { + adc1->fail_flag |= ADC_ERROR::WRONG_PIN; + return res; // all others are invalid + } + + uint8_t resolution0 = adc0->getResolution(); + uint8_t resolution1 = adc1->getResolution(); + + // check if we are interrupting a measurement, store setting if so. + // vars to save the current state of the ADC in case it's in use + ADC_Module::ADC_Config old_adc0_config = {}; + uint8_t wasADC0InUse = adc0->isConverting(); // is the ADC running now? + if (wasADC0InUse) + { // this means we're interrupting a conversion + // save the current conversion config, the adc isr will restore the adc + __disable_irq(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc0->saveConfig(&old_adc0_config); + __enable_irq(); + } + ADC_Module::ADC_Config old_adc1_config = {}; + uint8_t wasADC1InUse = adc1->isConverting(); // is the ADC running now? + if (wasADC1InUse) + { // this means we're interrupting a conversion + // save the current conversion config, the adc isr will restore the adc + __disable_irq(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc1->saveConfig(&old_adc1_config); + __enable_irq(); + } + + // no continuous mode + adc0->singleMode(); + adc1->singleMode(); + + // start both measurements + adc0->startDifferentialFast(pin0P, pin0N); + adc1->startDifferentialFast(pin1P, pin1N); + + // wait for both ADCs to finish + while ((adc0->isConverting()) || (adc1->isConverting())) + { + yield(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + } + __disable_irq(); // make sure nothing interrupts this part + if (adc0->isComplete()) + { // conversion succeded + res.result_adc0 = adc0->readSingle(); + if (resolution0 == 16) + { // 16 bit differential is actually 15 bit + 1 bit sign + res.result_adc0 *= 2; // multiply by 2 as if it were really 16 bits, so that getMaxValue gives a correct value. + } + } + else + { // comparison was false + adc0->fail_flag |= ADC_ERROR::COMPARISON; + } + if (adc1->isComplete()) + { // conversion succeded + res.result_adc1 = adc1->readSingle(); + if (resolution1 == 16) + { // 16 bit differential is actually 15 bit + 1 bit sign + res.result_adc1 *= 2; // multiply by 2 as if it were really 16 bits, so that getMaxValue gives a correct value. + } + } + else + { // comparison was false + adc1->fail_flag |= ADC_ERROR::COMPARISON; + } + __enable_irq(); + + // if we interrupted a conversion, set it again + if (wasADC0InUse) + { + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc0->loadConfig(&old_adc0_config); + } + if (wasADC1InUse) + { + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc1->loadConfig(&old_adc1_config); + } + + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + + return res; +} +#endif + +/////////////// SYNCHRONIZED NON-BLOCKING METHODS ////////////// + +// Starts an analog measurement at the same time on the two ADC modules +/* It returns inmediately, get value with readSynchronizedSingle(). +* If the pin is incorrect it returns false +* If this function interrupts a measurement, it stores the settings in adc_config +*/ +bool ADC::startSynchronizedSingleRead(uint8_t pin0, uint8_t pin1) +{ + // check pins + if (!adc0->checkPin(pin0)) + { + adc0->fail_flag |= ADC_ERROR::WRONG_PIN; + return false; + } + if (!adc1->checkPin(pin1)) + { + adc1->fail_flag |= ADC_ERROR::WRONG_PIN; + return false; + } + + // check if we are interrupting a measurement, store setting if so. + adc0->adcWasInUse = adc0->isConverting(); // is the ADC running now? + if (adc0->adcWasInUse) + { // this means we're interrupting a conversion + // save the current conversion config, the adc isr will restore the adc + __disable_irq(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc0->saveConfig(&adc0->adc_config); + __enable_irq(); + } + adc1->adcWasInUse = adc1->isConverting(); // is the ADC running now? + if (adc1->adcWasInUse) + { // this means we're interrupting a conversion + // save the current conversion config, the adc isr will restore the adc + __disable_irq(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc1->saveConfig(&adc1->adc_config); + __enable_irq(); + } + + // no continuous mode + adc0->singleMode(); + adc1->singleMode(); + + // start both measurements + adc0->startReadFast(pin0); + adc1->startReadFast(pin1); + + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + return true; +} + +#if ADC_DIFF_PAIRS > 0 +// Start a differential conversion between two pins (pin0P - pin0N) and (pin1P - pin1N) +/* It returns inmediately, get value with readSynchronizedSingle(). +* \param pinP must be A10 or A12. +* \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12). +* Other pins will return false. +* If this function interrupts a measurement, it stores the settings in adc_config +*/ +bool ADC::startSynchronizedSingleDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N) +{ + + // check pins + if (!adc0->checkDifferentialPins(pin0P, pin0N)) + { + adc0->fail_flag |= ADC_ERROR::WRONG_PIN; + return false; // all others are invalid + } + if (!adc1->checkDifferentialPins(pin1P, pin1N)) + { + adc1->fail_flag |= ADC_ERROR::WRONG_PIN; + return false; // all others are invalid + } + + // check if we are interrupting a measurement, store setting if so. + adc0->adcWasInUse = adc0->isConverting(); // is the ADC running now? + if (adc0->adcWasInUse) + { // this means we're interrupting a conversion + // save the current conversion config, the adc isr will restore the adc + __disable_irq(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc0->saveConfig(&adc0->adc_config); + __enable_irq(); + } + adc1->adcWasInUse = adc1->isConverting(); // is the ADC running now? + if (adc1->adcWasInUse) + { // this means we're interrupting a conversion + // save the current conversion config, the adc isr will restore the adc + __disable_irq(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + adc1->saveConfig(&adc1->adc_config); + __enable_irq(); + } + + // no continuous mode + adc0->singleMode(); + adc1->singleMode(); + + // start both measurements + adc0->startDifferentialFast(pin0P, pin0N); + adc1->startDifferentialFast(pin1P, pin1N); + + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + + return true; +} +#endif + +// Reads the analog value of a single conversion. +/* +* \return the converted value. +*/ +ADC::Sync_result ADC::readSynchronizedSingle() +{ + ADC::Sync_result res; + + res.result_adc0 = adc0->readSingle(); + res.result_adc1 = adc1->readSingle(); + + return res; +} + +///////////// SYNCHRONIZED CONTINUOUS CONVERSION METHODS //////////// + +//! Starts a continuous conversion in both ADCs simultaneously +/** Use readSynchronizedContinuous to get the values +* +*/ +bool ADC::startSynchronizedContinuous(uint8_t pin0, uint8_t pin1) +{ + + // check pins + if (!adc0->checkPin(pin0)) + { + adc0->fail_flag |= ADC_ERROR::WRONG_PIN; + return false; + } + if (!adc1->checkPin(pin1)) + { + adc1->fail_flag |= ADC_ERROR::WRONG_PIN; + return false; + } + + adc0->continuousMode(); + adc1->continuousMode(); + + __disable_irq(); // both measurements should have a maximum delay of an instruction time + adc0->startReadFast(pin0); + adc1->startReadFast(pin1); + __enable_irq(); + + return true; +} + +#if ADC_DIFF_PAIRS > 0 +//! Starts a continuous differential conversion in both ADCs simultaneously +/** Use readSynchronizedContinuous to get the values +* +*/ +bool ADC::startSynchronizedContinuousDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N) +{ + + // check pins + if (!adc0->checkDifferentialPins(pin0P, pin0N)) + { + adc0->fail_flag |= ADC_ERROR::WRONG_PIN; + return false; // all others are invalid + } + if (!adc1->checkDifferentialPins(pin1P, pin1N)) + { + adc1->fail_flag |= ADC_ERROR::WRONG_PIN; + return false; // all others are invalid + } + + adc0->continuousMode(); + adc1->continuousMode(); + + __disable_irq(); + adc0->startDifferentialFast(pin0P, pin0N); + adc1->startDifferentialFast(pin1P, pin1N); + __enable_irq(); + + return true; +} +#endif + +//! Returns the values of both ADCs. +ADC::Sync_result ADC::readSynchronizedContinuous() +{ + ADC::Sync_result res; + + res.result_adc0 = adc0->analogReadContinuous(); + res.result_adc1 = adc1->analogReadContinuous(); + + return res; +} + +//! Stops synchronous continuous conversion +void ADC::stopSynchronizedContinuous() +{ + + adc0->stopContinuous(); + adc1->stopContinuous(); +} + +#endif diff --git a/Firmware_V3/lib/ADC/ADC.h b/Firmware_V3/lib/ADC/ADC.h new file mode 100644 index 0000000..a41681b --- /dev/null +++ b/Firmware_V3/lib/ADC/ADC.h @@ -0,0 +1,577 @@ +/* Teensy 4.x, 3.x, LC ADC library + * https://github.com/pedvide/ADC + * Copyright (c) 2020 Pedro Villanueva + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* TODO +* - Function to measure more that 1 pin consecutively (stream?) +* +* bugs: +* - comparison values in 16 bit differential mode are twice what they should be +*/ + +/*! \mainpage ADC + +Teensy 4.x, 3.x, LC ADC library + +This manual is divided in the following sections: +- \subpage adc_doc "ADC" +- \subpage adc_module "ADC Module" +- \subpage settings "Board settings" +- \subpage error "ADC error codes" +- \subpage util "ADC util" + +*/ + +/*! \page adc_doc ADC +Make Analog to Digital conversions using the ADC interface. +See the ADC class for all methods. +*/ + +#ifndef ADC_H +#define ADC_H + +#define ADC_0 0 +#define ADC_1 1 +//enum class ADC_NUM {ADC_0, ADC_1}; // too verbose, but it'd avoid some mistakes + +// include ADC module class +#include "ADC_Module.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** Class ADC: Controls the Teensy 3.x, 4 ADC +* +*/ + class ADC + { + protected: + private: + // ADCs objects + ADC_Module adc0_obj; +#ifdef ADC_DUAL_ADCS + ADC_Module adc1_obj; +#endif + + //! Number of ADC objects + const uint8_t num_ADCs = ADC_NUM_ADCS; + + public: + /** Default constructor */ + ADC(); + + // create both adc objects + + //! Object to control the ADC0 + ADC_Module *const adc0 = &adc0_obj; // adc object pointer +#ifdef ADC_DUAL_ADCS + //! Object to control the ADC1 + ADC_Module *const adc1 = &adc1_obj; // adc object pointer +#endif + +#ifdef ADC_SINGLE_ADC + //! Array with the ADC Modules + ADC_Module *const adc[ADC_NUM_ADCS] = {adc0}; +#else + //! Array with the ADC Modules + ADC_Module *const adc[ADC_NUM_ADCS] = {adc0, adc1}; +#endif + + /////////////// METHODS TO SET/GET SETTINGS OF THE ADC //////////////////// + + //! Set the voltage reference you prefer, default is vcc + /*! It recalibrates at the end. + * \param type can be ADC_REFERENCE::REF_3V3, ADC_REFERENCE::REF_1V2 (not for Teensy LC) or ADC_REFERENCE::REF_EXT + * \param adc_num ADC number to change. + */ + __attribute__((error("Use adc->adcX->setReference instead"))) void setReference(ADC_REFERENCE type, int8_t adc_num = -1); + + //! Change the resolution of the measurement. + /*! + * \param bits is the number of bits of resolution. + * For single-ended measurements: 8, 10, 12 or 16 bits. + * For differential measurements: 9, 11, 13 or 16 bits. + * If you want something in between (11 bits single-ended for example) select the immediate higher + * and shift the result one to the right. + * Whenever you change the resolution, change also the comparison values (if you use them). + * \param adc_num ADC number to change. + */ + __attribute__((error("Use adc->adcX->setResolution instead"))) void setResolution(uint8_t bits, int8_t adc_num = -1); + + //! Returns the resolution of the ADC_Module. + /** + * \param adc_num ADC number to query. + * \return the resolution of adc_num ADC. + */ + __attribute__((error("Use adc->adcX->getResolution instead"))) + uint8_t + getResolution(int8_t adc_num = -1); + + //! Returns the maximum value for a measurement: 2^res-1. + /** + * \param adc_num ADC number to query. + * \return the maximum value of adc_num ADC. + */ + __attribute__((error("Use adc->adcX->getMaxValue instead"))) + uint32_t + getMaxValue(int8_t adc_num = -1); + + //! Sets the conversion speed (changes the ADC clock, ADCK) + /** + * \param speed can be any from the ADC_CONVERSION_SPEED enum: VERY_LOW_SPEED, LOW_SPEED, MED_SPEED, HIGH_SPEED_16BITS, HIGH_SPEED, VERY_HIGH_SPEED, + * ADACK_2_4, ADACK_4_0, ADACK_5_2 or ADACK_6_2. + * + * VERY_LOW_SPEED is guaranteed to be the lowest possible speed within specs for resolutions less than 16 bits (higher than 1 MHz), + * it's different from LOW_SPEED only for 24, 4 or 2 MHz bus frequency. + * LOW_SPEED is guaranteed to be the lowest possible speed within specs for all resolutions (higher than 2 MHz). + * MED_SPEED is always >= LOW_SPEED and <= HIGH_SPEED. + * HIGH_SPEED_16BITS is guaranteed to be the highest possible speed within specs for all resolutions (lower or eq than 12 MHz). + * HIGH_SPEED is guaranteed to be the highest possible speed within specs for resolutions less than 16 bits (lower or eq than 18 MHz). + * VERY_HIGH_SPEED may be out of specs, it's different from HIGH_SPEED only for 48, 40 or 24 MHz bus frequency. + * + * Additionally the conversion speed can also be ADACK_2_4, ADACK_4_0, ADACK_5_2 and ADACK_6_2, + * where the numbers are the frequency of the ADC clock (ADCK) in MHz and are independent on the bus speed. + * This is useful if you are using the Teensy at a very low clock frequency but want faster conversions, + * but if F_BUSadcX->setConversionSpeed instead"))) void setConversionSpeed(ADC_CONVERSION_SPEED speed, int8_t adc_num = -1); + + //! Sets the sampling speed + /** Increase the sampling speed for low impedance sources, decrease it for higher impedance ones. + * \param speed can be any of the ADC_SAMPLING_SPEED enum: VERY_LOW_SPEED, LOW_SPEED, MED_SPEED, HIGH_SPEED or VERY_HIGH_SPEED. + * + * VERY_LOW_SPEED is the lowest possible sampling speed (+24 ADCK). + * LOW_SPEED adds +16 ADCK. + * MED_SPEED adds +10 ADCK. + * HIGH_SPEED adds +6 ADCK. + * VERY_HIGH_SPEED is the highest possible sampling speed (0 ADCK added). + * \param adc_num ADC number to change. + */ + __attribute__((error("Use adc->adcX->setSamplingSpeed instead"))) void setSamplingSpeed(ADC_SAMPLING_SPEED speed, int8_t adc_num = -1); + + //! Set the number of averages + /*! + * \param num can be 0, 4, 8, 16 or 32. + * \param adc_num ADC number to change. + */ + __attribute__((error("Use adc->adcX->setAveraging instead"))) void setAveraging(uint8_t num, int8_t adc_num = -1); + + //! Enable interrupts + /** An IRQ_ADCx Interrupt will be raised when the conversion is completed + * (including hardware averages and if the comparison (if any) is true). + * \param adc_num ADC number to change. + * \param isr function (returns void and accepts no arguments) that will be executed after an interrupt. + * \param priority Interrupt priority, highest is 0, lowest is 255. + */ + __attribute__((error("Use adc->adcX->enableInterrupts instead"))) void enableInterrupts(void (*isr)(void), uint8_t priority = 255, int8_t adc_num = -1); + + //! Disable interrupts + /** + * \param adc_num ADC number to change. + */ + __attribute__((error("Use adc->adcX->disableInterrupts instead"))) void disableInterrupts(int8_t adc_num = -1); + +#ifdef ADC_USE_DMA + //! Enable DMA request + /** An ADC DMA request will be raised when the conversion is completed + * (including hardware averages and if the comparison (if any) is true). + * \param adc_num ADC number to change. + */ + __attribute__((error("Use adc->adcX->enableDMA instead"))) void enableDMA(int8_t adc_num = -1); + + //! Disable ADC DMA request + /** + * \param adc_num ADC number to change. + */ + __attribute__((error("Use adc->adcX->disableDMA instead"))) void disableDMA(int8_t adc_num = -1); +#endif + + //! Enable the compare function to a single value + /** A conversion will be completed only when the ADC value + * is >= compValue (greaterThan=1) or < compValue (greaterThan=0) + * Call it after changing the resolution + * Use with interrupts or poll conversion completion with isComplete() + * \param compValue value to compare + * \param greaterThan true or false + * \param adc_num ADC number to change. + */ + __attribute__((error("Use adc->adcX->enableCompare instead"))) void enableCompare(int16_t compValue, bool greaterThan, int8_t adc_num = -1); + + //! Enable the compare function to a range + /** A conversion will be completed only when the ADC value is inside (insideRange=1) or outside (=0) + * the range given by (lowerLimit, upperLimit),including (inclusive=1) the limits or not (inclusive=0). + * See Table 31-78, p. 617 of the freescale manual. + * Call it after changing the resolution + * Use with interrupts or poll conversion completion with isComplete() + * \param lowerLimit lower value to compare + * \param upperLimit upper value to compare + * \param insideRange true or false + * \param inclusive true or false + * \param adc_num ADC number to change. + */ + __attribute__((error("Use adc->adcX->enableCompareRange instead"))) void enableCompareRange(int16_t lowerLimit, int16_t upperLimit, bool insideRange, bool inclusive, int8_t adc_num = -1); + + //! Disable the compare function + /** + * \param adc_num ADC number to change. + */ + __attribute__((error("Use adc->adcX->disableCompare instead"))) void disableCompare(int8_t adc_num = -1); + +#ifdef ADC_USE_PGA + //! Enable and set PGA + /** Enables the PGA and sets the gain + * Use only for signals lower than 1.2 V and only in differential mode + * \param gain can be 1, 2, 4, 8, 16, 32 or 64 + * \param adc_num ADC number to change. + */ + __attribute__((error("Use adc->adcX->enablePGA instead"))) void enablePGA(uint8_t gain, int8_t adc_num = -1); + + //! Returns the PGA level + /** PGA level = from 1 to 64 + * \param adc_num ADC number to query. + * \return PGA level = from 1 to 64 + */ + __attribute__((error("Use adc->adcX->getPGA instead"))) + uint8_t + getPGA(int8_t adc_num = -1); + + //! Disable PGA + /** + * \param adc_num ADC number to query + */ + __attribute__((error("Use adc->adcX->disablePGA instead"))) void disablePGA(int8_t adc_num = -1); +#endif + + ////////////// INFORMATION ABOUT THE STATE OF THE ADC ///////////////// + + //! Is the ADC converting at the moment? + /** + * \param adc_num ADC number to query + * \return true if yes, false if not. + */ + __attribute__((error("Use adc->adcX->isConverting instead"))) bool isConverting(int8_t adc_num = -1); + + //! Is an ADC conversion ready? + /** When a value is read this function returns 0 until a new value exists + * So it only makes sense to call it with continuous or non-blocking methods + * \param adc_num ADC number to query + * \return true if yes, false if not. + */ + __attribute__((error("Use adc->adcX->isComplete instead"))) bool isComplete(int8_t adc_num = -1); + +#if ADC_DIFF_PAIRS > 0 + //! Is the ADC in differential mode? + /** + * \param adc_num ADC number to query + * \return true or false + */ + __attribute__((error("Use adc->adcX->isDifferential instead"))) bool isDifferential(int8_t adc_num = -1); +#endif + + //! Is the ADC in continuous mode? + /** + * \param adc_num ADC number to query + * \return true or false + */ + __attribute__((error("Use adc->adcX->isContinuous instead"))) bool isContinuous(int8_t adc_num = -1); + + //////////////// BLOCKING CONVERSION METHODS ////////////////// + + //! Returns the analog value of the pin. + /** It waits until the value is read and then returns the result. + * If a comparison has been set up and fails, it will return ADC_ERROR_VALUE. + * This function is interrupt safe, so it will restore the adc to the state it was before being called + * If more than one ADC exists, it will select the module with less workload, you can force a selection using + * adc_num. If you select ADC1 in Teensy 3.0 it will return ADC_ERROR_VALUE. + * \param pin can be any of the analog pins + * \param adc_num ADC_X ADC module + * \return the value of the pin. + */ + int analogRead(uint8_t pin, int8_t adc_num = -1); + + //! Returns the analog value of the special internal source, such as the temperature sensor. + /** It calls analogRead(uint8_t pin) internally, with the correct value for the pin for all boards. + * Possible values: + * TEMP_SENSOR, Temperature sensor. + * VREF_OUT, 1.2 V reference (switch on first using VREF.h). + * BANDGAP, BANDGAP (switch on first using VREF.h). + * VREFH, High VREF. + * VREFL, Low VREF. + * \param pin ADC_INTERNAL_SOURCE to read. + * \param adc_num ADC_X ADC module + * \return the value of the pin. + */ + int analogRead(ADC_INTERNAL_SOURCE pin, int8_t adc_num = -1) __attribute__((always_inline)) + { + return analogRead(static_cast(pin), adc_num); + } + +#if ADC_DIFF_PAIRS > 0 + //! Reads the differential analog value of two pins (pinP - pinN). + /** It waits until the value is read and then returns the result. + * This function is interrupt safe, so it will restore the adc to the state it was before being called + * If more than one ADC exists, it will select the module with less workload, you can force a selection using + * adc_num + * \param pinP must be A10 or A12. + * \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12). + * \param adc_num ADC_X ADC module + * \return the differential value of the pins, invalid pins return ADC_ERROR_VALUE. + * If a comparison has been set up and fails, it will return ADC_ERROR_VALUE. + */ + int analogReadDifferential(uint8_t pinP, uint8_t pinN, int8_t adc_num = -1); +#endif + + /////////////// NON-BLOCKING CONVERSION METHODS ////////////// + + //! Starts an analog measurement on the pin and enables interrupts. + /** It returns immediately, get value with readSingle(). + * If this function interrupts a measurement, it stores the settings in adc_config + * \param pin can be any of the analog pins + * \param adc_num ADC_X ADC module + * \return true if the pin is valid, false otherwise. + */ + bool startSingleRead(uint8_t pin, int8_t adc_num = -1); + +#if ADC_DIFF_PAIRS > 0 + //! Start a differential conversion between two pins (pinP - pinN) and enables interrupts. + /** It returns immediately, get value with readSingle(). + * If this function interrupts a measurement, it stores the settings in adc_config + * \param pinP must be A10 or A12. + * \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12). + * \param adc_num ADC_X ADC module + * \return true if the pins are valid, false otherwise. + */ + bool startSingleDifferential(uint8_t pinP, uint8_t pinN, int8_t adc_num = -1); +#endif + + //! Reads the analog value of a single conversion. + /** Set the conversion with with startSingleRead(pin) or startSingleDifferential(pinP, pinN). + * \param adc_num ADC_X ADC module + * \return the converted value. + */ + int readSingle(int8_t adc_num = -1); + + ///////////// CONTINUOUS CONVERSION METHODS //////////// + + //! Starts continuous conversion on the pin. + /** It returns as soon as the ADC is set, use analogReadContinuous() to read the value. + * \param pin can be any of the analog pins + * \param adc_num ADC_X ADC module + * \return true if the pin is valid, false otherwise. + */ + bool startContinuous(uint8_t pin, int8_t adc_num = -1); + +#if ADC_DIFF_PAIRS > 0 + //! Starts continuous conversion between the pins (pinP-pinN). + /** It returns as soon as the ADC is set, use analogReadContinuous() to read the value. + * \param pinP must be A10 or A12. + * \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12). + * \param adc_num ADC_X ADC module + * \return true if the pins are valid, false otherwise. + */ + bool startContinuousDifferential(uint8_t pinP, uint8_t pinN, int8_t adc_num = -1); +#endif + + //! Reads the analog value of a continuous conversion. + /** Set the continuous conversion with with analogStartContinuous(pin) or startContinuousDifferential(pinP, pinN). + * If single-ended and 16 bits it's necessary to typecast it to an unsigned type (like uint16_t), + * otherwise values larger than 3.3/2 V are interpreted as negative! + * \param adc_num ADC_X ADC module + * \return the last converted value. + */ + int analogReadContinuous(int8_t adc_num = -1); + + //! Stops continuous conversion + /** + * \param adc_num ADC_X ADC module + */ + void stopContinuous(int8_t adc_num = -1); + +/////////// SYNCHRONIZED METHODS /////////////// +///// ONLY FOR BOARDS WITH MORE THAN ONE ADC ///// +#ifdef ADC_DUAL_ADCS + + //! Struct for synchronous measurements + /** result_adc0 has the result from ADC0 and result_adc1 from ADC1. + */ + struct Sync_result + { + int32_t result_adc0, result_adc1; + }; + + //////////////// SYNCHRONIZED BLOCKING METHODS ////////////////// + + //! Returns the analog values of both pins, measured at the same time by the two ADC modules. + /** It waits until the values are read and then returns the result as a struct Sync_result, + * use Sync_result.result_adc0 and Sync_result.result_adc1. + * If a comparison has been set up and fails, it will return ADC_ERROR_VALUE in both fields of the struct. + * This function is interrupt safe, so it will restore the adc to the state it was before being called + * \param pin0 pin in ADC0 + * \param pin1 pin in ADC1 + * \return a Sync_result struct with the result of each ADC value. + */ + Sync_result analogSynchronizedRead(uint8_t pin0, uint8_t pin1); + + //! Same as analogSynchronizedRead + /** + * \param pin0 pin in ADC0 + * \param pin1 pin in ADC1 + * \return a Sync_result struct with the result of each ADC value. + */ + Sync_result analogSyncRead(uint8_t pin0, uint8_t pin1) __attribute__((always_inline)) { return analogSynchronizedRead(pin0, pin1); } + +#if ADC_DIFF_PAIRS > 0 + //! Returns the differential analog values of both sets of pins, measured at the same time by the two ADC modules. + /** It waits until the values are read and then returns the result as a struct Sync_result, + * use Sync_result.result_adc0 and Sync_result.result_adc1. + * If a comparison has been set up and fails, it will return ADC_ERROR_VALUE in both fields of the struct. + * This function is interrupt safe, so it will restore the adc to the state it was before being called + * \param pin0P positive pin in ADC0 + * \param pin0N negative pin in ADC0 + * \param pin1P positive pin in ADC1 + * \param pin1N negative pin in ADC1 + * \return a Sync_result struct with the result of each differential ADC value. + */ + Sync_result analogSynchronizedReadDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N); + + //! Same as analogSynchronizedReadDifferential + /** + * \param pin0P positive pin in ADC0 + * \param pin0N negative pin in ADC0 + * \param pin1P positive pin in ADC1 + * \param pin1N negative pin in ADC1 + * \return a Sync_result struct with the result of each differential ADC value. + */ + Sync_result analogSyncReadDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N) __attribute__((always_inline)) + { + return analogSynchronizedReadDifferential(pin0P, pin0N, pin1P, pin1N); + } +#endif + + /////////////// SYNCHRONIZED NON-BLOCKING METHODS ////////////// + + //! Starts an analog measurement at the same time on the two ADC modules + /** It returns immediately, get value with readSynchronizedSingle(). + * If this function interrupts a measurement, it stores the settings in adc_config + * \param pin0 pin in ADC0 + * \param pin1 pin in ADC1 + * \return true if the pins are valid, false otherwise. + */ + bool startSynchronizedSingleRead(uint8_t pin0, uint8_t pin1); + +#if ADC_DIFF_PAIRS > 0 + //! Start a differential conversion between two pins (pin0P - pin0N) and (pin1P - pin1N) + /** It returns immediately, get value with readSynchronizedSingle(). + * If this function interrupts a measurement, it stores the settings in adc_config + * \param pin0P positive pin in ADC0 + * \param pin0N negative pin in ADC0 + * \param pin1P positive pin in ADC1 + * \param pin1N negative pin in ADC1 + * \return true if the pins are valid, false otherwise. + */ + bool startSynchronizedSingleDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N); +#endif + + //! Reads the analog value of a single conversion. + /** + * \return the converted value. + */ + Sync_result readSynchronizedSingle(); + + ///////////// SYNCHRONIZED CONTINUOUS CONVERSION METHODS //////////// + + //! Starts a continuous conversion in both ADCs simultaneously + /** Use readSynchronizedContinuous to get the values + * \param pin0 pin in ADC0 + * \param pin1 pin in ADC1 + * \return true if the pins are valid, false otherwise. + */ + bool startSynchronizedContinuous(uint8_t pin0, uint8_t pin1); + +#if ADC_DIFF_PAIRS > 0 + //! Starts a continuous differential conversion in both ADCs simultaneously + /** Use readSynchronizedContinuous to get the values + * \param pin0P positive pin in ADC0 + * \param pin0N negative pin in ADC0 + * \param pin1P positive pin in ADC1 + * \param pin1N negative pin in ADC1 + * \return true if the pins are valid, false otherwise. + */ + bool startSynchronizedContinuousDifferential(uint8_t pin0P, uint8_t pin0N, uint8_t pin1P, uint8_t pin1N); +#endif + + //! Returns the values of both ADCs. + /** + * \return the converted value. + */ + Sync_result readSynchronizedContinuous(); + + //! Stops synchronous continuous conversion + void stopSynchronizedContinuous(); + +#endif + + //////////// ERRORS ///// + //! Resets all errors from all ADCs, if any. + void resetError() + { + for (int i = 0; i < ADC_NUM_ADCS; i++) + { + adc[i]->resetError(); + } + } + + //! Translate pin number to SC1A nomenclature + // should this be a constexpr? + static const uint8_t channel2sc1aADC0[ADC_MAX_PIN + 1]; +#ifdef ADC_DUAL_ADCS + //! Translate pin number to SC1A nomenclature + static const uint8_t channel2sc1aADC1[ADC_MAX_PIN + 1]; +#endif + + //! Translate pin number to SC1A nomenclature for differential pins + static const uint8_t sc1a2channelADC0[ADC_MAX_PIN + 1]; +#ifdef ADC_DUAL_ADCS + //! Translate pin number to SC1A nomenclature for differential pins + static const uint8_t sc1a2channelADC1[ADC_MAX_PIN + 1]; +#endif + +#if ADC_DIFF_PAIRS > 0 + //! Translate differential pin number to SC1A nomenclature + static const ADC_Module::ADC_NLIST diff_table_ADC0[ADC_DIFF_PAIRS]; +#ifdef ADC_DUAL_ADCS + //! Translate differential pin number to SC1A nomenclature + static const ADC_Module::ADC_NLIST diff_table_ADC1[ADC_DIFF_PAIRS]; +#endif +#endif + }; + +#ifdef __cplusplus +} +#endif + +#endif // ADC_H diff --git a/Firmware_V3/lib/ADC/ADC_Module.cpp b/Firmware_V3/lib/ADC/ADC_Module.cpp new file mode 100644 index 0000000..3b4e6ae --- /dev/null +++ b/Firmware_V3/lib/ADC/ADC_Module.cpp @@ -0,0 +1,1722 @@ +/* Teensy 4.x, 3.x, LC ADC library + * https://github.com/pedvide/ADC + * Copyright (c) 2020 Pedro Villanueva + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* ADC_Module.cpp: Implements the fuctions of a Teensy 3.x, LC ADC module + * + */ + +#include "ADC_Module.h" + +// include the internal reference +#ifdef ADC_USE_INTERNAL_VREF +#include +#endif + +/* Constructor +* Point the registers to the correct ADC module +* Copy the correct channel2sc1a +* Call init +*/ +ADC_Module::ADC_Module(uint8_t ADC_number, + const uint8_t *const a_channel2sc1a, +#if ADC_DIFF_PAIRS > 0 + const ADC_NLIST *const a_diff_table, +#endif + ADC_REGS_t &a_adc_regs) : ADC_num(ADC_number), channel2sc1a(a_channel2sc1a) +#if ADC_DIFF_PAIRS > 0 + , + diff_table(a_diff_table) +#endif + , + adc_regs(a_adc_regs) +#ifdef ADC_USE_PDB + , + PDB0_CHnC1(ADC_num ? PDB0_CH1C1 : PDB0_CH0C1) +#endif +#if defined(ADC_TEENSY_4) + , + XBAR_IN(ADC_num ? XBARA1_IN_QTIMER4_TIMER3 : XBARA1_IN_QTIMER4_TIMER0), XBAR_OUT(ADC_num ? XBARA1_OUT_ADC_ETC_TRIG10 : XBARA1_OUT_ADC_ETC_TRIG00), QTIMER4_INDEX(ADC_num ? 3 : 0), ADC_ETC_TRIGGER_INDEX(ADC_num ? 4 : 0), IRQ_ADC(ADC_num ? IRQ_NUMBER_t::IRQ_ADC2 : IRQ_NUMBER_t::IRQ_ADC1) +#elif defined(ADC_DUAL_ADCS) + // IRQ_ADC0 and IRQ_ADC1 aren't consecutive in Teensy 3.6 + // fix by SB, https://github.com/pedvide/ADC/issues/19 + , + IRQ_ADC(ADC_num ? IRQ_NUMBER_t::IRQ_ADC1 : IRQ_NUMBER_t::IRQ_ADC0) +#else + , + IRQ_ADC(IRQ_NUMBER_t::IRQ_ADC0) +#endif +{ + + // call our init + analog_init(); +} + +/* Initialize stuff: +* - Switch on clock +* - Clear all fail flags +* - Internal reference (default: external vcc) +* - Mux between a and b channels (b channels) +* - Calibrate with 32 averages and low speed +* - When first calibration is done it sets: +* - Resolution (default: 10 bits) +* - Conversion speed and sampling time (both set to medium speed) +* - Averaging (set to 4) +*/ +void ADC_Module::analog_init() +{ + + startClock(); + + // default settings: + /* + - 10 bits resolution + - 4 averages + - vcc reference + - no interrupts + - pga gain=1 + - conversion speed = medium + - sampling speed = medium + initiate to 0 (or 1) so the corresponding functions change it to the correct value + */ + analog_res_bits = 0; + analog_max_val = 0; + analog_num_average = 0; + analog_reference_internal = ADC_REF_SOURCE::REF_NONE; +#ifdef ADC_USE_PGA + pga_value = 1; +#endif + interrupts_enabled = false; + +#ifdef ADC_TEENSY_4 + // overwrite old values if a new conversion ends + atomic::setBitFlag(adc_regs.CFG, ADC_CFG_OVWREN); +// this is the only option for Teensy 3.x and LC +#endif + + conversion_speed = ADC_CONVERSION_SPEED::HIGH_SPEED; // set to something different from line 139 so it gets changed there + sampling_speed = ADC_SAMPLING_SPEED::VERY_HIGH_SPEED; + + calibrating = 0; + + fail_flag = ADC_ERROR::CLEAR; // clear all errors + + num_measurements = 0; + +// select b channels +#ifdef ADC_TEENSY_4 +// T4 has no a or b channels +#else + // ADC_CFG2_muxsel = 1; + atomic::setBitFlag(adc_regs.CFG2, ADC_CFG2_MUXSEL); +#endif + + // set reference to vcc + setReference(ADC_REFERENCE::REF_3V3); + + // set resolution to 10 + setResolution(10); + + // the first calibration will use 32 averages and lowest speed, + // when this calibration is over the averages and speed will be set to default by wait_for_cal and init_calib will be cleared. + init_calib = 1; + setAveraging(32); + setConversionSpeed(ADC_CONVERSION_SPEED::LOW_SPEED); + setSamplingSpeed(ADC_SAMPLING_SPEED::LOW_SPEED); + + // begin init calibration + calibrate(); +} + +// starts calibration +void ADC_Module::calibrate() +{ + + __disable_irq(); + + calibrating = 1; +#ifdef ADC_TEENSY_4 + atomic::clearBitFlag(adc_regs.GC, ADC_GC_CAL); + atomic::setBitFlag(adc_regs.GS, ADC_GS_CALF); + atomic::setBitFlag(adc_regs.GC, ADC_GC_CAL); +#else + // ADC_SC3_cal = 0; // stop possible previous calibration + atomic::clearBitFlag(adc_regs.SC3, ADC_SC3_CAL); + // ADC_SC3_calf = 1; // clear possible previous error + atomic::setBitFlag(adc_regs.SC3, ADC_SC3_CALF); + // ADC_SC3_cal = 1; // start calibration + atomic::setBitFlag(adc_regs.SC3, ADC_SC3_CAL); +#endif + + __enable_irq(); +} + +/* Waits until calibration is finished and writes the corresponding registers +* +*/ +void ADC_Module::wait_for_cal(void) +{ + +// wait for calibration to finish +#ifdef ADC_TEENSY_4 + while (atomic::getBitFlag(adc_regs.GC, ADC_GC_CAL)) + { // Bit ADC_GC_CAL in register GC cleared when calib. finishes. + yield(); + } + if (atomic::getBitFlag(adc_regs.GS, ADC_GS_CALF)) + { // calibration failed + fail_flag |= ADC_ERROR::CALIB; // the user should know and recalibrate manually + } +#else + while (atomic::getBitFlag(adc_regs.SC3, ADC_SC3_CAL)) + { // Bit ADC_SC3_CAL in register ADC0_SC3 cleared when calib. finishes. + yield(); + } + if (atomic::getBitFlag(adc_regs.SC3, ADC_SC3_CALF)) + { // calibration failed + fail_flag |= ADC_ERROR::CALIB; // the user should know and recalibrate manually + } +#endif + +// set calibrated values to registers +#ifdef ADC_TEENSY_4 +// T4 does not require anything else for calibration +#else + __disable_irq(); + uint16_t sum; + if (calibrating) + { + sum = adc_regs.CLPS + adc_regs.CLP4 + adc_regs.CLP3 + adc_regs.CLP2 + adc_regs.CLP1 + adc_regs.CLP0; + sum = (sum / 2) | 0x8000; + adc_regs.PG = sum; + + sum = adc_regs.CLMS + adc_regs.CLM4 + adc_regs.CLM3 + adc_regs.CLM2 + adc_regs.CLM1 + adc_regs.CLM0; + sum = (sum / 2) | 0x8000; + adc_regs.MG = sum; + } + __enable_irq(); +#endif + calibrating = 0; + + // the first calibration uses 32 averages and lowest speed, + // when this calibration is over, set the averages and speed to default. + if (init_calib) + { + + // set conversion speed to medium + setConversionSpeed(ADC_CONVERSION_SPEED::MED_SPEED); + + // set sampling speed to medium + setSamplingSpeed(ADC_SAMPLING_SPEED::MED_SPEED); + + // number of averages to 4 + setAveraging(4); + + init_calib = 0; // clear + } +} + +//! Starts the calibration sequence, waits until it's done and writes the results +/** Usually it's not necessary to call this function directly, but do it if the "environment" changed +* significantly since the program was started. +*/ +void ADC_Module::recalibrate() +{ + + calibrate(); + + wait_for_cal(); +} + +/////////////// METHODS TO SET/GET SETTINGS OF THE ADC //////////////////// + +/* Set the voltage reference you prefer, default is 3.3V +* It needs to recalibrate +* Use ADC_REF_3V3, ADC_REF_1V2 (not for Teensy LC) or ADC_REF_EXT +*/ +void ADC_Module::setReference(ADC_REFERENCE type) +{ + ADC_REF_SOURCE ref_type = static_cast(type); // cast to source type, that is, either internal or default + + if (analog_reference_internal == ref_type) + { // don't need to change anything + return; + } + + if (ref_type == ADC_REF_SOURCE::REF_ALT) + { // 1.2V ref for Teensy 3.x, 3.3 VDD for Teensy LC +// internal reference requested +#ifdef ADC_USE_INTERNAL_VREF + VREF::start(); // enable VREF if Teensy 3.x +#endif + + analog_reference_internal = ADC_REF_SOURCE::REF_ALT; + +#ifdef ADC_TEENSY_4 +// No REF_ALT for T4 +#else + // *ADC_SC2_ref = 1; // uses bitband: atomic + atomic::setBitFlag(adc_regs.SC2, ADC_SC2_REFSEL(1)); +#endif + } + else if (ref_type == ADC_REF_SOURCE::REF_DEFAULT) + { // ext ref for all Teensys, vcc also for Teensy 3.x + // vcc or external reference requested + +#ifdef ADC_USE_INTERNAL_VREF + VREF::stop(); // disable 1.2V reference source when using the external ref (p. 102, 3.7.1.7) +#endif + + analog_reference_internal = ADC_REF_SOURCE::REF_DEFAULT; + +#ifdef ADC_TEENSY_4 + atomic::clearBitFlag(adc_regs.CFG, ADC_CFG_REFSEL(3)); +#else + // *ADC_SC2_ref = 0; // uses bitband: atomic + atomic::clearBitFlag(adc_regs.SC2, ADC_SC2_REFSEL(1)); +#endif + } + + calibrate(); +} + +/* Change the resolution of the measurement +* For single-ended measurements: 8, 10, 12 or 16 bits. +* For differential measurements: 9, 11, 13 or 16 bits. +* If you want something in between (11 bits single-ended for example) select the inmediate higher +* and shift the result one to the right. +* +* It doesn't recalibrate +*/ +void ADC_Module::setResolution(uint8_t bits) +{ + + if (analog_res_bits == bits) + { + return; + } + + uint8_t config; + + if (calibrating) + wait_for_cal(); + + if (bits <= 9) + { + config = 8; + } + else if (bits <= 11) + { + config = 10; +#ifdef ADC_TEENSY_4 + } + else if (bits > 11) + { + config = 12; +#else + } + else if (bits <= 13) + { + config = 12; + } + else if (bits > 13) + { + config = 16; +#endif + } + else + { + config = 8; // default to 8 bits + } + + // conversion resolution + // single-ended 8 bits is the same as differential 9 bits, etc. + if ((config == 8) || (config == 9)) + { +#ifdef ADC_TEENSY_4 + atomic::clearBitFlag(adc_regs.CFG, ADC_CFG_MODE(3)); +#else + // *ADC_CFG1_mode1 = 0; + // *ADC_CFG1_mode0 = 0; + atomic::clearBitFlag(adc_regs.CFG1, ADC_CFG1_MODE(3)); +#endif + analog_max_val = 255; // diff mode 9 bits has 1 bit for sign, so max value is the same as single 8 bits + } + else if ((config == 10) || (config == 11)) + { +#ifdef ADC_TEENSY_4 + atomic::changeBitFlag(adc_regs.CFG, ADC_CFG_MODE(3), ADC_CFG_MODE(1)); +#else + // *ADC_CFG1_mode1 = 1; + // *ADC_CFG1_mode0 = 0; + atomic::changeBitFlag(adc_regs.CFG1, ADC_CFG1_MODE(3), ADC_CFG1_MODE(2)); +#endif + analog_max_val = 1023; + } + else if ((config == 12) || (config == 13)) + { +#ifdef ADC_TEENSY_4 + atomic::changeBitFlag(adc_regs.CFG, ADC_CFG_MODE(3), ADC_CFG_MODE(2)); +#else + // *ADC_CFG1_mode1 = 0; + // *ADC_CFG1_mode0 = 1; + atomic::changeBitFlag(adc_regs.CFG1, ADC_CFG1_MODE(3), ADC_CFG1_MODE(1)); +#endif + analog_max_val = 4095; + } + else + { +#ifdef ADC_TEENSY_4 +// Impossible for T4 +#else + // *ADC_CFG1_mode1 = 1; + // *ADC_CFG1_mode0 = 1; + atomic::setBitFlag(adc_regs.CFG1, ADC_CFG1_MODE(3)); +#endif + analog_max_val = 65535; + } + + analog_res_bits = config; + + // no recalibration is needed when changing the resolution, p. 619 +} + +/* Returns the resolution of the ADC +* +*/ +uint8_t ADC_Module::getResolution() +{ + return analog_res_bits; +} + +/* Returns the maximum value for a measurement, that is: 2^resolution-1 +* +*/ +uint32_t ADC_Module::getMaxValue() +{ + return analog_max_val; +} + +// Sets the conversion speed +/* Increase the sampling speed for low impedance sources, decrease it for higher impedance ones. +* \param speed can be any of the ADC_SAMPLING_SPEED enum: VERY_LOW_SPEED, LOW_SPEED, MED_SPEED, HIGH_SPEED or VERY_HIGH_SPEED. +* +* VERY_LOW_SPEED is the lowest possible sampling speed (+24 ADCK). +* LOW_SPEED adds +16 ADCK. +* MED_SPEED adds +10 ADCK. +* HIGH_SPEED adds +6 ADCK. +* VERY_HIGH_SPEED is the highest possible sampling speed (0 ADCK added). +*/ +void ADC_Module::setConversionSpeed(ADC_CONVERSION_SPEED speed) +{ + + if (speed == conversion_speed) + { // no change + return; + } + + //if (calibrating) wait_for_cal(); + + bool is_adack = false; + uint32_t ADC_CFG1_speed = 0; // store the clock and divisor (set to 0 to avoid warnings) + + switch (speed) + { +// normal bus clock +#ifndef ADC_TEENSY_4 + case ADC_CONVERSION_SPEED::VERY_LOW_SPEED: + atomic::clearBitFlag(adc_regs.CFG2, ADC_CFG2_ADHSC); + atomic::setBitFlag(adc_regs.CFG1, ADC_CFG1_ADLPC); + // ADC_CFG1_speed = ADC_CFG1_VERY_LOW_SPEED; + ADC_CFG1_speed = get_CFG_VERY_LOW_SPEED(ADC_F_BUS); + break; +#endif + case ADC_CONVERSION_SPEED::LOW_SPEED: +#ifdef ADC_TEENSY_4 + atomic::clearBitFlag(adc_regs.CFG, ADC_CFG_ADHSC); + atomic::setBitFlag(adc_regs.CFG, ADC_CFG_ADLPC); +#else + atomic::clearBitFlag(adc_regs.CFG2, ADC_CFG2_ADHSC); + atomic::setBitFlag(adc_regs.CFG1, ADC_CFG1_ADLPC); +#endif + // ADC_CFG1_speed = ADC_CFG1_LOW_SPEED; + ADC_CFG1_speed = get_CFG_LOW_SPEED(ADC_F_BUS); + break; + case ADC_CONVERSION_SPEED::MED_SPEED: +#ifdef ADC_TEENSY_4 + atomic::clearBitFlag(adc_regs.CFG, ADC_CFG_ADHSC); + atomic::clearBitFlag(adc_regs.CFG, ADC_CFG_ADLPC); +#else + atomic::clearBitFlag(adc_regs.CFG2, ADC_CFG2_ADHSC); + atomic::clearBitFlag(adc_regs.CFG1, ADC_CFG1_ADLPC); +#endif + ADC_CFG1_speed = get_CFG_MEDIUM_SPEED(ADC_F_BUS); + break; +#ifndef ADC_TEENSY_4 + case ADC_CONVERSION_SPEED::HIGH_SPEED_16BITS: + atomic::setBitFlag(adc_regs.CFG2, ADC_CFG2_ADHSC); + atomic::clearBitFlag(adc_regs.CFG1, ADC_CFG1_ADLPC); + // ADC_CFG1_speed = ADC_CFG1_HI_SPEED_16_BITS; + ADC_CFG1_speed = get_CFG_HI_SPEED_16_BITS(ADC_F_BUS); + break; +#endif + case ADC_CONVERSION_SPEED::HIGH_SPEED: +#ifdef ADC_TEENSY_4 + atomic::setBitFlag(adc_regs.CFG, ADC_CFG_ADHSC); + atomic::clearBitFlag(adc_regs.CFG, ADC_CFG_ADLPC); +#else + atomic::setBitFlag(adc_regs.CFG2, ADC_CFG2_ADHSC); + atomic::clearBitFlag(adc_regs.CFG1, ADC_CFG1_ADLPC); +#endif + ADC_CFG1_speed = get_CFG_HIGH_SPEED(ADC_F_BUS); + break; +#ifndef ADC_TEENSY_4 + case ADC_CONVERSION_SPEED::VERY_HIGH_SPEED: + atomic::setBitFlag(adc_regs.CFG2, ADC_CFG2_ADHSC); + atomic::clearBitFlag(adc_regs.CFG1, ADC_CFG1_ADLPC); + // ADC_CFG1_speed = ADC_CFG1_VERY_HIGH_SPEED; + ADC_CFG1_speed = get_CFG_VERY_HIGH_SPEED(ADC_F_BUS); + break; +#endif + +// adack - async clock source, independent of the bus clock +#ifdef ADC_TEENSY_4 // fADK = 10 or 20 MHz + case ADC_CONVERSION_SPEED::ADACK_10: + atomic::clearBitFlag(adc_regs.CFG, ADC_CFG_ADHSC); + is_adack = true; + break; + case ADC_CONVERSION_SPEED::ADACK_20: + atomic::setBitFlag(adc_regs.CFG, ADC_CFG_ADHSC); + is_adack = true; + break; +#else // fADK = 2.4, 4.0, 5.2 or 6.2 MHz + case ADC_CONVERSION_SPEED::ADACK_2_4: + atomic::clearBitFlag(adc_regs.CFG2, ADC_CFG2_ADHSC); + atomic::setBitFlag(adc_regs.CFG1, ADC_CFG1_ADLPC); + is_adack = true; + break; + case ADC_CONVERSION_SPEED::ADACK_4_0: + atomic::setBitFlag(adc_regs.CFG2, ADC_CFG2_ADHSC); + atomic::setBitFlag(adc_regs.CFG1, ADC_CFG1_ADLPC); + is_adack = true; + break; + case ADC_CONVERSION_SPEED::ADACK_5_2: + atomic::clearBitFlag(adc_regs.CFG2, ADC_CFG2_ADHSC); + atomic::clearBitFlag(adc_regs.CFG1, ADC_CFG1_ADLPC); + is_adack = true; + break; + case ADC_CONVERSION_SPEED::ADACK_6_2: + atomic::setBitFlag(adc_regs.CFG2, ADC_CFG2_ADHSC); + atomic::clearBitFlag(adc_regs.CFG1, ADC_CFG1_ADLPC); + is_adack = true; + break; +#endif + + default: + fail_flag |= ADC_ERROR::OTHER; + return; + } + + if (is_adack) + { +// async clock source, independent of the bus clock +#ifdef ADC_TEENSY_4 + atomic::setBitFlag(adc_regs.GC, ADC_GC_ADACKEN); // enable ADACK (takes max 5us to be ready) + atomic::setBitFlag(adc_regs.CFG, ADC_CFG_ADICLK(3)); // select ADACK as clock source + atomic::clearBitFlag(adc_regs.CFG, ADC_CFG_ADIV(3)); // select no dividers +#else + atomic::setBitFlag(adc_regs.CFG2, ADC_CFG2_ADACKEN); + atomic::setBitFlag(adc_regs.CFG1, ADC_CFG1_ADICLK(3)); + atomic::clearBitFlag(adc_regs.CFG1, ADC_CFG1_ADIV(3)); +#endif + } + else + { +// normal bus clock used - disable the internal asynchronous clock +// total speed can be: bus, bus/2, bus/4, bus/8 or bus/16. +#ifdef ADC_TEENSY_4 + atomic::clearBitFlag(adc_regs.GC, ADC_GC_ADACKEN); // disable async + atomic::changeBitFlag(adc_regs.CFG, ADC_CFG_ADICLK(3), ADC_CFG1_speed & ADC_CFG_ADICLK(3)); // bus or bus/2 + atomic::changeBitFlag(adc_regs.CFG, ADC_CFG_ADIV(3), ADC_CFG1_speed & ADC_CFG_ADIV(3)); // divisor for the clock source +#else + atomic::clearBitFlag(adc_regs.CFG2, ADC_CFG2_ADACKEN); + atomic::changeBitFlag(adc_regs.CFG1, ADC_CFG1_ADICLK(3), ADC_CFG1_speed & ADC_CFG1_ADICLK(3)); + atomic::changeBitFlag(adc_regs.CFG1, ADC_CFG1_ADIV(3), ADC_CFG1_speed & ADC_CFG1_ADIV(3)); +#endif + } + + conversion_speed = speed; + calibrate(); +} + +// Sets the sampling speed +/* Increase the sampling speed for low impedance sources, decrease it for higher impedance ones. +* \param speed can be any of the ADC_SAMPLING_SPEED enum: VERY_LOW_SPEED, LOW_SPEED, MED_SPEED, HIGH_SPEED or VERY_HIGH_SPEED. +* +* VERY_LOW_SPEED is the lowest possible sampling speed (+24 ADCK). +* LOW_SPEED adds +16 ADCK. +* MED_SPEED adds +10 ADCK. +* HIGH_SPEED adds +6 ADCK. +* VERY_HIGH_SPEED is the highest possible sampling speed (0 ADCK added). +*/ +void ADC_Module::setSamplingSpeed(ADC_SAMPLING_SPEED speed) +{ + if (calibrating) + wait_for_cal(); + + switch (speed) + { +#ifdef ADC_TEENSY_4 + case ADC_SAMPLING_SPEED::VERY_LOW_SPEED: + atomic::setBitFlag(adc_regs.CFG, ADC_CFG_ADLSMP); // long sampling time enable + atomic::changeBitFlag(adc_regs.CFG, ADC_CFG_ADSTS(3), ADC_CFG_ADSTS(3)); + break; + case ADC_SAMPLING_SPEED::LOW_SPEED: + atomic::setBitFlag(adc_regs.CFG, ADC_CFG_ADLSMP); // long sampling time enable + atomic::changeBitFlag(adc_regs.CFG, ADC_CFG_ADSTS(3), ADC_CFG_ADSTS(2)); + break; + case ADC_SAMPLING_SPEED::LOW_MED_SPEED: + atomic::setBitFlag(adc_regs.CFG, ADC_CFG_ADLSMP); // long sampling time enable + atomic::changeBitFlag(adc_regs.CFG, ADC_CFG_ADSTS(3), ADC_CFG_ADSTS(1)); + break; + case ADC_SAMPLING_SPEED::MED_SPEED: + atomic::setBitFlag(adc_regs.CFG, ADC_CFG_ADLSMP); // long sampling time enable + atomic::changeBitFlag(adc_regs.CFG, ADC_CFG_ADSTS(3), ADC_CFG_ADSTS(0)); + break; + case ADC_SAMPLING_SPEED::MED_HIGH_SPEED: + atomic::clearBitFlag(adc_regs.CFG, ADC_CFG_ADLSMP); // long sampling time disabled + atomic::changeBitFlag(adc_regs.CFG, ADC_CFG_ADSTS(3), ADC_CFG_ADSTS(3)); + break; + case ADC_SAMPLING_SPEED::HIGH_SPEED: + atomic::clearBitFlag(adc_regs.CFG, ADC_CFG_ADLSMP); // long sampling time disabled + atomic::changeBitFlag(adc_regs.CFG, ADC_CFG_ADSTS(3), ADC_CFG_ADSTS(2)); + break; + case ADC_SAMPLING_SPEED::HIGH_VERY_HIGH_SPEED: + atomic::clearBitFlag(adc_regs.CFG, ADC_CFG_ADLSMP); // long sampling time disabled + atomic::changeBitFlag(adc_regs.CFG, ADC_CFG_ADSTS(3), ADC_CFG_ADSTS(1)); + break; + case ADC_SAMPLING_SPEED::VERY_HIGH_SPEED: + atomic::clearBitFlag(adc_regs.CFG, ADC_CFG_ADLSMP); // long sampling time disabled + atomic::changeBitFlag(adc_regs.CFG, ADC_CFG_ADSTS(3), ADC_CFG_ADSTS(0)); + break; +#else + case ADC_SAMPLING_SPEED::VERY_LOW_SPEED: + atomic::setBitFlag(adc_regs.CFG1, ADC_CFG1_ADLSMP); // long sampling time enable + atomic::clearBitFlag(adc_regs.CFG2, ADC_CFG2_ADLSTS(3)); // maximum sampling time (+24 ADCK) + break; + case ADC_SAMPLING_SPEED::LOW_SPEED: + atomic::setBitFlag(adc_regs.CFG1, ADC_CFG1_ADLSMP); // long sampling time enable + atomic::changeBitFlag(adc_regs.CFG2, ADC_CFG2_ADLSTS(3), ADC_CFG2_ADLSTS(1)); // high sampling time (+16 ADCK) + break; + case ADC_SAMPLING_SPEED::MED_SPEED: + atomic::setBitFlag(adc_regs.CFG1, ADC_CFG1_ADLSMP); // long sampling time enable + atomic::changeBitFlag(adc_regs.CFG2, ADC_CFG2_ADLSTS(3), ADC_CFG2_ADLSTS(2)); // medium sampling time (+10 ADCK) + break; + case ADC_SAMPLING_SPEED::HIGH_SPEED: + atomic::setBitFlag(adc_regs.CFG1, ADC_CFG1_ADLSMP); // long sampling time enable + atomic::setBitFlag(adc_regs.CFG2, ADC_CFG2_ADLSTS(3)); // low sampling time (+6 ADCK) + break; + case ADC_SAMPLING_SPEED::VERY_HIGH_SPEED: + atomic::clearBitFlag(adc_regs.CFG1, ADC_CFG1_ADLSMP); // shortest sampling time + break; +#endif + } + sampling_speed = speed; +} + +/* Set the number of averages: 0, 4, 8, 16 or 32. +* +*/ +void ADC_Module::setAveraging(uint8_t num) +{ + + if (calibrating) + wait_for_cal(); + + if (num <= 1) + { + num = 0; +// ADC_SC3_avge = 0; +#ifdef ADC_TEENSY_4 + atomic::clearBitFlag(adc_regs.GC, ADC_GC_AVGE); +#else + atomic::clearBitFlag(adc_regs.SC3, ADC_SC3_AVGE); +#endif + } + else + { +// ADC_SC3_avge = 1; +#ifdef ADC_TEENSY_4 + atomic::setBitFlag(adc_regs.GC, ADC_GC_AVGE); +#else + atomic::setBitFlag(adc_regs.SC3, ADC_SC3_AVGE); +#endif + if (num <= 4) + { + num = 4; +// ADC_SC3_avgs0 = 0; +// ADC_SC3_avgs1 = 0; +#ifdef ADC_TEENSY_4 + atomic::clearBitFlag(adc_regs.CFG, ADC_CFG_AVGS(3)); +#else + atomic::clearBitFlag(adc_regs.SC3, ADC_SC3_AVGS(3)); +#endif + } + else if (num <= 8) + { + num = 8; +// ADC_SC3_avgs0 = 1; +// ADC_SC3_avgs1 = 0; +#ifdef ADC_TEENSY_4 + atomic::changeBitFlag(adc_regs.CFG, ADC_CFG_AVGS(3), ADC_CFG_AVGS(1)); +#else + atomic::changeBitFlag(adc_regs.SC3, ADC_SC3_AVGS(3), ADC_SC3_AVGS(1)); +#endif + } + else if (num <= 16) + { + num = 16; +// ADC_SC3_avgs0 = 0; +// ADC_SC3_avgs1 = 1; +#ifdef ADC_TEENSY_4 + atomic::changeBitFlag(adc_regs.CFG, ADC_CFG_AVGS(3), ADC_CFG_AVGS(2)); +#else + atomic::changeBitFlag(adc_regs.SC3, ADC_SC3_AVGS(3), ADC_SC3_AVGS(2)); +#endif + } + else + { + num = 32; +// ADC_SC3_avgs0 = 1; +// ADC_SC3_avgs1 = 1; +#ifdef ADC_TEENSY_4 + atomic::setBitFlag(adc_regs.CFG, ADC_CFG_AVGS(3)); +#else + atomic::setBitFlag(adc_regs.SC3, ADC_SC3_AVGS(3)); +#endif + } + } + analog_num_average = num; +} + +/* Enable interrupts: An ADC Interrupt will be raised when the conversion is completed +* (including hardware averages and if the comparison (if any) is true). +*/ +void ADC_Module::enableInterrupts(void (*isr)(void), uint8_t priority) +{ + if (calibrating) + wait_for_cal(); + +// ADC_SC1A_aien = 1; +#ifdef ADC_TEENSY_4 + atomic::setBitFlag(adc_regs.HC0, ADC_HC_AIEN); + interrupts_enabled = true; +#else + atomic::setBitFlag(adc_regs.SC1A, ADC_SC1_AIEN); +#endif + + attachInterruptVector(IRQ_ADC, isr); + NVIC_SET_PRIORITY(IRQ_ADC, priority); + NVIC_ENABLE_IRQ(IRQ_ADC); +} + +/* Disable interrupts +* +*/ +void ADC_Module::disableInterrupts() +{ +// ADC_SC1A_aien = 0; +#ifdef ADC_TEENSY_4 + atomic::clearBitFlag(adc_regs.HC0, ADC_HC_AIEN); + interrupts_enabled = false; +#else + atomic::clearBitFlag(adc_regs.SC1A, ADC_SC1_AIEN); +#endif + + NVIC_DISABLE_IRQ(IRQ_ADC); +} + +#ifdef ADC_USE_DMA +/* Enable DMA request: An ADC DMA request will be raised when the conversion is completed +* (including hardware averages and if the comparison (if any) is true). +*/ +void ADC_Module::enableDMA() +{ + + if (calibrating) + wait_for_cal(); + +// ADC_SC2_dma = 1; +#ifdef ADC_TEENSY_4 + atomic::setBitFlag(adc_regs.GC, ADC_GC_DMAEN); +#else + atomic::setBitFlag(adc_regs.SC2, ADC_SC2_DMAEN); +#endif +} + +/* Disable ADC DMA request +* +*/ +void ADC_Module::disableDMA() +{ + +// ADC_SC2_dma = 0; +#ifdef ADC_TEENSY_4 + atomic::clearBitFlag(adc_regs.GC, ADC_GC_DMAEN); +#else + atomic::clearBitFlag(adc_regs.SC2, ADC_SC2_DMAEN); +#endif +} +#endif + +/* Enable the compare function: A conversion will be completed only when the ADC value +* is >= compValue (greaterThan=1) or < compValue (greaterThan=0) +* Call it after changing the resolution +* Use with interrupts or poll conversion completion with isADC_Complete() +*/ +void ADC_Module::enableCompare(int16_t compValue, bool greaterThan) +{ + + if (calibrating) + wait_for_cal(); // if we modify the adc's registers when calibrating, it will fail + +// ADC_SC2_cfe = 1; // enable compare +// ADC_SC2_cfgt = (int32_t)greaterThan; // greater or less than? +#ifdef ADC_TEENSY_4 + atomic::setBitFlag(adc_regs.GC, ADC_GC_ACFE); + atomic::changeBitFlag(adc_regs.GC, ADC_GC_ACFGT, ADC_GC_ACFGT * greaterThan); + adc_regs.CV = ADC_CV_CV1(compValue); +#else + atomic::setBitFlag(adc_regs.SC2, ADC_SC2_ACFE); + atomic::changeBitFlag(adc_regs.SC2, ADC_SC2_ACFGT, ADC_SC2_ACFGT * greaterThan); + + adc_regs.CV1 = (int16_t)compValue; // comp value +#endif +} + +/* Enable the compare function: A conversion will be completed only when the ADC value +* is inside (insideRange=1) or outside (=0) the range given by (lowerLimit, upperLimit), +* including (inclusive=1) the limits or not (inclusive=0). +* See Table 31-78, p. 617 of the freescale manual. +* Call it after changing the resolution +*/ +void ADC_Module::enableCompareRange(int16_t lowerLimit, int16_t upperLimit, bool insideRange, bool inclusive) +{ + + if (calibrating) + wait_for_cal(); // if we modify the adc's registers when calibrating, it will fail + +// ADC_SC2_cfe = 1; // enable compare +// ADC_SC2_cren = 1; // enable compare range +#ifdef ADC_TEENSY_4 + atomic::setBitFlag(adc_regs.GC, ADC_GC_ACFE); + atomic::setBitFlag(adc_regs.GC, ADC_GC_ACREN); +#else + atomic::setBitFlag(adc_regs.SC2, ADC_SC2_ACFE); + atomic::setBitFlag(adc_regs.SC2, ADC_SC2_ACREN); +#endif + + if (insideRange && inclusive) + { // True if value is inside the range, including the limits. CV1 <= CV2 and ACFGT=1 +// ADC_SC2_cfgt = 1; +#ifdef ADC_TEENSY_4 + atomic::setBitFlag(adc_regs.GC, ADC_GC_ACFGT); + adc_regs.CV = ADC_CV_CV1(lowerLimit) | ADC_CV_CV2(upperLimit); +#else + atomic::setBitFlag(adc_regs.SC2, ADC_SC2_ACFGT); + adc_regs.CV1 = (int16_t)lowerLimit; + adc_regs.CV2 = (int16_t)upperLimit; +#endif + } + else if (insideRange && !inclusive) + { // True if value is inside the range, excluding the limits. CV1 > CV2 and ACFGT=0 +// ADC_SC2_cfgt = 0; +#ifdef ADC_TEENSY_4 + atomic::clearBitFlag(adc_regs.GC, ADC_GC_ACFGT); + adc_regs.CV = ADC_CV_CV2(lowerLimit) | ADC_CV_CV1(upperLimit); +#else + atomic::clearBitFlag(adc_regs.SC2, ADC_SC2_ACFGT); + adc_regs.CV2 = (int16_t)lowerLimit; + adc_regs.CV1 = (int16_t)upperLimit; +#endif + } + else if (!insideRange && inclusive) + { // True if value is outside of range or is equal to either limit. CV1 > CV2 and ACFGT=1 +// ADC_SC2_cfgt = 1; +#ifdef ADC_TEENSY_4 + atomic::setBitFlag(adc_regs.GC, ADC_GC_ACFGT); + adc_regs.CV = ADC_CV_CV2(lowerLimit) | ADC_CV_CV1(upperLimit); +#else + atomic::setBitFlag(adc_regs.SC2, ADC_SC2_ACFGT); + adc_regs.CV2 = (int16_t)lowerLimit; + adc_regs.CV1 = (int16_t)upperLimit; +#endif + } + else if (!insideRange && !inclusive) + { // True if value is outside of range and not equal to either limit. CV1 > CV2 and ACFGT=0 +// ADC_SC2_cfgt = 0; +#ifdef ADC_TEENSY_4 + atomic::clearBitFlag(adc_regs.GC, ADC_GC_ACFGT); + adc_regs.CV = ADC_CV_CV1(lowerLimit) | ADC_CV_CV2(upperLimit); +#else + atomic::clearBitFlag(adc_regs.SC2, ADC_SC2_ACFGT); + adc_regs.CV1 = (int16_t)lowerLimit; + adc_regs.CV2 = (int16_t)upperLimit; +#endif + } +} + +/* Disable the compare function +* +*/ +void ADC_Module::disableCompare() +{ + +// ADC_SC2_cfe = 0; +#ifdef ADC_TEENSY_4 + atomic::clearBitFlag(adc_regs.GC, ADC_GC_ACFE); +#else + atomic::clearBitFlag(adc_regs.SC2, ADC_SC2_ACFE); +#endif +} + +#ifdef ADC_USE_PGA +/* Enables the PGA and sets the gain +* Use only for signals lower than 1.2 V +* \param gain can be 1, 2, 4, 8, 16 32 or 64 +* +*/ +void ADC_Module::enablePGA(uint8_t gain) +{ + if (calibrating) + wait_for_cal(); + + uint8_t setting; + if (gain <= 1) + { + setting = 0; + } + else if (gain <= 2) + { + setting = 1; + } + else if (gain <= 4) + { + setting = 2; + } + else if (gain <= 8) + { + setting = 3; + } + else if (gain <= 16) + { + setting = 4; + } + else if (gain <= 32) + { + setting = 5; + } + else + { // 64 + setting = 6; + } + + adc_regs.PGA = ADC_PGA_PGAEN | ADC_PGA_PGAG(setting); + pga_value = 1 << setting; +} + +/* Returns the PGA level +* PGA level = from 0 to 64 +*/ +uint8_t ADC_Module::getPGA() +{ + return pga_value; +} + +//! Disable PGA +void ADC_Module::disablePGA() +{ + // ADC_PGA_pgaen = 0; + atomic::clearBitFlag(adc_regs.PGA, ADC_PGA_PGAEN); + pga_value = 1; +} +#endif + +//////////////// INFORMATION ABOUT VALID PINS ////////////////// + +// check whether the pin is a valid analog pin +bool ADC_Module::checkPin(uint8_t pin) +{ + + if (pin > ADC_MAX_PIN) + { + return false; // all others are invalid + } + + // translate pin number to SC1A number, that also contains MUX a or b info. + const uint8_t sc1a_pin = channel2sc1a[pin]; + + // check for valid pin + if ((sc1a_pin & ADC_SC1A_CHANNELS) == ADC_SC1A_PIN_INVALID) + { + return false; // all others are invalid + } + + return true; +} + +#if ADC_DIFF_PAIRS > 0 +// check whether the pins are a valid analog differential pins (including PGA if enabled) +bool ADC_Module::checkDifferentialPins(uint8_t pinP, uint8_t pinN) +{ + if (pinP > ADC_MAX_PIN) + { + return false; // all others are invalid + } + + // translate pinP number to SC1A number, to make sure it's differential + uint8_t sc1a_pin = channel2sc1a[pinP]; + + if (!(sc1a_pin & ADC_SC1A_PIN_DIFF)) + { + return false; // all others are invalid + } + + // get SC1A number, also whether it can do PGA + sc1a_pin = getDifferentialPair(pinP); + + // the pair can't be measured with this ADC + if ((sc1a_pin & ADC_SC1A_CHANNELS) == ADC_SC1A_PIN_INVALID) + { + return false; // all others are invalid + } + +#ifdef ADC_USE_PGA + // check if PGA is enabled, and whether the pin has access to it in this ADC module + if (isPGAEnabled() && !(sc1a_pin & ADC_SC1A_PIN_PGA)) + { + return false; + } +#endif // ADC_USE_PGA + + return true; +} +#endif + +//////////////// HELPER METHODS FOR CONVERSION ///////////////// + +// Starts a single-ended conversion on the pin (sets the mux correctly) +// Doesn't do any of the checks on the pin +// It doesn't change the continuous conversion bit +void ADC_Module::startReadFast(uint8_t pin) +{ + + // translate pin number to SC1A number, that also contains MUX a or b info. + const uint8_t sc1a_pin = channel2sc1a[pin]; + +#ifdef ADC_TEENSY_4 +// Teensy 4 has no a or b channels +#else + if (sc1a_pin & ADC_SC1A_PIN_MUX) + { // mux a + atomic::clearBitFlag(adc_regs.CFG2, ADC_CFG2_MUXSEL); + } + else + { // mux b + atomic::setBitFlag(adc_regs.CFG2, ADC_CFG2_MUXSEL); + } +#endif + + // select pin for single-ended mode and start conversion, enable interrupts if requested + __disable_irq(); +#ifdef ADC_TEENSY_4 + adc_regs.HC0 = (sc1a_pin & ADC_SC1A_CHANNELS) + interrupts_enabled * ADC_HC_AIEN; +#else + adc_regs.SC1A = (sc1a_pin & ADC_SC1A_CHANNELS) + atomic::getBitFlag(adc_regs.SC1A, ADC_SC1_AIEN) * ADC_SC1_AIEN; +#endif + __enable_irq(); +} + +#if ADC_DIFF_PAIRS > 0 +// Starts a differential conversion on the pair of pins +// Doesn't do any of the checks on the pins +// It doesn't change the continuous conversion bit +void ADC_Module::startDifferentialFast(uint8_t pinP, uint8_t pinN) +{ + + // get SC1A number + uint8_t sc1a_pin = getDifferentialPair(pinP); + +#ifdef ADC_USE_PGA + // check if PGA is enabled + if (isPGAEnabled()) + { + sc1a_pin = 0x2; // PGA always uses DAD2 + } +#endif // ADC_USE_PGA + + __disable_irq(); + adc_regs.SC1A = ADC_SC1_DIFF + (sc1a_pin & ADC_SC1A_CHANNELS) + atomic::getBitFlag(adc_regs.SC1A, ADC_SC1_AIEN) * ADC_SC1_AIEN; + __enable_irq(); +} +#endif + +//////////////// BLOCKING CONVERSION METHODS ////////////////// +/* + This methods are implemented like this: + + 1. Check that the pin is correct + 2. if calibrating, wait for it to finish before modifiying any ADC register + 3. Check if we're interrupting a measurement, if so store the settings. + 4. Disable continuous conversion mode and start the current measurement + 5. Wait until it's done, and check whether the comparison (if any) was succesful. + 6. Get the result. + 7. If step 3. is true, restore the previous ADC settings + +*/ + +/* Reads the analog value of the pin. +* It waits until the value is read and then returns the result. +* If a comparison has been set up and fails, it will return ADC_ERROR_VALUE. +* Set the resolution, number of averages and voltage reference using the appropriate functions. +*/ +int ADC_Module::analogRead(uint8_t pin) +{ + + //digitalWriteFast(LED_BUILTIN, HIGH); + + // check whether the pin is correct + if (!checkPin(pin)) + { + fail_flag |= ADC_ERROR::WRONG_PIN; + return ADC_ERROR_VALUE; + } + + // increase the counter of measurements + num_measurements++; + + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN)); + + if (calibrating) + wait_for_cal(); + + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN)); + + // check if we are interrupting a measurement, store setting if so. + // vars to save the current state of the ADC in case it's in use + ADC_Config old_config = {}; + const uint8_t wasADCInUse = isConverting(); // is the ADC running now? + + if (wasADCInUse) + { // this means we're interrupting a conversion + // save the current conversion config, we don't want any other interrupts messing up the configs + __disable_irq(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + saveConfig(&old_config); + __enable_irq(); + } + + // no continuous mode + singleMode(); + + startReadFast(pin); // start single read + + // wait for the ADC to finish + while (isConverting()) + { + yield(); + } + + // it's done, check if the comparison (if any) was true + int32_t result; + __disable_irq(); // make sure nothing interrupts this part + if (isComplete()) + { // conversion succeded + result = (uint16_t)readSingle(); + } + else + { // comparison was false + fail_flag |= ADC_ERROR::COMPARISON; + result = ADC_ERROR_VALUE; + } + __enable_irq(); + + // if we interrupted a conversion, set it again + if (wasADCInUse) + { + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + __disable_irq(); + loadConfig(&old_config); + __enable_irq(); + } + + num_measurements--; + return result; + +} // analogRead + +#if ADC_DIFF_PAIRS > 0 +/* Reads the differential analog value of two pins (pinP - pinN) +* It waits until the value is read and then returns the result +* If a comparison has been set up and fails, it will return ADC_ERROR_DIFF_VALUE +* Set the resolution, number of averages and voltage reference using the appropriate functions +*/ +int ADC_Module::analogReadDifferential(uint8_t pinP, uint8_t pinN) +{ + + if (!checkDifferentialPins(pinP, pinN)) + { + fail_flag |= ADC_ERROR::WRONG_PIN; + return ADC_ERROR_VALUE; // all others are invalid + } + + // increase the counter of measurements + num_measurements++; + + // check for calibration before setting channels, + // because conversion will start as soon as we write to adc_regs.SC1A + if (calibrating) + wait_for_cal(); + + uint8_t res = getResolution(); + + // vars to saved the current state of the ADC in case it's in use + ADC_Config old_config = {}; + uint8_t wasADCInUse = isConverting(); // is the ADC running now? + + if (wasADCInUse) + { // this means we're interrupting a conversion + // save the current conversion config, we don't want any other interrupts messing up the configs + __disable_irq(); + saveConfig(&old_config); + __enable_irq(); + } + + // no continuous mode + singleMode(); + + startDifferentialFast(pinP, pinN); // start conversion + + // wait for the ADC to finish + while (isConverting()) + { + yield(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); + } + + // it's done, check if the comparison (if any) was true + int32_t result; + __disable_irq(); // make sure nothing interrupts this part + if (isComplete()) + { // conversion succeded + result = (int16_t)(int32_t)readSingle(); // cast to 32 bits + if (res == 16) + { // 16 bit differential is actually 15 bit + 1 bit sign + result *= 2; // multiply by 2 as if it were really 16 bits, so that getMaxValue gives a correct value. + } + } + else + { // comparison was false + result = ADC_ERROR_VALUE; + fail_flag |= ADC_ERROR::COMPARISON; + } + __enable_irq(); + + // if we interrupted a conversion, set it again + if (wasADCInUse) + { + __disable_irq(); + loadConfig(&old_config); + __enable_irq(); + } + + num_measurements--; + return result; + +} // analogReadDifferential +#endif + +/////////////// NON-BLOCKING CONVERSION METHODS ////////////// +/* + This methods are implemented like this: + + 1. Check that the pin is correct + 2. if calibrating, wait for it to finish before modifiying any ADC register + 3. Check if we're interrupting a measurement, if so store the settings (in a member of the class, so it can be accessed). + 4. Disable continuous conversion mode and start the current measurement + + The fast methods only do step 4. + +*/ + +/* Starts an analog measurement on the pin. +* It returns inmediately, read value with readSingle(). +* If the pin is incorrect it returns false. +*/ +bool ADC_Module::startSingleRead(uint8_t pin) +{ + + // check whether the pin is correct + if (!checkPin(pin)) + { + fail_flag |= ADC_ERROR::WRONG_PIN; + return false; + } + + if (calibrating) + wait_for_cal(); + + // save the current state of the ADC in case it's in use + adcWasInUse = isConverting(); // is the ADC running now? + + if (adcWasInUse) + { // this means we're interrupting a conversion + // save the current conversion config, the adc isr will restore the adc + __disable_irq(); + saveConfig(&adc_config); + __enable_irq(); + } + + // no continuous mode + singleMode(); + + // start measurement + startReadFast(pin); + + return true; +} + +#if ADC_DIFF_PAIRS > 0 +/* Start a differential conversion between two pins (pinP - pinN). +* It returns inmediately, get value with readSingle(). +* Incorrect pins will return false. +* Set the resolution, number of averages and voltage reference using the appropriate functions +*/ +bool ADC_Module::startSingleDifferential(uint8_t pinP, uint8_t pinN) +{ + + if (!checkDifferentialPins(pinP, pinN)) + { + fail_flag |= ADC_ERROR::WRONG_PIN; + return false; // all others are invalid + } + + // check for calibration before setting channels, + // because conversion will start as soon as we write to adc_regs.SC1A + if (calibrating) + wait_for_cal(); + + // vars to saved the current state of the ADC in case it's in use + adcWasInUse = isConverting(); // is the ADC running now? + + if (adcWasInUse) + { // this means we're interrupting a conversion + // save the current conversion config, we don't want any other interrupts messing up the configs + __disable_irq(); + saveConfig(&adc_config); + __enable_irq(); + } + + // no continuous mode + singleMode(); + + // start the conversion + startDifferentialFast(pinP, pinN); + + return true; +} +#endif + +///////////// CONTINUOUS CONVERSION METHODS //////////// +/* + This methods are implemented like this: + + 1. Check that the pin is correct + 2. If calibrating, wait for it to finish before modifiying any ADC register + 4. Enable continuous conversion mode and start the current measurement + +*/ + +/* Starts continuous conversion on the pin + * It returns as soon as the ADC is set, use analogReadContinuous() to read the values + * Set the resolution, number of averages and voltage reference using the appropriate functions BEFORE calling this function +*/ +bool ADC_Module::startContinuous(uint8_t pin) +{ + + // check whether the pin is correct + if (!checkPin(pin)) + { + fail_flag |= ADC_ERROR::WRONG_PIN; + return false; + } + + // check for calibration before setting channels, + if (calibrating) + wait_for_cal(); + + // increase the counter of measurements + num_measurements++; + + // set continuous conversion flag + continuousMode(); + + startReadFast(pin); + + return true; +} + +#if ADC_DIFF_PAIRS > 0 +/* Starts continuous and differential conversion between the pins (pinP-pinN) + * It returns as soon as the ADC is set, use analogReadContinuous() to read the value + * Set the resolution, number of averages and voltage reference using the appropriate functions BEFORE calling this function +*/ +bool ADC_Module::startContinuousDifferential(uint8_t pinP, uint8_t pinN) +{ + + if (!checkDifferentialPins(pinP, pinN)) + { + fail_flag |= ADC_ERROR::WRONG_PIN; + return false; // all others are invalid + } + + // increase the counter of measurements + num_measurements++; + + // check for calibration before setting channels, + // because conversion will start as soon as we write to adc_regs.SC1A + if (calibrating) + wait_for_cal(); + + // save the current state of the ADC in case it's in use + uint8_t wasADCInUse = isConverting(); // is the ADC running now? + + if (wasADCInUse) + { // this means we're interrupting a conversion + // save the current conversion config, we don't want any other interrupts messing up the configs + __disable_irq(); + saveConfig(&adc_config); + __enable_irq(); + } + + // set continuous mode + continuousMode(); + + // start conversions + startDifferentialFast(pinP, pinN); + + return true; +} +#endif + +/* Stops continuous conversion +*/ +void ADC_Module::stopContinuous() +{ + +// set channel select to all 1's (31) to stop it. +#ifdef ADC_TEENSY_4 + adc_regs.HC0 = ADC_SC1A_PIN_INVALID + interrupts_enabled * ADC_HC_AIEN; +#else + adc_regs.SC1A = ADC_SC1A_PIN_INVALID + atomic::getBitFlag(adc_regs.SC1A, ADC_SC1_AIEN) * ADC_SC1_AIEN; +#endif + + // decrease the counter of measurements (unless it's 0) + if (num_measurements > 0) + { + num_measurements--; + } + + return; +} + +//////////// FREQUENCY METHODS //////// + +//////////// PDB //////////////// +#ifdef ADC_USE_PDB + +// frequency in Hz +void ADC_Module::startPDB(uint32_t freq) +{ + if (!(SIM_SCGC6 & SIM_SCGC6_PDB)) + { // setup PDB + SIM_SCGC6 |= SIM_SCGC6_PDB; // enable pdb clock + } + + if (freq > ADC_F_BUS) + return; // too high + if (freq < 1) + return; // too low + + // mod will have to be a 16 bit value + // we detect if it's higher than 0xFFFF and scale it back accordingly. + uint32_t mod = (ADC_F_BUS / freq); + + uint8_t prescaler = 0; // from 0 to 7: factor of 1, 2, 4, 8, 16, 32, 64 or 128 + uint8_t mult = 0; // from 0 to 3, factor of 1, 10, 20 or 40 + + // if mod is too high we need to use prescaler and mult to bring it down to a 16 bit number + const uint32_t min_level = 0xFFFF; + if (mod > min_level) + { + if (mod < 2 * min_level) + { + prescaler = 1; + } + else if (mod < 4 * min_level) + { + prescaler = 2; + } + else if (mod < 8 * min_level) + { + prescaler = 3; + } + else if (mod < 10 * min_level) + { + mult = 1; + } + else if (mod < 16 * min_level) + { + prescaler = 4; + } + else if (mod < 20 * min_level) + { + mult = 2; + } + else if (mod < 32 * min_level) + { + prescaler = 5; + } + else if (mod < 40 * min_level) + { + mult = 3; + } + else if (mod < 64 * min_level) + { + prescaler = 6; + } + else if (mod < 128 * min_level) + { + prescaler = 7; + } + else if (mod < 160 * min_level) + { // 16*10 + prescaler = 4; + mult = 1; + } + else if (mod < 320 * min_level) + { // 16*20 + prescaler = 4; + mult = 2; + } + else if (mod < 640 * min_level) + { // 16*40 + prescaler = 4; + mult = 3; + } + else if (mod < 1280 * min_level) + { // 32*40 + prescaler = 5; + mult = 3; + } + else if (mod < 2560 * min_level) + { // 64*40 + prescaler = 6; + mult = 3; + } + else if (mod < 5120 * min_level) + { // 128*40 + prescaler = 7; + mult = 3; + } + else + { // frequency too low + return; + } + + mod >>= prescaler; + if (mult > 0) + { + mod /= 10; + mod >>= (mult - 1); + } + } + + setHardwareTrigger(); // trigger ADC with hardware + + // software trigger enable PDB PDB interrupt continuous mode load immediately + constexpr uint32_t ADC_PDB_CONFIG = PDB_SC_TRGSEL(15) | PDB_SC_PDBEN | PDB_SC_PDBIE | PDB_SC_CONT | PDB_SC_LDMOD(0); + + constexpr uint32_t PDB_CHnC1_TOS_1 = 0x0100; + constexpr uint32_t PDB_CHnC1_EN_1 = 0x01; + + PDB0_IDLY = 1; // the pdb interrupt happens when IDLY is equal to CNT+1 + + PDB0_MOD = (uint16_t)(mod - 1); + + PDB0_SC = ADC_PDB_CONFIG | PDB_SC_PRESCALER(prescaler) | PDB_SC_MULT(mult) | PDB_SC_LDOK; // load all new values + + PDB0_SC = ADC_PDB_CONFIG | PDB_SC_PRESCALER(prescaler) | PDB_SC_MULT(mult) | PDB_SC_SWTRIG; // start the counter! + + PDB0_CHnC1 = PDB_CHnC1_TOS_1 | PDB_CHnC1_EN_1; // enable pretrigger 0 (SC1A) + + //NVIC_ENABLE_IRQ(IRQ_PDB); +} + +void ADC_Module::stopPDB() +{ + if (!(SIM_SCGC6 & SIM_SCGC6_PDB)) + { // if PDB clock wasn't on, return + setSoftwareTrigger(); + return; + } + PDB0_SC = 0; + setSoftwareTrigger(); + + //NVIC_DISABLE_IRQ(IRQ_PDB); +} + +//! Return the PDB's frequency +uint32_t ADC_Module::getPDBFrequency() +{ + const uint32_t mod = (uint32_t)PDB0_MOD; + const uint8_t prescaler = (PDB0_SC & 0x7000) >> 12; + const uint8_t mult = (PDB0_SC & 0xC) >> 2; + + const uint32_t freq = uint32_t((mod + 1) << (prescaler)) * uint32_t((mult == 0) ? 1 : 10 << (mult - 1)); + return ADC_F_BUS / freq; +} + +#endif + +#ifdef ADC_USE_QUAD_TIMER +// TODO: Add support for Teensy 3.x Quad timer +#if defined(ADC_TEENSY_4) // only supported by Teensy 4... +// try to use some teensy core functions... +// mainly out of pwm.c +extern "C" +{ + extern void xbar_connect(unsigned int input, unsigned int output); + extern void quadtimer_init(IMXRT_TMR_t *p); + extern void quadtimerWrite(IMXRT_TMR_t *p, unsigned int submodule, uint16_t val); + extern void quadtimerFrequency(IMXRT_TMR_t *p, unsigned int submodule, float frequency); +} + +void ADC_Module::startQuadTimer(uint32_t freq) +{ + // First lets setup the XBAR + CCM_CCGR2 |= CCM_CCGR2_XBAR1(CCM_CCGR_ON); //turn clock on for xbara1 + xbar_connect(XBAR_IN, XBAR_OUT); + + // Update the ADC + uint8_t adc_pin_channel = adc_regs.HC0 & 0x1f; // remember the trigger that was set + setHardwareTrigger(); // set the hardware trigger + adc_regs.HC0 = (adc_regs.HC0 & ~0x1f) | 16; // ADC_ETC channel remember other states... + singleMode(); // make sure continuous is turned off as you want the trigger to di it. + + // setup adc_etc - BUGBUG have not used the preset values yet. + if (IMXRT_ADC_ETC.CTRL & ADC_ETC_CTRL_SOFTRST) + { // SOFTRST + // Soft reset + atomic::clearBitFlag(IMXRT_ADC_ETC.CTRL, ADC_ETC_CTRL_SOFTRST); + delay(5); // give some time to be sure it is init + } + if (ADC_num == 0) + { // BUGBUG - in real code, should probably know we init ADC or not.. + IMXRT_ADC_ETC.CTRL |= + (ADC_ETC_CTRL_TSC_BYPASS | ADC_ETC_CTRL_DMA_MODE_SEL | ADC_ETC_CTRL_TRIG_ENABLE(1 << ADC_ETC_TRIGGER_INDEX)); // 0x40000001; // start with trigger 0 + IMXRT_ADC_ETC.TRIG[ADC_ETC_TRIGGER_INDEX].CTRL = ADC_ETC_TRIG_CTRL_TRIG_CHAIN(0); // chainlength -1 only us + IMXRT_ADC_ETC.TRIG[ADC_ETC_TRIGGER_INDEX].CHAIN_1_0 = + ADC_ETC_TRIG_CHAIN_IE0(1) /*| ADC_ETC_TRIG_CHAIN_B2B0 */ + | ADC_ETC_TRIG_CHAIN_HWTS0(1) | ADC_ETC_TRIG_CHAIN_CSEL0(adc_pin_channel); + + if (interrupts_enabled) + { + // Not sure yet? + } + if (adc_regs.GC & ADC_GC_DMAEN) + { + IMXRT_ADC_ETC.DMA_CTRL |= ADC_ETC_DMA_CTRL_TRIQ_ENABLE(ADC_ETC_TRIGGER_INDEX); + } + } + else + { + // This is our second one... Try second trigger? + // Remove the BYPASS? + IMXRT_ADC_ETC.CTRL &= ~(ADC_ETC_CTRL_TSC_BYPASS); // 0x40000001; // start with trigger 0 + IMXRT_ADC_ETC.CTRL |= ADC_ETC_CTRL_DMA_MODE_SEL | ADC_ETC_CTRL_TRIG_ENABLE(1 << ADC_ETC_TRIGGER_INDEX); // Add trigger + IMXRT_ADC_ETC.TRIG[ADC_ETC_TRIGGER_INDEX].CTRL = ADC_ETC_TRIG_CTRL_TRIG_CHAIN(0); // chainlength -1 only us + IMXRT_ADC_ETC.TRIG[ADC_ETC_TRIGGER_INDEX].CHAIN_1_0 = + ADC_ETC_TRIG_CHAIN_IE0(1) /*| ADC_ETC_TRIG_CHAIN_B2B0 */ + | ADC_ETC_TRIG_CHAIN_HWTS0(1) | ADC_ETC_TRIG_CHAIN_CSEL0(adc_pin_channel); + + if (adc_regs.GC & ADC_GC_DMAEN) + { + IMXRT_ADC_ETC.DMA_CTRL |= ADC_ETC_DMA_CTRL_TRIQ_ENABLE(ADC_ETC_TRIGGER_INDEX); + } + } + + // Now init the QTimer. + // Extracted from quadtimer_init in pwm.c but only the one channel... + // Maybe see if we have to do this every time we call this. But how often is that? + IMXRT_TMR4.CH[QTIMER4_INDEX].CTRL = 0; // stop timer + IMXRT_TMR4.CH[QTIMER4_INDEX].CNTR = 0; + IMXRT_TMR4.CH[QTIMER4_INDEX].SCTRL = TMR_SCTRL_OEN | TMR_SCTRL_OPS | TMR_SCTRL_VAL | TMR_SCTRL_FORCE; + IMXRT_TMR4.CH[QTIMER4_INDEX].CSCTRL = TMR_CSCTRL_CL1(1) | TMR_CSCTRL_ALT_LOAD; + // COMP must be less than LOAD - otherwise output is always low + IMXRT_TMR4.CH[QTIMER4_INDEX].LOAD = 24000; // low time (65537 - x) - + IMXRT_TMR4.CH[QTIMER4_INDEX].COMP1 = 0; // high time (0 = always low, max = LOAD-1) + IMXRT_TMR4.CH[QTIMER4_INDEX].CMPLD1 = 0; + IMXRT_TMR4.CH[QTIMER4_INDEX].CTRL = TMR_CTRL_CM(1) | TMR_CTRL_PCS(8) | + TMR_CTRL_LENGTH | TMR_CTRL_OUTMODE(6); + + quadtimerFrequency(&IMXRT_TMR4, QTIMER4_INDEX, freq); + quadtimerWrite(&IMXRT_TMR4, QTIMER4_INDEX, 5); +} + +//! Stop the PDB +void ADC_Module::stopQuadTimer() +{ + quadtimerWrite(&IMXRT_TMR4, QTIMER4_INDEX, 0); + setSoftwareTrigger(); +} + +//! Return the PDB's frequency +uint32_t ADC_Module::getQuadTimerFrequency() +{ + // Can I reverse the calculations of quad timer set frequency? + uint32_t high = IMXRT_TMR4.CH[QTIMER4_INDEX].CMPLD1; + uint32_t low = 65537 - IMXRT_TMR4.CH[QTIMER4_INDEX].LOAD; + uint32_t highPlusLow = high + low; // + if (highPlusLow == 0) + return 0; // + + uint8_t pcs = (IMXRT_TMR4.CH[QTIMER4_INDEX].CTRL >> 9) & 0x7; + uint32_t freq = (F_BUS_ACTUAL >> pcs) / highPlusLow; + //Serial.printf("ADC_Module::getTimerFrequency H:%u L:%u H+L=%u pcs:%u freq:%u\n", high, low, highPlusLow, pcs, freq); + return freq; +} + +#endif // Teensy 4 +#endif // ADC_USE_QUAD_TIMER \ No newline at end of file diff --git a/Firmware_V3/lib/ADC/ADC_Module.h b/Firmware_V3/lib/ADC/ADC_Module.h new file mode 100644 index 0000000..5ea245f --- /dev/null +++ b/Firmware_V3/lib/ADC/ADC_Module.h @@ -0,0 +1,762 @@ +/* Teensy 4.x, 3.x, LC ADC library + * https://github.com/pedvide/ADC + * Copyright (c) 2020 Pedro Villanueva + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* ADC_Module.h: Declarations of the fuctions of a Teensy 3.x, LC ADC module + * + */ + +/*! \page adc_module ADC Module +Control each ADC_Module independently. +See the ADC_Module class for all methods. +*/ + +#ifndef ADC_MODULE_H +#define ADC_MODULE_H + +#include +#include +#include + +using ADC_Error::ADC_ERROR; +using namespace ADC_settings; + +// debug mode: blink the led light +#define ADC_debug 0 + +/** Class ADC_Module: Implements all functions of the Teensy 3.x, LC analog to digital converter +* +*/ +class ADC_Module +{ + +public: +#if ADC_DIFF_PAIRS > 0 + //! \cond internal + //! Dictionary with the differential pins as keys and the SC1A number as values + /** Internal, do not use. + */ + struct ADC_NLIST + { + //! Pin and corresponding SC1A value. + uint8_t pin, sc1a; + }; +#endif + //! \endcond + +#if ADC_DIFF_PAIRS > 0 + //! Constructor + /** Pass the ADC number and the Channel number to SC1A number arrays. + * \param ADC_number Number of the ADC module, from 0. + * \param a_channel2sc1a contains an index that pairs each pin to its SC1A number (used to start a conversion on that pin) + * \param a_diff_table is similar to a_channel2sc1a, but for differential pins. + * \param a_adc_regs pointer to start of the ADC registers + */ + ADC_Module(uint8_t ADC_number, + const uint8_t *const a_channel2sc1a, + const ADC_NLIST *const a_diff_table, + ADC_REGS_t &a_adc_regs); +#else + //! Constructor + /** Pass the ADC number and the Channel number to SC1A number arrays. + * \param ADC_number Number of the ADC module, from 0. + * \param a_channel2sc1a contains an index that pairs each pin to its SC1A number (used to start a conversion on that pin) + * \param a_adc_regs pointer to start of the ADC registers + */ + ADC_Module(uint8_t ADC_number, + const uint8_t *const a_channel2sc1a, + ADC_REGS_t &a_adc_regs); +#endif + + //! Starts the calibration sequence, waits until it's done and writes the results + /** Usually it's not necessary to call this function directly, but do it if the "environment" changed + * significantly since the program was started. + */ + void recalibrate(); + + //! Starts the calibration sequence + void calibrate(); + + //! Waits until calibration is finished and writes the corresponding registers + void wait_for_cal(); + + /////////////// METHODS TO SET/GET SETTINGS OF THE ADC //////////////////// + + //! Set the voltage reference you prefer, default is vcc + /*! + * \param ref_type can be ADC_REFERENCE::REF_3V3, ADC_REFERENCE::REF_1V2 (not for Teensy LC) or ADC_REFERENCE::REF_EXT + * + * It recalibrates at the end. + */ + void setReference(ADC_REFERENCE ref_type); + + //! Change the resolution of the measurement. + /*! + * \param bits is the number of bits of resolution. + * For single-ended measurements: 8, 10, 12 or 16 bits. + * For differential measurements: 9, 11, 13 or 16 bits. + * If you want something in between (11 bits single-ended for example) select the immediate higher + * and shift the result one to the right. + * + * Whenever you change the resolution, change also the comparison values (if you use them). + */ + void setResolution(uint8_t bits); + + //! Returns the resolution of the ADC_Module. + /** + * \return the resolution of the ADC_Module. + */ + uint8_t getResolution(); + + //! Returns the maximum value for a measurement: 2^res-1. + /** + * \return the maximum value for a measurement: 2^res-1. + */ + uint32_t getMaxValue(); + + //! Sets the conversion speed (changes the ADC clock, ADCK) + /** + * \param speed can be any from the ADC_CONVERSION_SPEED enum: VERY_LOW_SPEED, LOW_SPEED, MED_SPEED, HIGH_SPEED_16BITS, HIGH_SPEED, VERY_HIGH_SPEED, + * ADACK_2_4, ADACK_4_0, ADACK_5_2 or ADACK_6_2. + * + * VERY_LOW_SPEED is guaranteed to be the lowest possible speed within specs for resolutions less than 16 bits (higher than 1 MHz), + * it's different from LOW_SPEED only for 24, 4 or 2 MHz bus frequency. + * LOW_SPEED is guaranteed to be the lowest possible speed within specs for all resolutions (higher than 2 MHz). + * MED_SPEED is always >= LOW_SPEED and <= HIGH_SPEED. + * HIGH_SPEED_16BITS is guaranteed to be the highest possible speed within specs for all resolutions (lower or eq than 12 MHz). + * HIGH_SPEED is guaranteed to be the highest possible speed within specs for resolutions less than 16 bits (lower or eq than 18 MHz). + * VERY_HIGH_SPEED may be out of specs, it's different from HIGH_SPEED only for 48, 40 or 24 MHz bus frequency. + * + * Additionally the conversion speed can also be ADACK_2_4, ADACK_4_0, ADACK_5_2 and ADACK_6_2, + * where the numbers are the frequency of the ADC clock (ADCK) in MHz and are independent on the bus speed. + * This is useful if you are using the Teensy at a very low clock frequency but want faster conversions, + * but if F_BUS= compValue (greaterThan=1) or < compValue (greaterThan=0) + * Call it after changing the resolution + * Use with interrupts or poll conversion completion with isComplete() + * \param compValue value to compare + * \param greaterThan true or false + */ + void enableCompare(int16_t compValue, bool greaterThan); + + //! Enable the compare function to a range + /** A conversion will be completed only when the ADC value is inside (insideRange=1) or outside (=0) + * the range given by (lowerLimit, upperLimit),including (inclusive=1) the limits or not (inclusive=0). + * See Table 31-78, p. 617 of the freescale manual. + * Call it after changing the resolution + * Use with interrupts or poll conversion completion with isComplete() + * \param lowerLimit lower value to compare + * \param upperLimit upper value to compare + * \param insideRange true or false + * \param inclusive true or false + */ + void enableCompareRange(int16_t lowerLimit, int16_t upperLimit, bool insideRange, bool inclusive); + + //! Disable the compare function + void disableCompare(); + +#ifdef ADC_USE_PGA + //! Enable and set PGA + /** Enables the PGA and sets the gain + * Use only for signals lower than 1.2 V and only in differential mode + * \param gain can be 1, 2, 4, 8, 16, 32 or 64 + */ + void enablePGA(uint8_t gain); + + //! Returns the PGA level + /** + * \return PGA level from 1 to 64 + */ + uint8_t getPGA(); + + //! Disable PGA + void disablePGA(); +#endif + + //! Set continuous conversion mode + void continuousMode() __attribute__((always_inline)) + { +#ifdef ADC_TEENSY_4 + atomic::setBitFlag(adc_regs.GC, ADC_GC_ADCO); +#else + atomic::setBitFlag(adc_regs.SC3, ADC_SC3_ADCO); +#endif + } + //! Set single-shot conversion mode + void singleMode() __attribute__((always_inline)) + { +#ifdef ADC_TEENSY_4 + atomic::clearBitFlag(adc_regs.GC, ADC_GC_ADCO); +#else + atomic::clearBitFlag(adc_regs.SC3, ADC_SC3_ADCO); +#endif + } + + //! Set single-ended conversion mode + void singleEndedMode() __attribute__((always_inline)) + { +#ifdef ADC_TEENSY_4 +// Teensy 4 is always single-ended +#else + atomic::clearBitFlag(adc_regs.SC1A, ADC_SC1_DIFF); +#endif + } +#if ADC_DIFF_PAIRS > 0 + //! Set differential conversion mode + void differentialMode() __attribute__((always_inline)) + { + atomic::setBitFlag(adc_regs.SC1A, ADC_SC1_DIFF); + } +#endif + + //! Use software to trigger the ADC, this is the most common setting + void setSoftwareTrigger() __attribute__((always_inline)) + { +#ifdef ADC_TEENSY_4 + atomic::clearBitFlag(adc_regs.CFG, ADC_CFG_ADTRG); +#else + atomic::clearBitFlag(adc_regs.SC2, ADC_SC2_ADTRG); +#endif + } + + //! Use hardware to trigger the ADC + void setHardwareTrigger() __attribute__((always_inline)) + { +#ifdef ADC_TEENSY_4 + atomic::setBitFlag(adc_regs.CFG, ADC_CFG_ADTRG); +#else + atomic::setBitFlag(adc_regs.SC2, ADC_SC2_ADTRG); +#endif + } + + ////////////// INFORMATION ABOUT THE STATE OF THE ADC ///////////////// + + //! Is the ADC converting at the moment? + /** + * \return true or false + */ + volatile bool isConverting() __attribute__((always_inline)) + { +#ifdef ADC_TEENSY_4 + return atomic::getBitFlag(adc_regs.GS, ADC_GS_ADACT); +#else + //return (ADC_SC2_adact); + return atomic::getBitFlag(adc_regs.SC2, ADC_SC2_ADACT); +//return ((adc_regs.SC2) & ADC_SC2_ADACT) >> 7; +#endif + } + + //! Is an ADC conversion ready? + /** + * \return true if yes, false if not. + * When a value is read this function returns false until a new value exists, + * so it only makes sense to call it before analogReadContinuous() or readSingle() + */ + volatile bool isComplete() __attribute__((always_inline)) + { +#ifdef ADC_TEENSY_4 + return atomic::getBitFlag(adc_regs.HS, ADC_HS_COCO0); +#else + //return (ADC_SC1A_coco); + return atomic::getBitFlag(adc_regs.SC1A, ADC_SC1_COCO); +//return ((adc_regs.SC1A) & ADC_SC1_COCO) >> 7; +#endif + } + +#if ADC_DIFF_PAIRS > 0 + //! Is the ADC in differential mode? + /** + * \return true or false + */ + volatile bool isDifferential() __attribute__((always_inline)) + { + //return ((adc_regs.SC1A) & ADC_SC1_DIFF) >> 5; + return atomic::getBitFlag(adc_regs.SC1A, ADC_SC1_DIFF); + } +#endif + + //! Is the ADC in continuous mode? + /** + * \return true or false + */ + volatile bool isContinuous() __attribute__((always_inline)) + { +#ifdef ADC_TEENSY_4 + return atomic::getBitFlag(adc_regs.GC, ADC_GC_ADCO); +#else + //return (ADC_SC3_adco); + return atomic::getBitFlag(adc_regs.SC3, ADC_SC3_ADCO); +//return ((adc_regs.SC3) & ADC_SC3_ADCO) >> 3; +#endif + } + +#ifdef ADC_USE_PGA + //! Is the PGA function enabled? + /** + * \return true or false + */ + volatile bool isPGAEnabled() __attribute__((always_inline)) + { + return atomic::getBitFlag(adc_regs.PGA, ADC_PGA_PGAEN); + } +#endif + + //////////////// INFORMATION ABOUT VALID PINS ////////////////// + + //! Check whether the pin is a valid analog pin + /** + * \param pin to check. + * \return true if the pin is valid, false otherwise. + */ + bool checkPin(uint8_t pin); + + //! Check whether the pins are a valid analog differential pair of pins + /** If PGA is enabled it also checks that this ADCx can use PGA on this pins + * \param pinP positive pin to check. + * \param pinN negative pin to check. + * \return true if the pin is valid, false otherwise. + */ + bool checkDifferentialPins(uint8_t pinP, uint8_t pinN); + + //////////////// HELPER METHODS FOR CONVERSION ///////////////// + + //! Starts a single-ended conversion on the pin + /** It sets the mux correctly, doesn't do any of the checks on the pin and + * doesn't change the continuous conversion bit. + * \param pin to read. + */ + void startReadFast(uint8_t pin); // helper method + +#if ADC_DIFF_PAIRS > 0 + //! Starts a differential conversion on the pair of pins + /** It sets the mux correctly, doesn't do any of the checks on the pin and + * doesn't change the continuous conversion bit. + * \param pinP positive pin to read. + * \param pinN negative pin to read. + */ + void startDifferentialFast(uint8_t pinP, uint8_t pinN); +#endif + + //////////////// BLOCKING CONVERSION METHODS ////////////////// + + //! Returns the analog value of the pin. + /** It waits until the value is read and then returns the result. + * If a comparison has been set up and fails, it will return ADC_ERROR_VALUE. + * This function is interrupt safe, so it will restore the adc to the state it was before being called + * \param pin pin to read. + * \return the value of the pin. + */ + int analogRead(uint8_t pin); + + //! Returns the analog value of the special internal source, such as the temperature sensor. + /** It calls analogRead(uint8_t pin) internally, with the correct value for the pin for all boards. + * Possible values: + * TEMP_SENSOR, Temperature sensor. + * VREF_OUT, 1.2 V reference (switch on first using VREF.h). + * BANDGAP, BANDGAP (switch on first using VREF.h). + * VREFH, High VREF. + * VREFL, Low VREF. + * \param pin ADC_INTERNAL_SOURCE to read. + * \return the value of the pin. + */ + int analogRead(ADC_INTERNAL_SOURCE pin) __attribute__((always_inline)) + { + return analogRead(static_cast(pin)); + } + +#if ADC_DIFF_PAIRS > 0 + //! Reads the differential analog value of two pins (pinP - pinN). + /** It waits until the value is read and then returns the result. + * If a comparison has been set up and fails, it will return ADC_ERROR_DIFF_VALUE. + * \param pinP must be A10 or A12. + * \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12). + * \return the difference between the pins if they are valid, othewise returns ADC_ERROR_DIFF_VALUE. + * This function is interrupt safe, so it will restore the adc to the state it was before being called + */ + int analogReadDifferential(uint8_t pinP, uint8_t pinN); +#endif + + /////////////// NON-BLOCKING CONVERSION METHODS ////////////// + + //! Starts an analog measurement on the pin and enables interrupts. + /** It returns immediately, get value with readSingle(). + * If this function interrupts a measurement, it stores the settings in adc_config + * \param pin pin to read. + * \return true if the pin is valid, false otherwise. + */ + bool startSingleRead(uint8_t pin); + +#if ADC_DIFF_PAIRS > 0 + //! Start a differential conversion between two pins (pinP - pinN) and enables interrupts. + /** It returns immediately, get value with readSingle(). + * If this function interrupts a measurement, it stores the settings in adc_config + * \param pinP must be A10 or A12. + * \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12). + * \return true if the pins are valid, false otherwise. + */ + bool startSingleDifferential(uint8_t pinP, uint8_t pinN); +#endif + + //! Reads the analog value of a single conversion. + /** Set the conversion with with startSingleRead(pin) or startSingleDifferential(pinP, pinN). + * \return the converted value. + */ + int readSingle() __attribute__((always_inline)) + { + return analogReadContinuous(); + } + + ///////////// CONTINUOUS CONVERSION METHODS //////////// + + //! Starts continuous conversion on the pin. + /** It returns as soon as the ADC is set, use analogReadContinuous() to read the value. + * \param pin can be any of the analog pins + * \return true if the pin is valid, false otherwise. + */ + bool startContinuous(uint8_t pin); + +#if ADC_DIFF_PAIRS > 0 + //! Starts continuous conversion between the pins (pinP-pinN). + /** It returns as soon as the ADC is set, use analogReadContinuous() to read the value. + * \param pinP must be A10 or A12. + * \param pinN must be A11 (if pinP=A10) or A13 (if pinP=A12). + * \return true if the pins are valid, false otherwise. + */ + bool startContinuousDifferential(uint8_t pinP, uint8_t pinN); +#endif + + //! Reads the analog value of a continuous conversion. + /** Set the continuous conversion with with analogStartContinuous(pin) or startContinuousDifferential(pinP, pinN). + * \return the last converted value. + * If single-ended and 16 bits it's necessary to typecast it to an unsigned type (like uint16_t), + * otherwise values larger than 3.3/2 V are interpreted as negative! + */ + int analogReadContinuous() __attribute__((always_inline)) + { +#ifdef ADC_TEENSY_4 + return (int16_t)(int32_t)adc_regs.R0; +#else + return (int16_t)(int32_t)adc_regs.RA; +#endif + } + + //! Stops continuous conversion + void stopContinuous(); + +//////////// FREQUENCY METHODS //////// +// The general API is: +// void startTimer(uint32_t freq) +// void stopTimer() +// uint32_t getTimerFrequency() +// For each board the best timer method will be selected + +//////////// PDB //////////////// +//// Only works for Teensy 3.x not LC nor Tensy 4.0 (they don't have PDB) +#if defined(ADC_USE_PDB) + + //! Start the default timer (PDB) triggering the ADC at the frequency + /** The default timer in this board is the PDB, you can also call it directly with startPDB(). + * Call startSingleRead or startSingleDifferential on the pin that you want to measure before calling this function. + * See the example adc_pdb.ino. + * \param freq is the frequency of the ADC conversion, it can't be lower that 1 Hz + */ + void startTimer(uint32_t freq) __attribute__((always_inline)) { startPDB(freq); } + //! Start PDB triggering the ADC at the frequency + /** Call startSingleRead or startSingleDifferential on the pin that you want to measure before calling this function. + * See the example adc_pdb.ino. + * \param freq is the frequency of the ADC conversion, it can't be lower that 1 Hz + */ + void startPDB(uint32_t freq); + + //! Stop the default timer (PDB) + void stopTimer() __attribute__((always_inline)) { stopPDB(); } + //! Stop the PDB + void stopPDB(); + + //! Return the default timer's (PDB) frequency + /** The default timer in this board is the PDB, you can also call it directly with getPDBFrequency(). + * \return the timer's frequency in Hz. + */ + uint32_t getTimerFrequency() __attribute__((always_inline)) { return getPDBFrequency(); } + //! Return the PDB's frequency + /** Return the PDB's frequency + * \return the timer's frequency in Hz. + */ + uint32_t getPDBFrequency(); + +//////////// TIMER //////////////// +//// Only works for Teensy 3.x and 4 (not LC) +#elif defined(ADC_USE_QUAD_TIMER) + //! Start the default timer (QuadTimer) triggering the ADC at the frequency + /** The default timer in this board is the QuadTimer, you can also call it directly with startQuadTimer(). + * Call startSingleRead or startSingleDifferential on the pin that you want to measure before calling this function. + * See the example adc_timer.ino. + * \param freq is the frequency of the ADC conversion, it can't be lower that 1 Hz + */ + void startTimer(uint32_t freq) __attribute__((always_inline)) { startQuadTimer(freq); } + //! Start a Quad timer to trigger the ADC at the frequency + /** Call startSingleRead or startSingleDifferential on the pin that you want to measure before calling this function. + * See the example adc_timer.ino. + * \param freq is the frequency of the ADC conversion, it can't be lower that 1 Hz + */ + void startQuadTimer(uint32_t freq); + + //! Stop the default timer (QuadTimer) + void stopTimer() __attribute__((always_inline)) { stopQuadTimer(); } + //! Stop the Quad timer + void stopQuadTimer(); + + //! Return the default timer's (QuadTimer) frequency + /** The default timer in this board is the QuadTimer, you can also call it directly with getQuadTimerFrequency(). + * \return the timer's frequency in Hz. + */ + uint32_t getTimerFrequency() __attribute__((always_inline)) { return getQuadTimerFrequency(); } + //! Return the Quad timer's frequency + /** Return the Quad timer's frequency + * \return the timer's frequency in Hz. + */ + uint32_t getQuadTimerFrequency(); +#endif + + //////// OTHER STUFF /////////// + + //! Store the config of the adc + struct ADC_Config + { +//! ADC registers +#ifdef ADC_TEENSY_4 + uint32_t savedHC0, savedCFG, savedGC, savedGS; +#else + uint32_t savedSC1A, savedSC2, savedSC3, savedCFG1, savedCFG2; +#endif + } adc_config; + + //! Was the adc in use before a call? + uint8_t adcWasInUse; + + /** Save config of the ADC to the ADC_Config struct + * \param config ADC_Config where the config will be stored + */ + void saveConfig(ADC_Config *config) + { +#ifdef ADC_TEENSY_4 + config->savedHC0 = adc_regs.HC0; + config->savedCFG = adc_regs.CFG; + config->savedGC = adc_regs.GC; + config->savedGS = adc_regs.GS; +#else + config->savedSC1A = adc_regs.SC1A; + config->savedCFG1 = adc_regs.CFG1; + config->savedCFG2 = adc_regs.CFG2; + config->savedSC2 = adc_regs.SC2; + config->savedSC3 = adc_regs.SC3; +#endif + } + + /** Load config to the ADC + * \param config ADC_Config from where the config will be loaded + */ + void loadConfig(const ADC_Config *config) + { +#ifdef ADC_TEENSY_4 + adc_regs.HC0 = config->savedHC0; + adc_regs.CFG = config->savedCFG; + adc_regs.GC = config->savedGC; + adc_regs.GS = config->savedGS; +#else + adc_regs.CFG1 = config->savedCFG1; + adc_regs.CFG2 = config->savedCFG2; + adc_regs.SC2 = config->savedSC2; + adc_regs.SC3 = config->savedSC3; + adc_regs.SC1A = config->savedSC1A; // restore last +#endif + } + + //! Number of measurements that the ADC is performing + uint8_t num_measurements; + + //! This flag indicates that some kind of error took place + /** Use the defines at the beginning of this file to find out what caused the fail. + */ + volatile ADC_ERROR fail_flag; + + //! Resets all errors from the ADC, if any. + void resetError() + { + ADC_Error::resetError(fail_flag); + } + + //! Which adc is this? + const uint8_t ADC_num; + +private: + // is set to 1 when the calibration procedure is taking place + uint8_t calibrating; + + // the first calibration will use 32 averages and lowest speed, + // when this calibration is over the averages and speed will be set to default. + uint8_t init_calib; + + // resolution + uint8_t analog_res_bits; + + // maximum value possible 2^res-1 + uint32_t analog_max_val; + + // num of averages + uint8_t analog_num_average; + + // reference can be internal or external + ADC_REF_SOURCE analog_reference_internal; + +#ifdef ADC_USE_PGA + // value of the pga + uint8_t pga_value; +#endif + + // conversion speed + ADC_CONVERSION_SPEED conversion_speed; + + // sampling speed + ADC_SAMPLING_SPEED sampling_speed; + + // translate pin number to SC1A nomenclature + const uint8_t *const channel2sc1a; + + // are interrupts on? + bool interrupts_enabled; + +// same for differential pins +#if ADC_DIFF_PAIRS > 0 + const ADC_NLIST *const diff_table; + + //! Get the SC1A value of the differential pair for this pin + uint8_t getDifferentialPair(uint8_t pin) + { + for (uint8_t i = 0; i < ADC_DIFF_PAIRS; i++) + { + if (diff_table[i].pin == pin) + { + return diff_table[i].sc1a; + } + } + return ADC_SC1A_PIN_INVALID; + } +#endif + + //! Initialize ADC + void analog_init(); + + //! Switch on clock to ADC + void startClock() + { +#if defined(ADC_TEENSY_4) + if (ADC_num == 0) + { + CCM_CCGR1 |= CCM_CCGR1_ADC1(CCM_CCGR_ON); + } + else + { + CCM_CCGR1 |= CCM_CCGR1_ADC2(CCM_CCGR_ON); + } +#else + if (ADC_num == 0) + { + SIM_SCGC6 |= SIM_SCGC6_ADC0; + } + else + { + SIM_SCGC3 |= SIM_SCGC3_ADC1; + } +#endif + } + + // registers point to the correct ADC module + typedef volatile uint32_t ® + + // registers that control the adc module + ADC_REGS_t &adc_regs; + +#ifdef ADC_USE_PDB + reg PDB0_CHnC1; // PDB channel 0 or 1 +#endif +#ifdef ADC_TEENSY_4 + uint8_t XBAR_IN; + uint8_t XBAR_OUT; + uint8_t QTIMER4_INDEX; + uint8_t ADC_ETC_TRIGGER_INDEX; +#endif + const IRQ_NUMBER_t IRQ_ADC; // IRQ number + +protected: +}; + +#endif // ADC_MODULE_H diff --git a/Firmware_V3/lib/ADC/ADC_util.h b/Firmware_V3/lib/ADC/ADC_util.h new file mode 100644 index 0000000..6aad6b8 --- /dev/null +++ b/Firmware_V3/lib/ADC/ADC_util.h @@ -0,0 +1,222 @@ +/* Teensy 4.x, 3.x, LC ADC library + * https://github.com/pedvide/ADC + * Copyright (c) 2020 Pedro Villanueva + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* util.h: Util functions for ino sketches and tests. + * This would increase the size of the ADC library because of the strings. + */ + +/*! \page util ADC util +Util functions for ino sketches and tests. +This would increase the size of the ADC library because of the strings. +See the namespace ADC_util for all functions. +*/ + +#ifndef ADC_UTIL_H +#define ADC_UTIL_H + +#include + +using ADC_Error::ADC_ERROR; +using namespace ADC_settings; + +//! Util functions for ino sketches and tests. +namespace ADC_util +{ + + //! Convert the conversion speed code to text + /** Convert the conversion speed code to text +* \param conv_speed The conversion speed code +* \return the corresponding text +*/ + const char *getConversionEnumStr(ADC_CONVERSION_SPEED conv_speed) + { + switch (conv_speed) + { +#if defined(ADC_TEENSY_4) // Teensy 4 +#else + case ADC_CONVERSION_SPEED::VERY_LOW_SPEED: + return (const char *)"VERY_LOW_SPEED"; +#endif + case ADC_CONVERSION_SPEED::LOW_SPEED: + return (const char *)"LOW_SPEED"; + case ADC_CONVERSION_SPEED::MED_SPEED: + return (const char *)"MED_SPEED"; + case ADC_CONVERSION_SPEED::HIGH_SPEED: + return (const char *)"HIGH_SPEED"; +#if defined(ADC_TEENSY_4) // Teensy 4 +#else + case ADC_CONVERSION_SPEED::VERY_HIGH_SPEED: + return (const char *)"VERY_HIGH_SPEED"; +#endif +#if defined(ADC_TEENSY_4) // Teensy 4 + case ADC_CONVERSION_SPEED::ADACK_10: + return (const char *)"ADACK_10"; + case ADC_CONVERSION_SPEED::ADACK_20: + return (const char *)"ADACK_20"; +#else + case ADC_CONVERSION_SPEED::HIGH_SPEED_16BITS: + return (const char *)"HIGH_SPEED_16BITS"; + case ADC_CONVERSION_SPEED::ADACK_2_4: + return (const char *)"ADACK_2_4"; + case ADC_CONVERSION_SPEED::ADACK_4_0: + return (const char *)"ADACK_4_0"; + case ADC_CONVERSION_SPEED::ADACK_5_2: + return (const char *)"ADACK_5_2"; + case ADC_CONVERSION_SPEED::ADACK_6_2: + return (const char *)"ADACK_6_2"; +#endif + } + return (const char *)"NONE"; + } + + //! Convert the sampling speed code to text + /** Convert the sampling speed code to text +* \param samp_speed The sampling speed code +* \return the corresponding text +*/ + const char *getSamplingEnumStr(ADC_SAMPLING_SPEED samp_speed) + { + switch (samp_speed) + { + case ADC_SAMPLING_SPEED::VERY_LOW_SPEED: + return (const char *)"VERY_LOW_SPEED"; + case ADC_SAMPLING_SPEED::LOW_SPEED: + return (const char *)"LOW_SPEED"; + case ADC_SAMPLING_SPEED::MED_SPEED: + return (const char *)"MED_SPEED"; + case ADC_SAMPLING_SPEED::HIGH_SPEED: + return (const char *)"HIGH_SPEED"; + case ADC_SAMPLING_SPEED::VERY_HIGH_SPEED: + return (const char *)"VERY_HIGH_SPEED"; +#if defined(ADC_TEENSY_4) // Teensy 4 + case ADC_SAMPLING_SPEED::LOW_MED_SPEED: + return (const char *)"LOW_MED_SPEED"; + case ADC_SAMPLING_SPEED::MED_HIGH_SPEED: + return (const char *)"MED_HIGH_SPEED"; + case ADC_SAMPLING_SPEED::HIGH_VERY_HIGH_SPEED: + return (const char *)"HIGH_VERY_HIGH_SPEED"; +#endif + } + return (const char *)"NONE"; + } + + //! Converts the error code to text. + /** Converts the error code to text. +* \param fail_flag The error code +* \return the corresponding text +*/ + const char *getStringADCError(ADC_ERROR fail_flag) + { + if (fail_flag != ADC_ERROR::CLEAR) + { + switch (fail_flag) + { + case ADC_ERROR::CALIB: + return (const char *)"Calibration"; + case ADC_ERROR::WRONG_PIN: + return (const char *)"Wrong pin"; + case ADC_ERROR::ANALOG_READ: + return (const char *)"Analog read"; + case ADC_ERROR::COMPARISON: + return (const char *)"Comparison"; + case ADC_ERROR::ANALOG_DIFF_READ: + return (const char *)"Analog differential read"; + case ADC_ERROR::CONT: + return (const char *)"Continuous read"; + case ADC_ERROR::CONT_DIFF: + return (const char *)"Continuous differential read"; + case ADC_ERROR::WRONG_ADC: + return (const char *)"Wrong ADC"; + case ADC_ERROR::SYNCH: + return (const char *)"Synchronous"; + case ADC_ERROR::OTHER: + case ADC_ERROR::CLEAR: // silence warnings + default: + return (const char *)"Unknown"; + } + } + return (const char *)""; + } + + //! List of possible averages + const uint8_t averages_list[] = {1, 4, 8, 16, 32}; + +#if defined(ADC_TEENSY_4) // Teensy 4 + //! List of possible resolutions + const uint8_t resolutions_list[] = {8, 10, 12}; +#else + //! List of possible resolutions + const uint8_t resolutions_list[] = {8, 10, 12, 16}; +#endif + +#if defined(ADC_TEENSY_4) // Teensy 4 + //! List of possible conversion speeds + const ADC_CONVERSION_SPEED conversion_speed_list[] = { + ADC_CONVERSION_SPEED::LOW_SPEED, + ADC_CONVERSION_SPEED::MED_SPEED, + ADC_CONVERSION_SPEED::HIGH_SPEED, + ADC_CONVERSION_SPEED::ADACK_10, + ADC_CONVERSION_SPEED::ADACK_20}; +#else + //! List of possible conversion speeds + const ADC_CONVERSION_SPEED conversion_speed_list[] = { + ADC_CONVERSION_SPEED::VERY_LOW_SPEED, + ADC_CONVERSION_SPEED::LOW_SPEED, + ADC_CONVERSION_SPEED::MED_SPEED, + ADC_CONVERSION_SPEED::HIGH_SPEED, + ADC_CONVERSION_SPEED::HIGH_SPEED_16BITS, + ADC_CONVERSION_SPEED::VERY_HIGH_SPEED, + ADC_CONVERSION_SPEED::ADACK_2_4, + ADC_CONVERSION_SPEED::ADACK_4_0, + ADC_CONVERSION_SPEED::ADACK_5_2, + ADC_CONVERSION_SPEED::ADACK_6_2}; +#endif + +#if defined(ADC_TEENSY_4) // Teensy 4 + //! List of possible sampling speeds + const ADC_SAMPLING_SPEED sampling_speed_list[] = { + ADC_SAMPLING_SPEED::VERY_LOW_SPEED, + ADC_SAMPLING_SPEED::LOW_SPEED, + ADC_SAMPLING_SPEED::LOW_MED_SPEED, + ADC_SAMPLING_SPEED::MED_SPEED, + ADC_SAMPLING_SPEED::MED_HIGH_SPEED, + ADC_SAMPLING_SPEED::HIGH_SPEED, + ADC_SAMPLING_SPEED::HIGH_VERY_HIGH_SPEED, + ADC_SAMPLING_SPEED::VERY_HIGH_SPEED}; +#else + //! List of possible sampling speeds + const ADC_SAMPLING_SPEED sampling_speed_list[] = { + ADC_SAMPLING_SPEED::VERY_LOW_SPEED, + ADC_SAMPLING_SPEED::LOW_SPEED, + ADC_SAMPLING_SPEED::MED_SPEED, + ADC_SAMPLING_SPEED::HIGH_SPEED, + ADC_SAMPLING_SPEED::VERY_HIGH_SPEED}; +#endif + +} // namespace ADC_util + +using namespace ADC_util; + +#endif // ADC_UTIL_H \ No newline at end of file diff --git a/Firmware_V3/lib/ADC/AnalogBufferDMA.cpp b/Firmware_V3/lib/ADC/AnalogBufferDMA.cpp new file mode 100644 index 0000000..2a11c31 --- /dev/null +++ b/Firmware_V3/lib/ADC/AnalogBufferDMA.cpp @@ -0,0 +1,281 @@ +/* Teensy 3.x, LC, 4.0 ADC library + https://github.com/pedvide/ADC + Copyright (c) 2020 Pedro Villanueva + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#include "AnalogBufferDMA.h" + +#ifdef ADC_USE_DMA + +//#define DEBUG_DUMP_DATA +// Global objects +AnalogBufferDMA *AnalogBufferDMA::_activeObjectPerADC[2] = {nullptr, nullptr}; + +#if defined(__IMXRT1062__) // Teensy 4.0 +#define SOURCE_ADC_0 ADC1_R0 +#define DMAMUX_ADC_0 DMAMUX_SOURCE_ADC1 +#define SOURCE_ADC_1 ADC2_R0 +#define DMAMUX_ADC_1 DMAMUX_SOURCE_ADC2 +#elif defined(KINETISK) +#define SOURCE_ADC_0 ADC0_RA +#define DMAMUX_ADC_0 DMAMUX_SOURCE_ADC0 +#ifdef ADC_DUAL_ADCS +#define SOURCE_ADC_1 ADC1_RA +#define DMAMUX_ADC_1 DMAMUX_SOURCE_ADC1 +#endif +#elif defined(KINETISL) +#define SOURCE_ADC_0 ADC0_RA +#define DMAMUX_ADC_0 DMAMUX_SOURCE_ADC0 +#endif + +//============================================================================= +// Debug support +//============================================================================= + +#ifdef DEBUG_DUMP_DATA +static void dumpDMA_TCD(DMABaseClass *dmabc) +{ +#ifndef KINETISL + Serial.printf("%x %x:", (uint32_t)dmabc, (uint32_t)dmabc->TCD); + + Serial.printf("SA:%x SO:%d AT:%x NB:%x SL:%d DA:%x DO: %d CI:%x DL:%x CS:%x BI:%x\n", (uint32_t)dmabc->TCD->SADDR, + dmabc->TCD->SOFF, dmabc->TCD->ATTR, dmabc->TCD->NBYTES, dmabc->TCD->SLAST, (uint32_t)dmabc->TCD->DADDR, + dmabc->TCD->DOFF, dmabc->TCD->CITER, dmabc->TCD->DLASTSGA, dmabc->TCD->CSR, dmabc->TCD->BITER); +#else + Serial.printf("%x %x:", (uint32_t)dmabc, (uint32_t)dmabc->CFG); + + Serial.printf("SAR:%x DAR:%x DSR_BCR:%x DCR:%x\n", (uint32_t)dmabc->CFG->SAR, dmabc->CFG->DAR, dmabc->CFG->DSR_BCR, + dmabc->CFG->DCR); +#endif +} +#endif + +//============================================================================= +// Init - Initialize the object including setup DMA structures +//============================================================================= +void AnalogBufferDMA::init(ADC *adc, int8_t adc_num) +{ + // enable DMA and interrupts +#ifdef DEBUG_DUMP_DATA + Serial.println("AnalogBufferDMA::init"); + Serial.flush(); +#endif + +#ifndef KINETISL + // setup a DMA Channel. + // Now lets see the different things that RingbufferDMA setup for us before + // See if we were created with one or two buffers. If one assume we stop on completion, else assume continuous. + if (_buffer2 && _buffer2_count) + { +#ifdef ADC_DUAL_ADCS + _dmasettings_adc[0].source((volatile uint16_t &)((adc_num == 1) ? SOURCE_ADC_1 : SOURCE_ADC_0)); +#else + _dmasettings_adc[0].source((volatile uint16_t &)(SOURCE_ADC_0)); +#endif + _dmasettings_adc[0].destinationBuffer((uint16_t *)_buffer1, _buffer1_count * 2); // 2*b_size is necessary for some reason + _dmasettings_adc[0].replaceSettingsOnCompletion(_dmasettings_adc[1]); // go off and use second one... + _dmasettings_adc[0].interruptAtCompletion(); //interruptAtHalf or interruptAtCompletion +#ifdef ADC_DUAL_ADCS + _dmasettings_adc[1].source((volatile uint16_t &)((adc_num == 1) ? SOURCE_ADC_1 : SOURCE_ADC_0)); + _dmasettings_adc[1].destinationBuffer((uint16_t *)_buffer2, _buffer2_count * 2); // 2*b_size is necessary for some reason + _dmasettings_adc[1].replaceSettingsOnCompletion(_dmasettings_adc[0]); // Cycle back to the first one + _dmasettings_adc[1].interruptAtCompletion(); //interruptAtHalf or interruptAtCompletion +#endif + + _dmachannel_adc = _dmasettings_adc[0]; + + _stop_on_completion = false; + } + else + { +// Only one buffer so lets just setup the dmachannel ... +// Serial.printf("AnalogBufferDMA::init Single buffer %d\n", adc_num); +#ifdef ADC_DUAL_ADCS + _dmachannel_adc.source((volatile uint16_t &)((adc_num == 1) ? SOURCE_ADC_1 : SOURCE_ADC_0)); +#else + _dmachannel_adc.source((volatile uint16_t &)(SOURCE_ADC_0)); +#endif + _dmachannel_adc.destinationBuffer((uint16_t *)_buffer1, _buffer1_count * 2); // 2*b_size is necessary for some reason + _dmachannel_adc.interruptAtCompletion(); //interruptAtHalf or interruptAtCompletion + _dmachannel_adc.disableOnCompletion(); // we will disable on completion. + _stop_on_completion = true; + } + + if (adc_num == 1) + { +#ifdef ADC_DUAL_ADCS + _activeObjectPerADC[1] = this; + _dmachannel_adc.attachInterrupt(&adc_1_dmaISR); + _dmachannel_adc.triggerAtHardwareEvent(DMAMUX_ADC_1); // start DMA channel when ADC finishes a conversion +#endif + } + else + { + _activeObjectPerADC[0] = this; + _dmachannel_adc.attachInterrupt(&adc_0_dmaISR); + _dmachannel_adc.triggerAtHardwareEvent(DMAMUX_ADC_0); // start DMA channel when ADC finishes a conversion + } + //arm_dcache_flush((void*)dmaChannel, sizeof(dmaChannel)); + _dmachannel_adc.enable(); + + adc->adc[adc_num]->continuousMode(); + adc->adc[adc_num]->enableDMA(); + +#ifdef DEBUG_DUMP_DATA + dumpDMA_TCD(&_dmachannel_adc); + dumpDMA_TCD(&_dmasettings_adc[0]); + dumpDMA_TCD(&_dmasettings_adc[1]); +#if defined(__IMXRT1062__) // Teensy 4.0 + + if (adc_num == 1) + { + Serial.printf("ADC2: HC0:%x HS:%x CFG:%x GC:%x GS:%x\n", ADC2_HC0, ADC2_HS, ADC2_CFG, ADC2_GC, ADC2_GS); + } + else + { + Serial.printf("ADC1: HC0:%x HS:%x CFG:%x GC:%x GS:%x\n", ADC1_HC0, ADC1_HS, ADC1_CFG, ADC1_GC, ADC1_GS); + } +#endif +#endif +#else + // Kinetisl (TLC) + // setup a DMA Channel. + // Now lets see the different things that RingbufferDMA setup for us before + _dmachannel_adc.source((volatile uint16_t &)(SOURCE_ADC_0)); + ; + _dmachannel_adc.destinationBuffer((uint16_t *)_buffer1, _buffer1_count * 2); // 2*b_size is necessary for some reason + _dmachannel_adc.disableOnCompletion(); // ISR will hae to restart with other buffer + _dmachannel_adc.interruptAtCompletion(); //interruptAtHalf or interruptAtCompletion + _activeObjectPerADC[0] = this; + _dmachannel_adc.attachInterrupt(&adc_0_dmaISR); + _dmachannel_adc.triggerAtHardwareEvent(DMAMUX_ADC_0); // start DMA channel when ADC finishes a conversion + _dmachannel_adc.enable(); + + adc->startContinuous(adc_num); + adc->enableDMA(adc_num); +#ifdef DEBUG_DUMP_DATA + dumpDMA_TCD(&_dmachannel_adc); +#endif + +#endif + + _last_isr_time = millis(); +} + +//============================================================================= +// stopOnCompletion: allows you to turn on or off stopping when a DMA buffer +// has completed filling. Default is on when only one buffer passed in to the +// constructor and off if two buffers passed in. +//============================================================================= +void AnalogBufferDMA::stopOnCompletion(bool stop_on_complete) +{ +#ifndef KINETISL + if (stop_on_complete) + _dmachannel_adc.TCD->CSR |= DMA_TCD_CSR_DREQ; + else + _dmachannel_adc.TCD->CSR &= ~DMA_TCD_CSR_DREQ; +#else + if (stop_on_complete) + _dmachannel_adc.CFG->DCR |= DMA_DCR_D_REQ; + else + _dmachannel_adc.CFG->DCR &= ~DMA_DCR_D_REQ; +#endif + _stop_on_completion = stop_on_complete; +} + +//============================================================================= +// ClearCompletion: if we have stop on completion, then clear the completion state +// i.e. reenable the dma operation. Note only valid if we are +// in the stopOnCompletion state. +//============================================================================= +bool AnalogBufferDMA::clearCompletion() +{ + if (!_stop_on_completion) + return false; + // should probably check to see if we are dsiable or not... + _dmachannel_adc.enable(); + return true; +} + +//============================================================================= +// processADC_DMAISR: Process the DMA completion ISR +// common for both ISRs on those processors who have more than one ADC +//============================================================================= +void AnalogBufferDMA::processADC_DMAISR() +{ + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN)); + uint32_t cur_time = millis(); + + _interrupt_count++; + _interrupt_delta_time = cur_time - _last_isr_time; + _last_isr_time = cur_time; + // update the internal buffer positions + _dmachannel_adc.clearInterrupt(); +#ifdef KINETISL + // Lets try to clear the previous interrupt, change buffers + // and restart + if (_buffer2 && (_interrupt_count & 1)) + { + _dmachannel_adc.destinationBuffer((uint16_t *)_buffer2, _buffer2_count * 2); // 2*b_size is necessary for some reason + } + else + { + _dmachannel_adc.destinationBuffer((uint16_t *)_buffer1, _buffer1_count * 2); // 2*b_size is necessary for some reason + } + + // If we are not stopping on completion, then reenable... + if (!_stop_on_completion) + _dmachannel_adc.enable(); + +#endif +} + +//============================================================================= +// adc_0_dmaISR: called for first ADC when DMA has completed filling a buffer. +//============================================================================= +void AnalogBufferDMA::adc_0_dmaISR() +{ + if (_activeObjectPerADC[0]) + { + _activeObjectPerADC[0]->processADC_DMAISR(); + } +#if defined(__IMXRT1062__) // Teensy 4.0 + asm("DSB"); +#endif +} + +//============================================================================= +// adc_1_dmaISR - Used for processors that have a second ADC object +//============================================================================= +void AnalogBufferDMA::adc_1_dmaISR() +{ + if (_activeObjectPerADC[1]) + { + _activeObjectPerADC[1]->processADC_DMAISR(); + } +#if defined(__IMXRT1062__) // Teensy 4.0 + asm("DSB"); +#endif +} + +#endif // ADC_USE_DMA diff --git a/Firmware_V3/lib/ADC/AnalogBufferDMA.h b/Firmware_V3/lib/ADC/AnalogBufferDMA.h new file mode 100644 index 0000000..efe784b --- /dev/null +++ b/Firmware_V3/lib/ADC/AnalogBufferDMA.h @@ -0,0 +1,83 @@ +/* Teensy 3.x, LC, 4.0 ADC library + * https://github.com/pedvide/ADC + * Copyright (c) 2020 Pedro Villanueva + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +#ifndef ANALOGBUFFERDMA_H +#define ANALOGBUFFERDMA_H + +#include "DMAChannel.h" +#include "ADC.h" + +#ifdef ADC_USE_DMA + +// lets wrap some of our Dmasettings stuff into helper class +class AnalogBufferDMA +{ + // keep our settings and the like: +public: // At least temporary to play with dma settings. +#ifndef KINETISL + DMASetting _dmasettings_adc[2]; +#endif + DMAChannel _dmachannel_adc; + + static AnalogBufferDMA *_activeObjectPerADC[2]; + static void adc_0_dmaISR(); + static void adc_1_dmaISR(); + void processADC_DMAISR(); + +public: + AnalogBufferDMA(volatile uint16_t *buffer1, uint16_t buffer1_count, + volatile uint16_t *buffer2 = nullptr, uint16_t buffer2_count = 0) : _buffer1(buffer1), _buffer1_count(buffer1_count), _buffer2(buffer2), _buffer2_count(buffer2_count){}; + + void init(ADC *adc, int8_t adc_num = -1); + + void stopOnCompletion(bool stop_on_complete); + inline bool stopOnCompletion(void) { return _stop_on_completion; } + bool clearCompletion(); + inline volatile uint16_t *bufferLastISRFilled() { return (!_buffer2 || (_interrupt_count & 1)) ? _buffer1 : _buffer2; } + inline uint16_t bufferCountLastISRFilled() { return (!_buffer2 || (_interrupt_count & 1)) ? _buffer1_count : _buffer2_count; } + inline uint32_t interruptCount() { return _interrupt_count; } + inline uint32_t interruptDeltaTime() { return _interrupt_delta_time; } + inline bool interrupted() { return _interrupt_delta_time != 0; } + inline void clearInterrupt() { _interrupt_delta_time = 0; } + inline void userData(uint32_t new_data) { _user_data = new_data; } + inline uint32_t userData(void) { return _user_data; } + +protected: + volatile uint32_t _interrupt_count = 0; + volatile uint32_t _interrupt_delta_time; + volatile uint32_t _last_isr_time; + + volatile uint16_t *_buffer1; + uint16_t _buffer1_count; + volatile uint16_t *_buffer2; + uint16_t _buffer2_count; + uint32_t _user_data = 0; + bool _stop_on_completion = false; +}; + +#endif // ADC_USE_DMA +#endif + diff --git a/Firmware_V3/lib/ADC/VREF.h b/Firmware_V3/lib/ADC/VREF.h new file mode 100644 index 0000000..5aa252e --- /dev/null +++ b/Firmware_V3/lib/ADC/VREF.h @@ -0,0 +1,126 @@ +/* Teensy 4.x, 3.x, LC ADC library + * https://github.com/pedvide/ADC + * Copyright (c) 2020 Pedro Villanueva + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ADC_VREF_H +#define ADC_VREF_H + +#include + +#include +#include + +#ifdef ADC_USE_INTERNAL_VREF + +//! Controls the Teensy internal voltage reference module (VREFV1) +namespace VREF +{ + + //! Start the 1.2V internal reference (if present) + /** This is called automatically by ADC_Module::setReference(ADC_REFERENCE::REF_1V2) + * Use it to switch on the internal reference on the VREF_OUT pin. + * You can measure it with adc->analogRead(ADC_INTERNAL_SOURCE::VREF_OUT). + * \param mode can be (these are defined in kinetis.h) + * VREF_SC_MODE_LV_BANDGAPONLY (0) for stand-by + * VREF_SC_MODE_LV_HIGHPOWERBUF (1) for high power buffer and + * VREF_SC_MODE_LV_LOWPOWERBUF (2) for low power buffer. + * \param trim adjusts the reference value, from 0 to 0x3F (63). Default is 32. + * + */ + inline void start(uint8_t mode = VREF_SC_MODE_LV_HIGHPOWERBUF, uint8_t trim = 0x20) + { + VREF_TRM = VREF_TRM_CHOPEN | (trim & 0x3F); // enable module and set the trimmer to medium (max=0x3F=63) + // enable 1.2 volt ref with all compensations in high power mode + VREF_SC = VREF_SC_VREFEN | VREF_SC_REGEN | VREF_SC_ICOMPEN | VREF_SC_MODE_LV(mode); + + // "PMC_REGSC[BGEN] bit must be set if the VREF regulator is + // required to remain operating in VLPx modes." + // Also "If the chop oscillator is to be used in very low power modes, + // the system (bandgap) voltage reference must also be enabled." + // enable bandgap, can be read directly with ADC_INTERNAL_SOURCE::BANDGAP + atomic::setBitFlag(PMC_REGSC, PMC_REGSC_BGBE); + } + + //! Set the trim + /** Set the trim, the change in the reference is about 0.5 mV per step. + * \param trim adjusts the reference value, from 0 to 0x3F (63). + */ + inline void trim(uint8_t trim) + { + bool chopen = atomic::getBitFlag(VREF_TRM, VREF_TRM_CHOPEN); + VREF_TRM = (chopen ? VREF_TRM_CHOPEN : 0) | (trim & 0x3F); + } + + //! Stops the internal reference + /** This is called automatically by ADC_Module::setReference(ref) when ref is any other than REF_1V2 + */ + __attribute__((always_inline)) inline void stop() + { + VREF_SC = 0; + atomic::clearBitFlag(PMC_REGSC, PMC_REGSC_BGBE); + } + + //! Check if the internal reference has stabilized. + /** NOTE: This is valid only when the chop oscillator is not being used. + * By default the chop oscillator IS used, so wait the maximum start-up time of 35 ms (as per datasheet). + * waitUntilStable waits 35 us. + * This should be polled after enabling the reference after reset, after changing + * its buffer mode from VREF_SC_MODE_LV_BANDGAPONLY to any of the buffered modes, or + * after changing the trim. + * + * \return true if the VREF module is already in a stable condition and can be used. + */ + __attribute__((always_inline)) inline volatile bool isStable() + { + return atomic::getBitFlag(VREF_SC, VREF_SC_VREFST); + } + + //! Check if the internal reference is on. + /** + * \return true if the VREF module is switched on. + */ + __attribute__((always_inline)) inline volatile bool isOn() + { + return atomic::getBitFlag(VREF_SC, VREF_SC_VREFEN); + } + + //! Wait for the internal reference to stabilize. + /** This function can be called to wait for the internal reference to stabilize. + * It will block until the reference has stabilized, or return immediately if the + * reference is not enabled in the first place. + */ + inline void waitUntilStable() + { + delay(35); // see note in isStable() + while (isOn() && !isStable()) + { + yield(); + } + } + +} // namespace VREF + +#endif // ADC_USE_INTERNAL_VREF + +#endif // ADC_VREF_H diff --git a/Firmware_V3/lib/ADC/atomic.h b/Firmware_V3/lib/ADC/atomic.h new file mode 100644 index 0000000..02fd3b3 --- /dev/null +++ b/Firmware_V3/lib/ADC/atomic.h @@ -0,0 +1,248 @@ +/* Teensy 4.x, 3.x, LC ADC library + * https://github.com/pedvide/ADC + * Copyright (c) 2020 Pedro Villanueva + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ADC_ATOMIC_H +#define ADC_ATOMIC_H + +/* int __builtin_ctz (unsigned int x): + Returns the number of trailing 0-bits in x, + starting at the least significant bit position. + If x is 0, the result is undefined. +*/ +/* int __builtin_clz (unsigned int x) + Returns the number of leading 0-bits in x, + starting at the most significant bit position. + If x is 0, the result is undefined. +*/ +/* int __builtin_popcount (unsigned int x) + Returns the number of 1-bits in x. +*/ + +// kinetis.h has the following types for addresses: uint32_t, uint16_t, uint8_t, int32_t, int16_t + +//! Atomic set, clear, change, or get bit in a register +namespace atomic +{ +/////// Atomic bit set/clear +/* Clear bit in address (make it zero), set bit (make it one), or return the value of that bit + * changeBitFlag can change up to 2 bits in a flag at the same time + * We can change this functions depending on the board. + * Teensy 3.x use bitband while Teensy LC has a more advanced bit manipulation engine. + * Teensy 4 also has bitband capabilities, but are not yet implemented, instead registers are + * set and cleared manually. TODO: fix this. + */ +#if defined(KINETISK) // Teensy 3.x + //! Bitband address + /** Gets the aliased address of the bit-band register + * \param reg Register in the bit-band area + * \param bit Bit number of reg to read/modify + * \return A pointer to the aliased address of the bit of reg + */ + template + __attribute__((always_inline)) inline volatile T &bitband_address(volatile T ®, uint8_t bit) + { + return (*(volatile T *)(((uint32_t)® - 0x40000000) * 32 + bit * 4 + 0x42000000)); + } + + template + __attribute__((always_inline)) inline void setBit(volatile T ®, uint8_t bit) + { + bitband_address(reg, bit) = 1; + } + template + __attribute__((always_inline)) inline void setBitFlag(volatile T ®, T flag) + { + // 31-__builtin_clzl(flag) = gets bit number in flag + // __builtin_clzl works for long ints, which are guaranteed by standard to be at least 32 bit wide. + // there's no difference in the asm emitted. + bitband_address(reg, 31 - __builtin_clzl(flag)) = 1; + if (__builtin_popcount(flag) > 1) + { + // __builtin_ctzl returns the number of trailing 0-bits in x, starting at the least significant bit position + bitband_address(reg, __builtin_ctzl(flag)) = 1; + } + } + + template + __attribute__((always_inline)) inline void clearBit(volatile T ®, uint8_t bit) + { + bitband_address(reg, bit) = 0; + } + template + __attribute__((always_inline)) inline void clearBitFlag(volatile T ®, T flag) + { + bitband_address(reg, 31 - __builtin_clzl(flag)) = 0; + if (__builtin_popcount(flag) > 1) + { + bitband_address(reg, __builtin_ctzl(flag)) = 0; + } + } + + template + __attribute__((always_inline)) inline void changeBit(volatile T ®, uint8_t bit, bool state) + { + bitband_address(reg, bit) = state; + } + template + __attribute__((always_inline)) inline void changeBitFlag(volatile T ®, T flag, T state) + { + bitband_address(reg, __builtin_ctzl(flag)) = (state >> __builtin_ctzl(flag)) & 0x1; + if (__builtin_popcount(flag) > 1) + { + bitband_address(reg, 31 - __builtin_clzl(flag)) = (state >> (31 - __builtin_clzl(flag))) & 0x1; + } + } + + template + __attribute__((always_inline)) inline volatile bool getBit(volatile T ®, uint8_t bit) + { + return (volatile bool)bitband_address(reg, bit); + } + template + __attribute__((always_inline)) inline volatile bool getBitFlag(volatile T ®, T flag) + { + return (volatile bool)bitband_address(reg, 31 - __builtin_clzl(flag)); + } + +#elif defined(__IMXRT1062__) // Teensy 4 + template + __attribute__((always_inline)) inline void setBitFlag(volatile T ®, T flag) + { + __disable_irq(); + reg |= flag; + __enable_irq(); + } + + template + __attribute__((always_inline)) inline void clearBitFlag(volatile T ®, T flag) + { + __disable_irq(); + reg &= ~flag; + __enable_irq(); + } + + template + __attribute__((always_inline)) inline void changeBitFlag(volatile T ®, T flag, T state) + { + // flag can be 1 or 2 bits wide + // state can have one or two bits set + if (__builtin_popcount(flag) == 1) + { // 1 bit + if (state) + { + setBitFlag(reg, flag); + } + else + { + clearBitFlag(reg, flag); + } + } + else + { // 2 bits + // lsb first + if ((state >> __builtin_ctzl(flag)) & 0x1) + { // lsb of state is 1 + setBitFlag(reg, (uint32_t)(1 << __builtin_ctzl(flag))); + } + else + { // lsb is 0 + clearBitFlag(reg, (uint32_t)(1 << __builtin_ctzl(flag))); + } + // msb + if ((state >> (31 - __builtin_clzl(flag))) & 0x1) + { // msb of state is 1 + setBitFlag(reg, (uint32_t)(1 << (31 - __builtin_clzl(flag)))); + } + else + { // msb is 0 + clearBitFlag(reg, (uint32_t)(1 << (31 - __builtin_clzl(flag)))); + } + } + } + + template + __attribute__((always_inline)) inline volatile bool getBitFlag(volatile T ®, T flag) + { + return (volatile bool)((reg)&flag) >> (31 - __builtin_clzl(flag)); + } + +#elif defined(KINETISL) // Teensy LC + // bit manipulation engine + + template + __attribute__((always_inline)) inline void setBit(volatile T ®, uint8_t bit) + { + //temp = *(uint32_t *)((uint32_t)(reg) | (1<<26) | (bit<<21)); // LAS + *(volatile T *)((uint32_t)(®) | (1 << 27)) = 1 << bit; // OR + } + template + __attribute__((always_inline)) inline void setBitFlag(volatile T ®, uint32_t flag) + { + *(volatile T *)((uint32_t)® | (1 << 27)) = flag; // OR + } + + template + __attribute__((always_inline)) inline void clearBit(volatile T ®, uint8_t bit) + { + //temp = *(uint32_t *)((uint32_t)(reg) | (3<<27) | (bit<<21)); // LAC + *(volatile T *)((uint32_t)(®) | (1 << 26)) = ~(1 << bit); // AND + } + template + __attribute__((always_inline)) inline void clearBitFlag(volatile T ®, uint32_t flag) + { + //temp = *(uint32_t *)((uint32_t)(reg) | (3<<27) | (bit<<21)); // LAC + *(volatile T *)((uint32_t)(®) | (1 << 26)) = ~flag; // AND + } + + template + __attribute__((always_inline)) inline void changeBit(volatile T ®, uint8_t bit, bool state) + { + //temp = *(uint32_t *)((uint32_t)(reg) | ((3-2*!!state)<<27) | (bit<<21)); // LAS/LAC + state ? setBit(reg, bit) : clearBit(reg, bit); + } + template + __attribute__((always_inline)) inline void changeBitFlag(volatile T ®, T flag, T state) + { + // BFI, bitfield width set to __builtin_popcount(flag) + // least significant bit set to __builtin_ctzl(flag) + *(volatile T *)((uint32_t)(®) | (1 << 28) | (__builtin_ctzl(flag) << 23) | ((__builtin_popcount(flag) - 1) << 19)) = state; + } + + template + __attribute__((always_inline)) inline volatile bool getBit(volatile T ®, uint8_t bit) + { + return (volatile bool)*(volatile T *)((uint32_t)(®) | (1 << 28) | (bit << 23)); // UBFX + } + template + __attribute__((always_inline)) inline volatile bool getBitFlag(volatile T ®, T flag) + { + return (volatile bool)*(volatile T *)((uint32_t)(®) | (1 << 28) | ((31 - __builtin_clzl(flag)) << 23)); // UBFX + } + +#endif + +} // namespace atomic + +#endif // ADC_ATOMIC_H diff --git a/Firmware_V3/lib/ADC/settings_defines.h b/Firmware_V3/lib/ADC/settings_defines.h new file mode 100644 index 0000000..f3cffe6 --- /dev/null +++ b/Firmware_V3/lib/ADC/settings_defines.h @@ -0,0 +1,701 @@ +/* Teensy 4.x, 3.x, LC ADC library + * https://github.com/pedvide/ADC + * Copyright (c) 2020 Pedro Villanueva + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/*! \page settings ADC Settings +Board-dependent settings. +See the namespace ADC_settings for all functions. +*/ + +#ifndef ADC_SETTINGS_H +#define ADC_SETTINGS_H + +#include + +//! Board-dependent settings +namespace ADC_settings +{ + +// Easier names for the boards +#if defined(__MK20DX256__) // Teensy 3.1/3.2 +#define ADC_TEENSY_3_1 +#elif defined(__MK20DX128__) // Teensy 3.0 +#define ADC_TEENSY_3_0 +#elif defined(__MKL26Z64__) // Teensy LC +#define ADC_TEENSY_LC +#elif defined(__MK64FX512__) // Teensy 3.5 +#define ADC_TEENSY_3_5 +#elif defined(__MK66FX1M0__) // Teensy 3.6 +#define ADC_TEENSY_3_6 +#elif defined(__IMXRT1062__) // Teensy 4.0/4.1 +// They only differ in the number of exposed pins +#define ADC_TEENSY_4 +#ifdef ARDUINO_TEENSY41 +#define ADC_TEENSY_4_1 +#else +#define ADC_TEENSY_4_0 +#endif +#else +#error "Board not supported!" +#endif + +// Teensy 3.1 has 2 ADCs, Teensy 3.0 and LC only 1. +#if defined(ADC_TEENSY_3_1) // Teensy 3.1 +#define ADC_NUM_ADCS (2) +#define ADC_DUAL_ADCS +#elif defined(ADC_TEENSY_3_0) // Teensy 3.0 +#define ADC_NUM_ADCS (1) +#define ADC_SINGLE_ADC +#elif defined(ADC_TEENSY_LC) // Teensy LC +#define ADC_NUM_ADCS (1) +#define ADC_SINGLE_ADC +#elif defined(ADC_TEENSY_3_5) // Teensy 3.5 +#define ADC_NUM_ADCS (2) +#define ADC_DUAL_ADCS +#elif defined(ADC_TEENSY_3_6) // Teensy 3.6 +#define ADC_NUM_ADCS (2) +#define ADC_DUAL_ADCS +#elif defined(ADC_TEENSY_4) // Teensy 4, 4.1 +#define ADC_NUM_ADCS (2) +#define ADC_DUAL_ADCS +#endif + +// Use DMA? +#if defined(ADC_TEENSY_3_1) // Teensy 3.1 +#define ADC_USE_DMA +#elif defined(ADC_TEENSY_3_0) // Teensy 3.0 +#define ADC_USE_DMA +#elif defined(ADC_TEENSY_LC) // Teensy LC +#define ADC_USE_DMA +#elif defined(ADC_TEENSY_3_5) // Teensy 3.5 +#define ADC_USE_DMA +#elif defined(ADC_TEENSY_3_6) // Teensy 3.6 +#define ADC_USE_DMA +#elif defined(ADC_TEENSY_4) // Teensy 4, 4.1 +#define ADC_USE_DMA +#endif + +// Use PGA? +#if defined(ADC_TEENSY_3_1) // Teensy 3.1 +#define ADC_USE_PGA +#elif defined(ADC_TEENSY_3_0) // Teensy 3.0 +#elif defined(ADC_TEENSY_LC) // Teensy LC +#elif defined(ADC_TEENSY_3_5) // Teensy 3.5 +#elif defined(ADC_TEENSY_3_6) // Teensy 3.6 +#elif defined(ADC_TEENSY_4) // Teensy 4, 4.1 +#endif + +// Use PDB? +#if defined(ADC_TEENSY_3_1) // Teensy 3.1 +#define ADC_USE_PDB +#elif defined(ADC_TEENSY_3_0) // Teensy 3.0 +#define ADC_USE_PDB +#elif defined(ADC_TEENSY_LC) // Teensy LC +#elif defined(ADC_TEENSY_3_5) // Teensy 3.5 +#define ADC_USE_PDB +#elif defined(ADC_TEENSY_3_6) // Teensy 3.6 +#define ADC_USE_PDB +#elif defined(ADC_TEENSY_4) // Teensy 4, 4.1 +#endif + +// Use Quad Timer +#if defined(ADC_TEENSY_3_1) // Teensy 3.1 +#define ADC_USE_QUAD_TIMER // TODO: Not implemented +#elif defined(ADC_TEENSY_3_0) // Teensy 3.0 +#define ADC_USE_QUAD_TIMER // TODO: Not implemented +#elif defined(ADC_TEENSY_LC) // Teensy LC +#elif defined(ADC_TEENSY_3_5) // Teensy 3.5 +#define ADC_USE_QUAD_TIMER // TODO: Not implemented +#elif defined(ADC_TEENSY_3_6) // Teensy 3.6 +#define ADC_USE_QUAD_TIMER // TODO: Not implemented +#elif defined(ADC_TEENSY_4) // Teensy 4, 4.1 +#define ADC_USE_QUAD_TIMER +#endif + +// Has a timer? +#if defined(ADC_USE_PDB) || defined(ADC_USE_QUAD_TIMER) +#define ADC_USE_TIMER +#endif + +// Has internal reference? +#if defined(ADC_TEENSY_3_1) // Teensy 3.1 +#define ADC_USE_INTERNAL_VREF +#elif defined(ADC_TEENSY_3_0) // Teensy 3.0 +#define ADC_USE_INTERNAL_VREF +#elif defined(ADC_TEENSY_LC) // Teensy LC +#elif defined(ADC_TEENSY_3_5) // Teensy 3.5 +#define ADC_USE_INTERNAL_VREF +#elif defined(ADC_TEENSY_3_6) // Teensy 3.6 +#define ADC_USE_INTERNAL_VREF +#elif defined(ADC_TEENSY_4) // Teensy 4, 4.1 +#endif + + //! \cond internal + //! Select the voltage reference sources for ADC. This is an internal setting, do not use, @internal + enum class ADC_REF_SOURCE : uint8_t + { + REF_DEFAULT = 0, + REF_ALT = 1, + REF_NONE = 2 + }; // internal, do not use +//! \endcond +#if defined(ADC_TEENSY_3_0) || defined(ADC_TEENSY_3_1) || defined(ADC_TEENSY_3_5) || defined(ADC_TEENSY_3_6) + // default is the external, that is connected to the 3.3V supply. + // To use the external simply connect AREF to a different voltage + // alt is connected to the 1.2 V ref. + //! Voltage reference for the ADC + enum class ADC_REFERENCE : uint8_t + { + REF_3V3 = static_cast(ADC_REF_SOURCE::REF_DEFAULT), /*!< 3.3 volts */ + REF_1V2 = static_cast(ADC_REF_SOURCE::REF_ALT), /*!< 1.2 volts */ + REF_EXT = static_cast(ADC_REF_SOURCE::REF_DEFAULT), /*!< External VREF */ + NONE = static_cast(ADC_REF_SOURCE::REF_NONE) // internal, do not use + }; +#elif defined(ADC_TEENSY_LC) + // alt is the internal ref, 3.3 V + // the default is AREF + //! Voltage reference for the ADC + enum class ADC_REFERENCE : uint8_t + { + REF_3V3 = static_cast(ADC_REF_SOURCE::REF_ALT), /*!< 3.3 volts */ + REF_EXT = static_cast(ADC_REF_SOURCE::REF_DEFAULT), /*!< External VREF */ + NONE = static_cast(ADC_REF_SOURCE::REF_NONE) // internal, do not use + }; +#elif defined(ADC_TEENSY_4) + // default is the external, that is connected to the 3.3V supply. + //! Voltage reference for the ADC + enum class ADC_REFERENCE : uint8_t + { + REF_3V3 = static_cast(ADC_REF_SOURCE::REF_DEFAULT), /*!< 3.3 volts */ + NONE = static_cast(ADC_REF_SOURCE::REF_NONE) // internal, do not use + }; +#endif + +// max number of pins, size of channel2sc1aADCx +#if defined(ADC_TEENSY_3_1) // Teensy 3.1 +#define ADC_MAX_PIN (43) +#elif defined(ADC_TEENSY_3_0) // Teensy 3.0 +#define ADC_MAX_PIN (43) +#elif defined(ADC_TEENSY_LC) // Teensy LC +#define ADC_MAX_PIN (43) +#elif defined(ADC_TEENSY_3_5) // Teensy 3.5 +#define ADC_MAX_PIN (69) +#elif defined(ADC_TEENSY_3_6) // Teensy 3.6 +#define ADC_MAX_PIN (67) +#elif defined(ADC_TEENSY_4_0) // Teensy 4 +#define ADC_MAX_PIN (27) +#elif defined(ADC_TEENSY_4_1) // Teensy 4 +#define ADC_MAX_PIN (41) +#endif + +// number of differential pairs PER ADC! +#if defined(ADC_TEENSY_3_1) // Teensy 3.1 +#define ADC_DIFF_PAIRS (2) // normal and with PGA +#elif defined(ADC_TEENSY_3_0) // Teensy 3.0 +#define ADC_DIFF_PAIRS (2) +#elif defined(ADC_TEENSY_LC) // Teensy LC +#define ADC_DIFF_PAIRS (1) +#elif defined(ADC_TEENSY_3_5) // Teensy 3.5 +#define ADC_DIFF_PAIRS (1) +#elif defined(ADC_TEENSY_3_6) // Teensy 3.6 +#define ADC_DIFF_PAIRS (1) +#elif defined(ADC_TEENSY_4) // Teensy 4, 4.1 +#define ADC_DIFF_PAIRS (0) +#endif + +// Other things to measure with the ADC that don't use external pins +// In my Teensy I read 1.22 V for the ADC_VREF_OUT (see VREF.h), 1.0V for ADC_BANDGAP (after PMC_REGSC |= PMC_REGSC_BGBE), +// 3.3 V for ADC_VREFH and 0.0 V for ADC_VREFL. +#if defined(ADC_TEENSY_LC) + /*! Other ADC sources to measure, such as the temperature sensor. + */ + enum class ADC_INTERNAL_SOURCE : uint8_t + { + TEMP_SENSOR = 38, + /*!< Temperature sensor. */ // 0.719 V at 25ºC and slope of 1.715 mV/ºC for Teensy 3.x and 0.716 V, 1.62 mV/ºC for Teensy LC + BANDGAP = 41, + /*!< BANDGAP */ // Enable the Bandgap with PMC_REGSC |= PMC_REGSC_BGBE; (see VREF.h) + VREFH = 42, /*!< High VREF */ + VREFL = 43, /*!< Low VREF. */ + }; +#elif defined(ADC_TEENSY_3_1) || defined(ADC_TEENSY_3_0) + /*! Other ADC sources to measure, such as the temperature sensor. + */ + enum class ADC_INTERNAL_SOURCE : uint8_t + { + TEMP_SENSOR = 38, + /*!< Temperature sensor. */ // 0.719 V at 25ºC and slope of 1.715 mV/ºC for Teensy 3.x and 0.716 V, 1.62 mV/ºC for Teensy LC + VREF_OUT = 39, /*!< 1.2 V reference */ + BANDGAP = 41, + /*!< BANDGAP */ // Enable the Bandgap with PMC_REGSC |= PMC_REGSC_BGBE; (see VREF.h) + VREFH = 42, /*!< High VREF */ + VREFL = 43, /*!< Low VREF. */ + }; +#elif defined(ADC_TEENSY_3_5) || defined(ADC_TEENSY_3_6) + /*! Other ADC sources to measure, such as the temperature sensor. + */ + enum class ADC_INTERNAL_SOURCE : uint8_t + { + TEMP_SENSOR = 24, + /*!< Temperature sensor. */ // 0.719 V at 25ºC and slope of 1.715 mV/ºC for Teensy 3.x and 0.716 V, 1.62 mV/ºC for Teensy LC + VREF_OUT = 28, + /*!< 1.2 V reference */ // only on ADC1 + BANDGAP = 25, + /*!< BANDGAP */ // Enable the Bandgap with PMC_REGSC |= PMC_REGSC_BGBE; (see VREF::start in VREF.h) + VREFH = 26, /*!< High VREF */ + VREFL = 27, /*!< Low VREF. */ + }; +#elif defined(ADC_TEENSY_4) + /*! Other ADC sources to measure, such as the temperature sensor. + */ + enum class ADC_INTERNAL_SOURCE : uint8_t + { + VREFSH = 25, /*!< internal channel, for ADC self-test, hard connected to VRH internally */ + }; +#endif + +//! \cond internal +//! Struct containing the registers controlling the ADC +#if defined(ADC_TEENSY_4) + typedef struct + { + volatile uint32_t HC0; + volatile uint32_t HC1; + volatile uint32_t HC2; + volatile uint32_t HC3; + volatile uint32_t HC4; + volatile uint32_t HC5; + volatile uint32_t HC6; + volatile uint32_t HC7; + volatile uint32_t HS; + volatile uint32_t R0; + volatile uint32_t R1; + volatile uint32_t R2; + volatile uint32_t R3; + volatile uint32_t R4; + volatile uint32_t R5; + volatile uint32_t R6; + volatile uint32_t R7; + volatile uint32_t CFG; + volatile uint32_t GC; + volatile uint32_t GS; + volatile uint32_t CV; + volatile uint32_t OFS; + volatile uint32_t CAL; + } ADC_REGS_t; +#define ADC0_START (*(ADC_REGS_t *)0x400C4000) +#define ADC1_START (*(ADC_REGS_t *)0x400C8000) +#else + typedef struct + { + volatile uint32_t SC1A; + volatile uint32_t SC1B; + volatile uint32_t CFG1; + volatile uint32_t CFG2; + volatile uint32_t RA; + volatile uint32_t RB; + volatile uint32_t CV1; + volatile uint32_t CV2; + volatile uint32_t SC2; + volatile uint32_t SC3; + volatile uint32_t OFS; + volatile uint32_t PG; + volatile uint32_t MG; + volatile uint32_t CLPD; + volatile uint32_t CLPS; + volatile uint32_t CLP4; + volatile uint32_t CLP3; + volatile uint32_t CLP2; + volatile uint32_t CLP1; + volatile uint32_t CLP0; + volatile uint32_t PGA; + volatile uint32_t CLMD; + volatile uint32_t CLMS; + volatile uint32_t CLM4; + volatile uint32_t CLM3; + volatile uint32_t CLM2; + volatile uint32_t CLM1; + volatile uint32_t CLM0; + } ADC_REGS_t; +#define ADC0_START (*(ADC_REGS_t *)0x4003B000) +#define ADC1_START (*(ADC_REGS_t *)0x400BB000) +#endif + //! \endcond + + /* MK20DX256 Datasheet: +The 16-bit accuracy specifications listed in Table 24 and Table 25 are achievable on the +differential pins ADCx_DP0, ADCx_DM0 +All other ADC channels meet the 13-bit differential/12-bit single-ended accuracy +specifications. + +The results in this data sheet were derived from a system which has < 8 Ohm analog source resistance. The RAS/CAS +time constant should be kept to < 1ns. + +ADC clock should be 2 to 12 MHz for 16 bit mode +ADC clock should be 1 to 18 MHz for 8-12 bit mode, and 1-24 MHz for Teensy 3.6 (NOT 3.5) +To use the maximum ADC conversion clock frequency, the ADHSC bit must be set and the ADLPC bit must be clear + +The ADHSC bit is used to configure a higher clock input frequency. This will allow +faster overall conversion times. To meet internal ADC timing requirements, the ADHSC +bit adds additional ADCK cycles. Conversions with ADHSC = 1 take two more ADCK +cycles. ADHSC should be used when the ADCLK exceeds the limit for ADHSC = 0. + +*/ + // the alternate clock is connected to OSCERCLK (16 MHz). + // datasheet says ADC clock should be 2 to 12 MHz for 16 bit mode + // datasheet says ADC clock should be 1 to 18 MHz for 8-12 bit mode, and 1-24 MHz for Teensy 3.6 (NOT 3.5) + // calibration works best when averages are 32 and speed is less than 4 MHz + // ADC_CFG1_ADICLK: 0=bus, 1=bus/2, 2=(alternative clk) altclk, 3=(async. clk) adack + // See below for an explanation of VERY_LOW_SPEED, LOW_SPEED, etc. + +#define ADC_MHz (1000000) // not so many zeros +// Min freq for 8-12 bit mode is 1 MHz, 4 MHz for Teensy 4 +#if defined(ADC_TEENSY_4) +#define ADC_MIN_FREQ (4 * ADC_MHz) +#else +#define ADC_MIN_FREQ (1 * ADC_MHz) +#endif +// Max freq for 8-12 bit mode is 18 MHz, 24 MHz for Teensy 3.6, and 40 for Teensy 4 +#if defined(ADC_TEENSY_3_6) +#define ADC_MAX_FREQ (24 * ADC_MHz) +#elif defined(ADC_TEENSY_4) +#define ADC_MAX_FREQ (40 * ADC_MHz) +#else +#define ADC_MAX_FREQ (18 * ADC_MHz) +#endif + +// Min and max for 16 bits. For Teensy 4 is the same as before (no 16 bit mode) +#if defined(ADC_TEENSY_4) +#define ADC_MIN_FREQ_16BITS ADC_MIN_FREQ +#define ADC_MAX_FREQ_16BITS ADC_MAX_FREQ +#else +// Min freq for 16 bit mode is 2 MHz +#define ADC_MIN_FREQ_16BITS (2 * ADC_MHz) +// Max freq for 16 bit mode is 12 MHz +#define ADC_MAX_FREQ_16BITS (12 * ADC_MHz) +#endif + +// We can divide F_BUS by 1, 2, 4, 8, or 16: +/* +Divide by ADC_CFG1_ADIV ADC_CFG1_ADICLK TOTAL VALUE +1 0 0 0 0x00 +2 1 0 1 0x20 +4 2 0 2 0x40 +8 3 0 3 0x60 +16 3 1 4 0x61 +(Other combinations are possible) +*/ + +// Redefine from kinetis.h to remove (uint32_t) casts that the preprocessor doesn't understand +// so we can do arithmetic with them when defining ADC_CFG1_MED_SPEED +#define ADC_LIB_CFG1_ADIV(n) (((n)&0x03) << 5) +#define ADC_LIB_CFG1_ADICLK(n) (((n)&0x03) << 0) + +#if defined(ADC_TEENSY_4) +#define ADC_F_BUS F_BUS_ACTUAL // (150*ADC_MHz) +#else +#define ADC_F_BUS F_BUS +#endif + + //! \cond internal + //! ADC_CFG1_VERY_LOW_SPEED is the lowest freq @internal + constexpr uint32_t get_CFG_VERY_LOW_SPEED(uint32_t f_adc_clock) + { + if (f_adc_clock / 16 >= ADC_MIN_FREQ) + { + return (ADC_LIB_CFG1_ADIV(3) + ADC_LIB_CFG1_ADICLK(1)); + } + else if (f_adc_clock / 8 >= ADC_MIN_FREQ) + { + return (ADC_LIB_CFG1_ADIV(3) + ADC_LIB_CFG1_ADICLK(0)); + } + else if (f_adc_clock / 4 >= ADC_MIN_FREQ) + { + return (ADC_LIB_CFG1_ADIV(2) + ADC_LIB_CFG1_ADICLK(0)); + } + else if (f_adc_clock / 2 >= ADC_MIN_FREQ) + { + return (ADC_LIB_CFG1_ADIV(1) + ADC_LIB_CFG1_ADICLK(0)); + } + else + { + return (ADC_LIB_CFG1_ADIV(0) + ADC_LIB_CFG1_ADICLK(0)); + } + } + + //! ADC_CFG1_LOW_SPEED is the lowest freq for 16 bits @internal + constexpr uint32_t get_CFG_LOW_SPEED(uint32_t f_adc_clock) + { + if (f_adc_clock / 16 >= ADC_MIN_FREQ_16BITS) + { + return (ADC_LIB_CFG1_ADIV(3) + ADC_LIB_CFG1_ADICLK(1)); + } + else if (f_adc_clock / 8 >= ADC_MIN_FREQ_16BITS) + { + return (ADC_LIB_CFG1_ADIV(3) + ADC_LIB_CFG1_ADICLK(0)); + } + else if (f_adc_clock / 4 >= ADC_MIN_FREQ_16BITS) + { + return (ADC_LIB_CFG1_ADIV(2) + ADC_LIB_CFG1_ADICLK(0)); + } + else if (f_adc_clock / 2 >= ADC_MIN_FREQ_16BITS) + { + return (ADC_LIB_CFG1_ADIV(1) + ADC_LIB_CFG1_ADICLK(0)); + } + else + { + return (ADC_LIB_CFG1_ADIV(0) + ADC_LIB_CFG1_ADICLK(0)); + } + } + + //! ADC_CFG1_HI_SPEED_16_BITS is the highest freq for 16 bits @internal + constexpr uint32_t get_CFG_HI_SPEED_16_BITS(uint32_t f_adc_clock) + { + if (f_adc_clock <= ADC_MAX_FREQ_16BITS) + { + return (ADC_LIB_CFG1_ADIV(0) + ADC_LIB_CFG1_ADICLK(0)); + } + else if (f_adc_clock / 2 <= ADC_MAX_FREQ_16BITS) + { + return (ADC_LIB_CFG1_ADIV(1) + ADC_LIB_CFG1_ADICLK(0)); + } + else if (f_adc_clock / 4 <= ADC_MAX_FREQ_16BITS) + { + return (ADC_LIB_CFG1_ADIV(2) + ADC_LIB_CFG1_ADICLK(0)); + } + else if (f_adc_clock / 8 <= ADC_MAX_FREQ_16BITS) + { + return (ADC_LIB_CFG1_ADIV(3) + ADC_LIB_CFG1_ADICLK(0)); + } + else + { + return (ADC_LIB_CFG1_ADIV(3) + ADC_LIB_CFG1_ADICLK(1)); + } + } + + //! For ADC_CFG1_MED_SPEED the idea is to check if there's an unused setting between + // ADC_CFG1_LOW_SPEED and ADC_CFG1_HI_SPEED_16_BITS @internal + constexpr uint32_t get_CFG_MEDIUM_SPEED(uint32_t f_adc_clock) + { + uint32_t ADC_CFG1_LOW_SPEED = get_CFG_LOW_SPEED(f_adc_clock); + uint32_t ADC_CFG1_HI_SPEED_16_BITS = get_CFG_HI_SPEED_16_BITS(f_adc_clock); + if (ADC_CFG1_LOW_SPEED - ADC_CFG1_HI_SPEED_16_BITS > 0x20) + { + return ADC_CFG1_HI_SPEED_16_BITS + 0x20; + } + else + { + return ADC_CFG1_HI_SPEED_16_BITS; + } + } + + //! ADC_CFG1_HI_SPEED is the highest freq for under 16 bits @internal + constexpr uint32_t get_CFG_HIGH_SPEED(uint32_t f_adc_clock) + { + if (f_adc_clock <= ADC_MAX_FREQ) + { + return (ADC_LIB_CFG1_ADIV(0) + ADC_LIB_CFG1_ADICLK(0)); + } + else if (f_adc_clock / 2 <= ADC_MAX_FREQ) + { + return (ADC_LIB_CFG1_ADIV(1) + ADC_LIB_CFG1_ADICLK(0)); + } + else if (f_adc_clock / 4 <= ADC_MAX_FREQ) + { + return (ADC_LIB_CFG1_ADIV(2) + ADC_LIB_CFG1_ADICLK(0)); + } + else if (f_adc_clock / 8 <= ADC_MAX_FREQ) + { + return (ADC_LIB_CFG1_ADIV(3) + ADC_LIB_CFG1_ADICLK(0)); + } + else + { + return (ADC_LIB_CFG1_ADIV(3) + ADC_LIB_CFG1_ADICLK(1)); + } + } + + //! ADC_CFG1_VERY_HIGH_SPEED >= ADC_CFG1_HI_SPEED and may be out of specs, @internal + // but not more than ADC_VERY_HIGH_SPEED_FACTOR*ADC_MAX_FREQ + constexpr uint32_t get_CFG_VERY_HIGH_SPEED(uint32_t f_adc_clock) + { + const uint8_t speed_factor = 2; + if (f_adc_clock <= speed_factor * ADC_MAX_FREQ) + { + return (ADC_LIB_CFG1_ADIV(0) + ADC_LIB_CFG1_ADICLK(0)); + } + else if (f_adc_clock / 2 <= speed_factor * ADC_MAX_FREQ) + { + return (ADC_LIB_CFG1_ADIV(1) + ADC_LIB_CFG1_ADICLK(0)); + } + else if (f_adc_clock / 4 <= speed_factor * ADC_MAX_FREQ) + { + return (ADC_LIB_CFG1_ADIV(2) + ADC_LIB_CFG1_ADICLK(0)); + } + else if (f_adc_clock / 8 <= speed_factor * ADC_MAX_FREQ) + { + return (ADC_LIB_CFG1_ADIV(3) + ADC_LIB_CFG1_ADICLK(0)); + } + else + { + return (ADC_LIB_CFG1_ADIV(3) + ADC_LIB_CFG1_ADICLK(1)); + } + } + //! \endcond + + // Settings for the power/speed of conversions/sampling + /*! ADC conversion speed. +* Common set of options to select the ADC clock speed F_ADCK, which depends on ADC_F_BUS, except for the ADACK_X_Y options that are independent. +* This selection affects the sampling speed too. +* Note: the F_ADCK speed is not equal to the conversion speed; any measurement takes several F_ADCK cycles to complete including the sampling and conversion steps. +*/ + enum class ADC_CONVERSION_SPEED : uint8_t + { +#if defined(ADC_TEENSY_4) + VERY_LOW_SPEED, /* Same as LOW_SPEED, here for compatibility*/ + LOW_SPEED = VERY_LOW_SPEED, /*!< is guaranteed to be the lowest possible speed within specs for all resolutions. */ + MED_SPEED, /*!< is always >= LOW_SPEED and <= HIGH_SPEED. */ + HIGH_SPEED, /*!< is guaranteed to be the highest possible speed within specs for resolutions */ + VERY_HIGH_SPEED = HIGH_SPEED, /* Same as HIGH_SPEED, here for compatibility*/ + + ADACK_10, /*!< 10 MHz asynchronous ADC clock (independent of the global clocks F_CPU or F_BUS) */ + ADACK_20 /*!< 20 MHz asynchronous ADC clock (independent of the global clocks F_CPU or F_BUS) */ +#else + VERY_LOW_SPEED, /*!< is guaranteed to be the lowest possible speed within specs for resolutions less than 16 bits. */ + LOW_SPEED, /*!< is guaranteed to be the lowest possible speed within specs for all resolutions. */ + MED_SPEED, /*!< is always >= LOW_SPEED and <= HIGH_SPEED. */ + HIGH_SPEED_16BITS, /*!< is guaranteed to be the highest possible speed within specs for all resolutions. */ + HIGH_SPEED, /*!< is guaranteed to be the highest possible speed within specs for resolutions less than 16 bits. */ + VERY_HIGH_SPEED, /*!< may be out of specs */ + + ADACK_2_4, /*!< 2.4 MHz asynchronous ADC clock (independent of the global clocks F_CPU or F_BUS) */ + ADACK_4_0, /*!< 4.0 MHz asynchronous ADC clock (independent of the global clocks F_CPU or F_BUS) */ + ADACK_5_2, /*!< 5.2 MHz asynchronous ADC clock (independent of the global clocks F_CPU or F_BUS) */ + ADACK_6_2 /*!< 6.2 MHz asynchronous ADC clock (independent of the global clocks F_CPU or F_BUS) */ +#endif + }; + /*! ADC sampling speed. +* It selects how many ADCK clock cycles to add. +*/ + enum class ADC_SAMPLING_SPEED : uint8_t + { +#if defined(ADC_TEENSY_4) + VERY_LOW_SPEED, /*!< is the lowest possible sampling speed (+22 ADCK, 24 in total). */ + LOW_SPEED, /*!< adds +18 ADCK, 20 in total. */ + LOW_MED_SPEED, /*!< adds +14, 16 in total. */ + MED_SPEED, /*!< adds +10, 12 in total. */ + MED_HIGH_SPEED, /*!< adds +6 ADCK, 8 in total. */ + HIGH_SPEED, /*!< adds +4 ADCK, 6 in total. */ + HIGH_VERY_HIGH_SPEED, /*!< +2 ADCK, 4 in total */ + VERY_HIGH_SPEED, /*!< is the highest possible sampling speed (0 ADCK added, 2 in total). */ +#else + VERY_LOW_SPEED, /*!< is the lowest possible sampling speed (+24 ADCK). */ + LOW_SPEED, /*!< adds +16 ADCK. */ + MED_SPEED, /*!< adds +10 ADCK. */ + HIGH_SPEED, /*!< adds +6 ADCK. */ + VERY_HIGH_SPEED, /*!< is the highest possible sampling speed (0 ADCK added). */ +#endif + }; + +// Mask for the channel selection in ADCx_SC1A, +// useful if you want to get the channel number from ADCx_SC1A +#define ADC_SC1A_CHANNELS (0x1F) +// 0x1F=31 in the channel2sc1aADCx means the pin doesn't belong to the ADC module +#define ADC_SC1A_PIN_INVALID (0x1F) +// Muxsel mask, pins in channel2sc1aADCx with bit 7 set use mux A. +#define ADC_SC1A_PIN_MUX (0x80) +// Differential pin mask, pins in channel2sc1aADCx with bit 6 set are differential pins. +#define ADC_SC1A_PIN_DIFF (0x40) +// PGA mask. The pins can use PGA on that ADC +#define ADC_SC1A_PIN_PGA (0x80) + +// Error codes for analogRead and analogReadDifferential +#define ADC_ERROR_DIFF_VALUE (-70000) +#define ADC_ERROR_VALUE ADC_ERROR_DIFF_VALUE + +} // namespace ADC_settings + +/*! \page error ADC error codes +Handle ADC errors. See the namespace ADC_Error for all functions. +*/ + +//! Handle ADC errors +namespace ADC_Error +{ + + //! ADC errors. + /*! Each board has a adc->adX->fail_flag. + * Include ADC_util.h and use getStringADCError to print the errors (if any) in a human-readable form. + * Use adc->adX->resetError() to reset them. + */ + enum class ADC_ERROR : uint16_t + { + OTHER = 1 << 0, /*!< Other error not considered below. */ + CALIB = 1 << 1, /*!< Calibration error. */ + WRONG_PIN = 1 << 2, /*!< A pin was selected that cannot be read by this ADC module. */ + ANALOG_READ = 1 << 3, /*!< Error inside the analogRead method. */ + ANALOG_DIFF_READ = 1 << 4, /*!< Error inside the analogReadDifferential method. */ + CONT = 1 << 5, /*!< Continuous single-ended measurement error. */ + CONT_DIFF = 1 << 6, /*!< Continuous differential measurement error. */ + COMPARISON = 1 << 7, /*!< Error during the comparison. */ + WRONG_ADC = 1 << 8, /*!< A non-existent ADC module was selected. */ + SYNCH = 1 << 9, /*!< Error during a synchronized measurement. */ + + CLEAR = 0, /*!< No error. */ + }; + //! \cond internal + //! OR operator for ADC_ERRORs. @internal + inline constexpr ADC_ERROR operator|(ADC_ERROR lhs, ADC_ERROR rhs) + { + return static_cast(static_cast(lhs) | static_cast(rhs)); + } + //! AND operator for ADC_ERRORs. @internal + inline constexpr ADC_ERROR operator&(ADC_ERROR lhs, ADC_ERROR rhs) + { + return static_cast(static_cast(lhs) & static_cast(rhs)); + } + //! |= operator for ADC_ERRORs, it changes the left hand side ADC_ERROR. @internal + inline ADC_ERROR operator|=(volatile ADC_ERROR &lhs, ADC_ERROR rhs) + { + return lhs = static_cast(static_cast(lhs) | static_cast(rhs)); + } + //! &= operator for ADC_ERRORs, it changes the left hand side ADC_ERROR. @internal + inline ADC_ERROR operator&=(volatile ADC_ERROR &lhs, ADC_ERROR rhs) + { + return lhs = static_cast(static_cast(lhs) & static_cast(rhs)); + } + + // Prints the human-readable error, if any. + // Moved to util.h. + // inline const char* getError(ADC_ERROR fail_flag) + + //! Resets all errors from the ADC, if any. @internal + inline void resetError(volatile ADC_ERROR &fail_flag) + { + fail_flag = ADC_ERROR::CLEAR; + } + //! \endcond + +} // namespace ADC_Error + +#endif // ADC_SETTINGS_H \ No newline at end of file diff --git a/Firmware_V3/lib/Bounce/Bounce.cpp b/Firmware_V3/lib/Bounce/Bounce.cpp new file mode 100644 index 0000000..06f100c --- /dev/null +++ b/Firmware_V3/lib/Bounce/Bounce.cpp @@ -0,0 +1,88 @@ + +// Please read Bounce.h for information about the liscence and authors + +#include +#include "Bounce.h" + + +Bounce::Bounce(uint8_t pin,unsigned long interval_millis) +{ + interval(interval_millis); + previous_millis = millis(); + state = digitalRead(pin); + this->pin = pin; +} + + +void Bounce::write(int new_state) + { + this->state = new_state; + digitalWrite(pin,state); + } + + +void Bounce::interval(unsigned long interval_millis) +{ + this->interval_millis = interval_millis; + this->rebounce_millis = 0; +} + +void Bounce::rebounce(unsigned long interval) +{ + this->rebounce_millis = interval; +} + + + +int Bounce::update() +{ + if ( debounce() ) { + rebounce(0); + return stateChanged = 1; + } + + // We need to rebounce, so simulate a state change + + if ( rebounce_millis && (millis() - previous_millis >= rebounce_millis) ) { + previous_millis = millis(); + rebounce(0); + return stateChanged = 1; + } + + return stateChanged = 0; +} + + +unsigned long Bounce::duration() +{ + return millis() - previous_millis; +} + + +int Bounce::read() +{ + return (int)state; +} + + +// Protected: debounces the pin +int Bounce::debounce() { + + uint8_t newState = digitalRead(pin); + if (state != newState ) { + if (millis() - previous_millis >= interval_millis) { + previous_millis = millis(); + state = newState; + return 1; + } + } + + return 0; + +} + +// The risingEdge method is true for one scan after the de-bounced input goes from off-to-on. +bool Bounce::risingEdge() { return stateChanged && state; } +// The fallingEdge method it true for one scan after the de-bounced input goes from on-to-off. +bool Bounce::fallingEdge() { return stateChanged && !state; } + diff --git a/Firmware_V3/lib/Bounce/Bounce.h b/Firmware_V3/lib/Bounce/Bounce.h new file mode 100644 index 0000000..7be294c --- /dev/null +++ b/Firmware_V3/lib/Bounce/Bounce.h @@ -0,0 +1,70 @@ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * + Main code by Thomas O Fredericks + Rebounce and duration functions contributed by Eric Lowry + Write function contributed by Jim Schimpf + risingEdge and fallingEdge contributed by Tom Harkaway +* * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef Bounce_h +#define Bounce_h + +#include + +class Bounce +{ + +public: + // Initialize + Bounce(uint8_t pin, unsigned long interval_millis ); + // Sets the debounce interval + void interval(unsigned long interval_millis); + // Updates the pin + // Returns 1 if the state changed + // Returns 0 if the state did not change + int update(); + // Forces the pin to signal a change (through update()) in X milliseconds + // even if the state does not actually change + // Example: press and hold a button and have it repeat every X milliseconds + void rebounce(unsigned long interval); + // Returns the updated pin state + int read(); + // Sets the stored pin state + void write(int new_state); + // Returns the number of milliseconds the pin has been in the current state + unsigned long duration(); + // The risingEdge method is true for one scan after the de-bounced input goes from off-to-on. + bool risingEdge(); + // The fallingEdge method it true for one scan after the de-bounced input goes from on-to-off. + bool fallingEdge(); + +protected: + int debounce(); + unsigned long previous_millis, interval_millis, rebounce_millis; + uint8_t state; + uint8_t pin; + uint8_t stateChanged; +}; + +#endif + + diff --git a/Firmware_V3/lib/EEPROM/EEPROM.cpp b/Firmware_V3/lib/EEPROM/EEPROM.cpp new file mode 100644 index 0000000..ec716dd --- /dev/null +++ b/Firmware_V3/lib/EEPROM/EEPROM.cpp @@ -0,0 +1 @@ +// this file no longer used diff --git a/Firmware_V3/lib/EEPROM/EEPROM.h b/Firmware_V3/lib/EEPROM/EEPROM.h new file mode 100644 index 0000000..4ca29ca --- /dev/null +++ b/Firmware_V3/lib/EEPROM/EEPROM.h @@ -0,0 +1,156 @@ +/* + EEPROM.h - EEPROM library + Original Copyright (c) 2006 David A. Mellis. All right reserved. + New version by Christopher Andrews 2015. + This copy has minor modificatons for use with Teensy, by Paul Stoffregen + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef EEPROM_h +#define EEPROM_h + +#include +#include +#include + + +/*** + EERef class. + + This object references an EEPROM cell. + Its purpose is to mimic a typical byte of RAM, however its storage is the EEPROM. + This class has an overhead of two bytes, similar to storing a pointer to an EEPROM cell. +***/ + +struct EERef{ + + EERef( const int index ) + : index( index ) {} + + //Access/read members. + uint8_t operator*() const { return eeprom_read_byte( (uint8_t*) index ); } + operator const uint8_t() const { return **this; } + + //Assignment/write members. + EERef &operator=( const EERef &ref ) { return *this = *ref; } + EERef &operator=( uint8_t in ) { return eeprom_write_byte( (uint8_t*) index, in ), *this; } + EERef &operator +=( uint8_t in ) { return *this = **this + in; } + EERef &operator -=( uint8_t in ) { return *this = **this - in; } + EERef &operator *=( uint8_t in ) { return *this = **this * in; } + EERef &operator /=( uint8_t in ) { return *this = **this / in; } + EERef &operator ^=( uint8_t in ) { return *this = **this ^ in; } + EERef &operator %=( uint8_t in ) { return *this = **this % in; } + EERef &operator &=( uint8_t in ) { return *this = **this & in; } + EERef &operator |=( uint8_t in ) { return *this = **this | in; } + EERef &operator <<=( uint8_t in ) { return *this = **this << in; } + EERef &operator >>=( uint8_t in ) { return *this = **this >> in; } + + EERef &update( uint8_t in ) { return in != *this ? *this = in : *this; } + + /** Prefix increment/decrement **/ + EERef& operator++() { return *this += 1; } + EERef& operator--() { return *this -= 1; } + + /** Postfix increment/decrement **/ + uint8_t operator++ (int) { + uint8_t ret = **this; + return ++(*this), ret; + } + + uint8_t operator-- (int) { + uint8_t ret = **this; + return --(*this), ret; + } + + int index; //Index of current EEPROM cell. +}; + +/*** + EEPtr class. + + This object is a bidirectional pointer to EEPROM cells represented by EERef objects. + Just like a normal pointer type, this can be dereferenced and repositioned using + increment/decrement operators. +***/ + +struct EEPtr{ + + EEPtr( const int index ) + : index( index ) {} + + operator const int() const { return index; } + EEPtr &operator=( int in ) { return index = in, *this; } + + //Iterator functionality. + bool operator!=( const EEPtr &ptr ) { return index != ptr.index; } + EERef operator*() { return index; } + + /** Prefix & Postfix increment/decrement **/ + EEPtr& operator++() { return ++index, *this; } + EEPtr& operator--() { return --index, *this; } + EEPtr operator++ (int) { return index++; } + EEPtr operator-- (int) { return index--; } + + int index; //Index of current EEPROM cell. +}; + +/*** + EEPROMClass class. + + This object represents the entire EEPROM space. + It wraps the functionality of EEPtr and EERef into a basic interface. + This class is also 100% backwards compatible with earlier Arduino core releases. +***/ + +struct EEPROMClass{ + +#if defined(__arm__) && defined(TEENSYDUINO) + EEPROMClass() { eeprom_initialize(); } +#endif + + //Basic user access methods. + EERef operator[]( const int idx ) { return idx; } + uint8_t read( int idx ) { return EERef( idx ); } + void write( int idx, uint8_t val ) { (EERef( idx )) = val; } + void update( int idx, uint8_t val ) { EERef( idx ).update( val ); } + + //STL and C++11 iteration capability. + EEPtr begin() { return 0x00; } + EEPtr end() { return length(); } //Standards requires this to be the item after the last valid entry. The returned pointer is invalid. + uint16_t length() { return E2END + 1; } + + //Functionality to 'get' and 'put' objects to and from EEPROM. + template< typename T > T &get( int idx, T &t ){ + EEPtr e = idx; + uint8_t *ptr = (uint8_t*) &t; + for( int count = sizeof(T) ; count ; --count, ++e ) *ptr++ = *e; + return t; + } + + template< typename T > const T &put( int idx, const T &t ){ + const uint8_t *ptr = (const uint8_t*) &t; +#ifdef __arm__ + eeprom_write_block(ptr, (void *)idx, sizeof(T)); +#else + EEPtr e = idx; + for( int count = sizeof(T) ; count ; --count, ++e ) (*e).update( *ptr++ ); +#endif + return t; + } +}; + +static EEPROMClass EEPROM __attribute__ ((unused)); +#endif diff --git a/Firmware_V3/lib/LittleFS/LittleFS.cpp b/Firmware_V3/lib/LittleFS/LittleFS.cpp new file mode 100644 index 0000000..8139ea2 --- /dev/null +++ b/Firmware_V3/lib/LittleFS/LittleFS.cpp @@ -0,0 +1,949 @@ +/* LittleFS for Teensy + * Copyright (c) 2020, Paul Stoffregen, paul@pjrc.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice, development funding notice, and this permission + * notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#define SPICONFIG SPISettings(30000000, MSBFIRST, SPI_MODE0) + + + +PROGMEM static const struct chipinfo { + uint8_t id[3]; + uint8_t addrbits; // number of address bits, 24 or 32 + uint16_t progsize; // page size for programming, in bytes + uint32_t erasesize; // sector size for erasing, in bytes + uint32_t chipsize; // total number of bytes in the chip + uint32_t progtime; // maximum microseconds to wait for page programming + uint32_t erasetime; // maximum microseconds to wait for sector erase +} known_chips[] = { + {{0xEF, 0x40, 0x15}, 24, 256, 4096, 2097152, 3000, 400000}, // Winbond W25Q16JV*IQ/W25Q16FV + {{0xEF, 0x40, 0x16}, 24, 256, 4096, 4194304, 3000, 400000}, // Winbond W25Q32JV*IQ/W25Q32FV + {{0xEF, 0x40, 0x17}, 24, 256, 4096, 8388608, 3000, 400000}, // Winbond W25Q64JV*IQ/W25Q64FV + {{0xEF, 0x40, 0x18}, 24, 256, 4096, 16777216, 3000, 400000}, // Winbond W25Q128JV*IQ/W25Q128FV + {{0xEF, 0x40, 0x19}, 32, 256, 4096, 33554432, 3000, 400000}, // Winbond W25Q256JV*IQ + {{0xEF, 0x40, 0x20}, 32, 256, 4096, 67108864, 3500, 400000}, // Winbond W25Q512JV*IQ + {{0xEF, 0x70, 0x17}, 24, 256, 4096, 8388608, 3000, 400000}, // Winbond W25Q64JV*IM (DTR) + {{0xEF, 0x70, 0x18}, 24, 256, 4096, 16777216, 3000, 400000}, // Winbond W25Q128JV*IM (DTR) + {{0xEF, 0x70, 0x19}, 32, 256, 4096, 33554432, 3000, 400000}, // Winbond W25Q256JV*IM (DTR) + {{0xEF, 0x70, 0x20}, 32, 256, 4096, 67108864, 3500, 400000}, // Winbond W25Q512JV*IM (DTR) + {{0x1F, 0x84, 0x01}, 24, 256, 4096, 524288, 2500, 300000}, // Adesto/Atmel AT25SF041 + {{0x01, 0x40, 0x14}, 24, 256, 4096, 1048576, 5000, 300000}, // Spansion S25FL208K + //FRAM + {{0x03, 0x2E, 0xC2}, 24, 64, 128, 1048576, 250, 1200}, //Cypress 8Mb FRAM + {{0xC2, 0x24, 0x00}, 24, 64, 128, 131072, 250, 1200}, //Cypress 1Mb FRAM + {{0xC2, 0x24, 0x01}, 24, 64, 128, 131072, 250, 1200}, //Cypress 1Mb FRAM, rev1 + {{0xAE, 0x83, 0x09}, 24, 64, 128, 131072, 250, 1200}, //ROHM MR45V100A 1 Mbit FeRAM Memory + {{0xC2, 0x26, 0x08}, 24, 64, 128, 131072, 250, 1200}, //Cypress 4Mb FRAM + +}; + +static const struct chipinfo * chip_lookup(const uint8_t *id) +{ + const unsigned int numchips = sizeof(known_chips) / sizeof(struct chipinfo); + for (unsigned int i=0; i < numchips; i++) { + const uint8_t *chip = known_chips[i].id; + if (id[0] == chip[0] && id[1] == chip[1] && id[2] == chip[2]) { + return known_chips + i; + } + } + return nullptr; +} + +FLASHMEM +bool LittleFS_SPIFlash::begin(uint8_t cspin, SPIClass &spiport) +{ + pin = cspin; + port = &spiport; + + //Serial.println("flash begin"); + configured = false; + digitalWrite(pin, HIGH); + pinMode(pin, OUTPUT); + port->begin(); + + uint8_t buf[4] = {0x9F, 0, 0, 0}; + port->beginTransaction(SPICONFIG); + digitalWrite(pin, LOW); + port->transfer(buf, 4); + digitalWrite(pin, HIGH); + port->endTransaction(); + + //Serial.printf("Flash ID: %02X %02X %02X %02X\n", buf[1], buf[2], buf[3], buf[4]); + const struct chipinfo *info = chip_lookup(buf + 1); + if (!info) return false; + //Serial.printf("Flash size is %.2f Mbyte\n", (float)info->chipsize / 1048576.0f); + + memset(&lfs, 0, sizeof(lfs)); + memset(&config, 0, sizeof(config)); + config.context = (void *)this; + config.read = &static_read; + config.prog = &static_prog; + config.erase = &static_erase; + config.sync = &static_sync; + config.read_size = info->progsize; + config.prog_size = info->progsize; + config.block_size = info->erasesize; + config.block_count = info->chipsize / info->erasesize; + config.block_cycles = 400; + config.cache_size = info->progsize; + config.lookahead_size = info->progsize; + // config.lookahead_size = config.block_count/8; + config.name_max = LFS_NAME_MAX; + addrbits = info->addrbits; + progtime = info->progtime; + erasetime = info->erasetime; + configured = true; + + //Serial.println("attempting to mount existing media"); + if (lfs_mount(&lfs, &config) < 0) { + //Serial.println("couldn't mount media, attemping to format"); + if (lfs_format(&lfs, &config) < 0) { + //Serial.println("format failed :("); + port = nullptr; + return false; + } + //Serial.println("attempting to mount freshly formatted media"); + if (lfs_mount(&lfs, &config) < 0) { + //Serial.println("mount after format failed :("); + port = nullptr; + return false; + } + } + mounted = true; + //Serial.println("success"); + return true; +} + +FLASHMEM +bool LittleFS_SPIFram::begin(uint8_t cspin, SPIClass &spiport) +{ + pin = cspin; + port = &spiport; + + //Serial.println("flash begin"); + configured = false; + digitalWrite(pin, HIGH); + pinMode(pin, OUTPUT); + port->begin(); + + delay(100); + uint8_t buf[9]; + + port->beginTransaction(SPICONFIG); + digitalWrite(pin, LOW); + delayNanoseconds(50); + port->transfer(0x9f); //0x9f - JEDEC register + for(uint8_t i = 0; i<9; i++) + buf[i] = port->transfer(0); + //delayNanoseconds(50); + digitalWriteFast(pin, HIGH); // Chip deselect + port->endTransaction(); + + + if(buf[0] == 0x7F){ + buf[0] = buf[6]; + buf[1] = buf[7]; + buf[2] = buf[8]; + } + //Serial.printf("Flash ID: %02X %02X %02X\n", buf[0], buf[1], buf[2]); + const struct chipinfo *info = chip_lookup(buf ); + if (!info) return false; + //Serial.printf("Flash size is %.2f Mbyte\n", (float)info->chipsize / 1048576.0f); + + memset(&lfs, 0, sizeof(lfs)); + memset(&config, 0, sizeof(config)); + config.context = (void *)this; + config.read = &static_read; + config.prog = &static_prog; + config.erase = &static_erase; + config.sync = &static_sync; + config.read_size = info->progsize; + config.prog_size = info->progsize; + config.block_size = info->erasesize; + config.block_count = info->chipsize / info->erasesize; + config.block_cycles = 400; + config.cache_size = info->progsize; + config.lookahead_size = info->progsize; + config.name_max = LFS_NAME_MAX; + addrbits = info->addrbits; + progtime = info->progtime; + erasetime = info->erasetime; + configured = true; + + Serial.println("attempting to mount existing media"); + if (lfs_mount(&lfs, &config) < 0) { + Serial.println("couldn't mount media, attemping to format"); + if (lfs_format(&lfs, &config) < 0) { + Serial.println("format failed :("); + port = nullptr; + return false; + } + Serial.println("attempting to mount freshly formatted media"); + if (lfs_mount(&lfs, &config) < 0) { + Serial.println("mount after format failed :("); + port = nullptr; + return false; + } + } + mounted = true; + //Serial.println("success"); + return true; +} + + +FLASHMEM +bool LittleFS::quickFormat() +{ + if (!configured) return false; + if (mounted) { + //Serial.println("unmounting filesystem"); + lfs_unmount(&lfs); + mounted = false; + // TODO: What happens if lingering LittleFSFile instances + // still have lfs_file_t structs allocated which reference + // this previously mounted filesystem? + } + //Serial.println("attempting to format existing media"); + if (lfs_format(&lfs, &config) < 0) { + //Serial.println("format failed :("); + return false; + } + //Serial.println("attempting to mount freshly formatted media"); + if (lfs_mount(&lfs, &config) < 0) { + //Serial.println("mount after format failed :("); + return false; + } + mounted = true; + //Serial.println("success"); + return true; +} + +static bool blockIsBlank(struct lfs_config *config, lfs_block_t block, void *readBuf, bool full=true ); +static bool blockIsBlank(struct lfs_config *config, lfs_block_t block, void *readBuf, bool full ) +{ + if (!readBuf) return false; + for (lfs_off_t offset=0; offset < config->block_size; offset += config->read_size) { + memset(readBuf, 0, config->read_size); + config->read(config, block, offset, readBuf, config->read_size); + const uint8_t *buf = (uint8_t *)readBuf; + for (unsigned int i=0; i < config->read_size; i++) { + if (buf[i] != 0xFF) return false; + } + if ( !full ) + return true; // first bytes read as 0xFF + } + return true; // all bytes read as 0xFF +} + +static int cb_usedBlocks( void *inData, lfs_block_t block ) +{ + static lfs_block_t maxBlock; + static uint32_t totBlock; + if ( nullptr == inData ) { // not null during traverse + uint32_t totRet = totBlock; + if ( 0 != block ) { + maxBlock = block; + totBlock = 0; + } + return totRet; // exit after init, end, or bad call + } + totBlock++; + if ( block > maxBlock ) return block; // this is beyond media blocks + uint32_t iiblk = block/8; + uint8_t jjbit = 1<<(block%8); + uint8_t *myData = (uint8_t *)inData; + myData[iiblk] = myData[iiblk] | jjbit; + return 0; +} + +FLASHMEM +uint32_t LittleFS::formatUnused(uint32_t blockCnt, uint32_t blockStart) { + if ( !configured ) return 0; + uint32_t iiblk = 1+(config.block_count /8); + uint8_t *checkused = (uint8_t *)malloc( iiblk ); + if ( checkused == nullptr) return 0; + void *buffer = malloc(config.read_size); + if ( buffer == nullptr) { + free(checkused); + return 0; + } + memset(checkused, 0, iiblk); + cb_usedBlocks( nullptr, config.block_count ); // init and pass MAX block_count + int err = lfs_fs_traverse(&lfs, cb_usedBlocks, checkused); // on return 1 bits are used blocks + + if ( err < 0 ) + { + free(checkused); + free(buffer); + return 0; + } + uint32_t block=blockStart, jj=0; + if ( block >= config.block_count ) blockStart=0; + if ( 0 == blockCnt) blockCnt = config.block_count; + while ( block= config.block_count ) block=0; + return block; // return lastChecked block to store to start next pass as blockStart +} + +FLASHMEM +bool LittleFS::lowLevelFormat(char progressChar) +{ + if (!configured) return false; + if (mounted) { + lfs_unmount(&lfs); + mounted = false; + } + int ii=config.block_count/120; + void *buffer = malloc(config.read_size); + for (unsigned int block=0; block < config.block_count; block++) { + if (progressChar && (0 == block%ii) ) Serial.write(progressChar); + if (!blockIsBlank(&config, block, buffer)) { + (*config.erase)(&config, block); + } + } + free(buffer); + if (progressChar) Serial.println(); + return quickFormat(); +} + +static void make_command_and_address(uint8_t *buf, uint8_t cmd, uint32_t addr, uint8_t addrbits) +{ + buf[0] = cmd; + if (addrbits == 24) { + buf[1] = addr >> 16; + buf[2] = addr >> 8; + buf[3] = addr; + } else { + buf[1] = addr >> 24; + buf[2] = addr >> 16; + buf[3] = addr >> 8; + buf[4] = addr; + } +} +static void printtbuf(const void *buf, unsigned int len) __attribute__((unused)); +static void printtbuf(const void *buf, unsigned int len) +{ + //const uint8_t *p = (const uint8_t *)buf; + //Serial.print(" "); + //while (len--) Serial.printf("%02X ", *p++); + //Serial.println(); +} + +int LittleFS_SPIFlash::read(lfs_block_t block, lfs_off_t offset, void *buf, lfs_size_t size) +{ + if (!port) return LFS_ERR_IO; + const uint32_t addr = block * config.block_size + offset; + const uint8_t cmd = (addrbits == 24) ? 0x03 : 0x13; // standard read command + uint8_t cmdaddr[5]; + //Serial.printf(" addrbits=%d\n", addrbits); + make_command_and_address(cmdaddr, cmd, addr, addrbits); + //printtbuf(cmdaddr, 1 + (addrbits >> 3)); + memset(buf, 0, size); + port->beginTransaction(SPICONFIG); + digitalWrite(pin, LOW); + port->transfer(cmdaddr, 1 + (addrbits >> 3)); + port->transfer(buf, size); + digitalWrite(pin, HIGH); + port->endTransaction(); + //printtbuf(buf, 20); + return 0; +} + +int LittleFS_SPIFlash::prog(lfs_block_t block, lfs_off_t offset, const void *buf, lfs_size_t size) +{ + if (!port) return LFS_ERR_IO; + const uint32_t addr = block * config.block_size + offset; + const uint8_t cmd = (addrbits == 24) ? 0x02 : 0x12; // page program + uint8_t cmdaddr[5]; + make_command_and_address(cmdaddr, cmd, addr, addrbits); + //printtbuf(cmdaddr, 1 + (addrbits >> 3)); + port->beginTransaction(SPICONFIG); + digitalWrite(pin, LOW); + port->transfer(0x06); // 0x06 = write enable + digitalWrite(pin, HIGH); + delayNanoseconds(250); + digitalWrite(pin, LOW); + port->transfer(cmdaddr, 1 + (addrbits >> 3)); + port->transfer(buf, nullptr, size); + digitalWrite(pin, HIGH); + port->endTransaction(); + //printtbuf(buf, 20); + return wait(progtime); +} + +int LittleFS_SPIFlash::erase(lfs_block_t block) +{ + if (!port) return LFS_ERR_IO; + void *buffer = malloc(config.read_size); + if ( buffer != nullptr) { + if ( blockIsBlank(&config, block, buffer)) { + free(buffer); + return 0; // Already formatted exit no wait + } + free(buffer); + } + const uint32_t addr = block * config.block_size; + const uint8_t cmd = (addrbits == 24) ? 0x20 : 0x21; // erase sector + uint8_t cmdaddr[5]; + make_command_and_address(cmdaddr, cmd, addr, addrbits); + //printtbuf(cmdaddr, 1 + (addrbits >> 3)); + port->beginTransaction(SPICONFIG); + digitalWrite(pin, LOW); + port->transfer(0x06); // 0x06 = write enable + digitalWrite(pin, HIGH); + delayNanoseconds(250); + digitalWrite(pin, LOW); + port->transfer(cmdaddr, 1 + (addrbits >> 3)); + digitalWrite(pin, HIGH); + port->endTransaction(); + return wait(erasetime); +} + +int LittleFS_SPIFlash::wait(uint32_t microseconds) +{ + elapsedMicros usec = 0; + while (1) { + port->beginTransaction(SPICONFIG); + digitalWrite(pin, LOW); + uint16_t status = port->transfer16(0x0500); // 0x05 = get status + digitalWrite(pin, HIGH); + port->endTransaction(); + if (!(status & 1)) break; + if (usec > microseconds) return LFS_ERR_IO; // timeout + yield(); + } + //Serial.printf(" waited %u us\n", (unsigned int)usec); + return 0; // success +} + + + +int LittleFS_SPIFram::read(lfs_block_t block, lfs_off_t offset, void *buf, lfs_size_t size) +{ + if (!port) return LFS_ERR_IO; + const uint32_t addr = block * config.block_size + offset; + + //FRAM READ OPERATION + uint8_t cmdaddr[5]; + //Serial.printf(" addrbits=%d\n", addrbits); + make_command_and_address(cmdaddr, 0x03, addr, addrbits); + memset(buf, 0, size); + port->beginTransaction(SPICONFIG); + digitalWrite(pin,LOW); //chip select + port->transfer(cmdaddr, 1 + (addrbits >> 3)); + port->transfer(buf, size); + digitalWrite(pin,HIGH); //release chip, signal end of transfer + port->endTransaction(); + + //printtbuf(buf, 20); + return 0; +} + +int LittleFS_SPIFram::prog(lfs_block_t block, lfs_off_t offset, const void *buf, lfs_size_t size) +{ + if (!port) return LFS_ERR_IO; + const uint32_t addr = block * config.block_size + offset; + + // F-RAM WRITE ENABLE COMMAND + uint8_t cmdaddr[5]; + //Serial.printf(" addrbits=%d\n", addrbits); + make_command_and_address(cmdaddr, 0x02, addr, addrbits); + + port->beginTransaction(SPICONFIG); + digitalWrite(pin,LOW); //chip select + delayNanoseconds(50); + SPI.transfer(0x06); //transmit write enable opcode + digitalWrite(pin,HIGH); //release chip, signal end transfer + delayNanoseconds(50); + // F-RAM WRITE OPERATION + digitalWrite(pin,LOW); //chip select + port->transfer(cmdaddr, 1 + (addrbits >> 3)); + // Data byte transmission + port->transfer(buf, nullptr, size); + digitalWrite(pin,HIGH); //release chip, signal end of transfer + port->endTransaction(); + + return 0; +} + +int LittleFS_SPIFram::erase(lfs_block_t block) +{ + if (!port) return LFS_ERR_IO; + void *buffer = malloc(config.read_size); + if ( buffer != nullptr) { + if ( blockIsBlank(&config, block, buffer)) { + free(buffer); + return 0; // Already formatted exit no wait + } + free(buffer); + } + //Serial.printf(" flash er: block=%d\n", block); + uint8_t buf[256]; + //for(uint32_t i = 0; i < config.block_size; i++) buf[i] = 0xFF; + memset(buf, 0xFF, config.block_size); + uint8_t cmdaddr[5]; + const uint32_t addr = block * config.block_size; + make_command_and_address(cmdaddr, 0x02, addr, addrbits); + + // F-RAM WRITE ENABLE COMMAND + port->beginTransaction(SPICONFIG); + digitalWrite(pin,LOW); //chip select + SPI.transfer(0x06); //transmit write enable opcode + digitalWrite(pin,HIGH); //release chip, signal end transfer + delayNanoseconds(50); + // F-RAM WRITE OPERATION + digitalWrite(pin,LOW); //chip select + port->transfer(cmdaddr, 1 + (addrbits >> 3)); + + // Data byte transmission + port->transfer(buf, nullptr, config.block_size); + digitalWrite(pin,HIGH); //release chip, signal end of transfer + port->endTransaction(); + + return 0; +} + +int LittleFS_SPIFram::wait(uint32_t microseconds) +{ + elapsedMicros usec = 0; + while (1) { + if (usec > microseconds) break; // timeout + yield(); + } + //Serial.printf(" waited %u us\n", (unsigned int)usec); + return 0; // success +} + + + + + +#if defined(__IMXRT1062__) + +#define LUT0(opcode, pads, operand) (FLEXSPI_LUT_INSTRUCTION((opcode), (pads), (operand))) +#define LUT1(opcode, pads, operand) (FLEXSPI_LUT_INSTRUCTION((opcode), (pads), (operand)) << 16) +#define CMD_SDR FLEXSPI_LUT_OPCODE_CMD_SDR +#define ADDR_SDR FLEXSPI_LUT_OPCODE_RADDR_SDR +#define READ_SDR FLEXSPI_LUT_OPCODE_READ_SDR +#define WRITE_SDR FLEXSPI_LUT_OPCODE_WRITE_SDR +#define DUMMY_SDR FLEXSPI_LUT_OPCODE_DUMMY_SDR +#define PINS1 FLEXSPI_LUT_NUM_PADS_1 +#define PINS4 FLEXSPI_LUT_NUM_PADS_4 + +static void flexspi2_ip_command(uint32_t index, uint32_t addr) +{ + uint32_t n; + FLEXSPI2_IPCR0 = addr; + FLEXSPI2_IPCR1 = FLEXSPI_IPCR1_ISEQID(index); + FLEXSPI2_IPCMD = FLEXSPI_IPCMD_TRG; + while (!((n = FLEXSPI2_INTR) & FLEXSPI_INTR_IPCMDDONE)); // wait + if (n & FLEXSPI_INTR_IPCMDERR) { + FLEXSPI2_INTR = FLEXSPI_INTR_IPCMDERR; + //Serial.printf("Error: FLEXSPI2_IPRXFSTS=%08lX\n", FLEXSPI2_IPRXFSTS); + } + FLEXSPI2_INTR = FLEXSPI_INTR_IPCMDDONE; +} + +static void flexspi2_ip_read(uint32_t index, uint32_t addr, void *data, uint32_t length) +{ + uint8_t *p = (uint8_t *)data; + + FLEXSPI2_INTR = FLEXSPI_INTR_IPRXWA; + // Clear RX FIFO and set watermark to 16 bytes + FLEXSPI2_IPRXFCR = FLEXSPI_IPRXFCR_CLRIPRXF | FLEXSPI_IPRXFCR_RXWMRK(1); + FLEXSPI2_IPCR0 = addr; + FLEXSPI2_IPCR1 = FLEXSPI_IPCR1_ISEQID(index) | FLEXSPI_IPCR1_IDATSZ(length); + FLEXSPI2_IPCMD = FLEXSPI_IPCMD_TRG; +// page 1649 : Reading Data from IP RX FIFO +// page 1706 : Interrupt Register (INTR) +// page 1723 : IP RX FIFO Control Register (IPRXFCR) +// page 1732 : IP RX FIFO Status Register (IPRXFSTS) + + while (1) { + if (length >= 16) { + if (FLEXSPI2_INTR & FLEXSPI_INTR_IPRXWA) { + volatile uint32_t *fifo = &FLEXSPI2_RFDR0; + uint32_t a = *fifo++; + uint32_t b = *fifo++; + uint32_t c = *fifo++; + uint32_t d = *fifo++; + *(uint32_t *)(p+0) = a; + *(uint32_t *)(p+4) = b; + *(uint32_t *)(p+8) = c; + *(uint32_t *)(p+12) = d; + p += 16; + length -= 16; + FLEXSPI2_INTR = FLEXSPI_INTR_IPRXWA; + } + } else if (length > 0) { + if ((FLEXSPI2_IPRXFSTS & 0xFF) >= ((length + 7) >> 3)) { + volatile uint32_t *fifo = &FLEXSPI2_RFDR0; + while (length >= 4) { + *(uint32_t *)(p) = *fifo++; + p += 4; + length -= 4; + } + uint32_t a = *fifo; + if (length >= 1) { + *p++ = a & 0xFF; + a = a >> 8; + } + if (length >= 2) { + *p++ = a & 0xFF; + a = a >> 8; + } + if (length >= 3) { + *p++ = a & 0xFF; + a = a >> 8; + } + length = 0; + } + } else { + if (FLEXSPI2_INTR & FLEXSPI_INTR_IPCMDDONE) break; + } + // TODO: timeout... + } + if (FLEXSPI2_INTR & FLEXSPI_INTR_IPCMDERR) { + FLEXSPI2_INTR = FLEXSPI_INTR_IPCMDERR; + //Serial.printf("Error: FLEXSPI2_IPRXFSTS=%08lX\r\n", FLEXSPI2_IPRXFSTS); + } + FLEXSPI2_INTR = FLEXSPI_INTR_IPCMDDONE; +} + +static void flexspi2_ip_write(uint32_t index, uint32_t addr, const void *data, uint32_t length) +{ + const uint8_t *src; + uint32_t n, wrlen; + + FLEXSPI2_IPCR0 = addr; + FLEXSPI2_IPCR1 = FLEXSPI_IPCR1_ISEQID(index) | FLEXSPI_IPCR1_IDATSZ(length); + src = (const uint8_t *)data; + FLEXSPI2_IPCMD = FLEXSPI_IPCMD_TRG; + while (!((n = FLEXSPI2_INTR) & FLEXSPI_INTR_IPCMDDONE)) { + if (n & FLEXSPI_INTR_IPTXWE) { + wrlen = length; + if (wrlen > 8) wrlen = 8; + if (wrlen > 0) { + //Serial.print("%"); + memcpy((void *)&FLEXSPI2_TFDR0, src, wrlen); + src += wrlen; + length -= wrlen; + FLEXSPI2_INTR = FLEXSPI_INTR_IPTXWE; + } + } + } + if (n & FLEXSPI_INTR_IPCMDERR) { + FLEXSPI2_INTR = FLEXSPI_INTR_IPCMDERR; + //Serial.printf("Error: FLEXSPI2_IPRXFSTS=%08lX\r\n", FLEXSPI2_IPRXFSTS); + } + FLEXSPI2_INTR = FLEXSPI_INTR_IPCMDDONE; +} + + + + + + +FLASHMEM +bool LittleFS_QSPIFlash::begin() +{ + //Serial.println("QSPI flash begin"); + + configured = false; + + uint8_t buf[4] = {0, 0, 0, 0}; + + FLEXSPI2_LUTKEY = FLEXSPI_LUTKEY_VALUE; + FLEXSPI2_LUTCR = FLEXSPI_LUTCR_UNLOCK; + // cmd index 8 = read ID bytes + FLEXSPI2_LUT32 = LUT0(CMD_SDR, PINS1, 0x9F) | LUT1(READ_SDR, PINS1, 1); + FLEXSPI2_LUT33 = 0; + + flexspi2_ip_read(8, 0x00800000, buf, 3); + + + //Serial.printf("Flash ID: %02X %02X %02X\n", buf[0], buf[1], buf[2]); + const struct chipinfo *info = chip_lookup(buf); + if (!info) return false; + //Serial.printf("Flash size is %.2f Mbyte\n", (float)info->chipsize / 1048576.0f); + + memset(&lfs, 0, sizeof(lfs)); + memset(&config, 0, sizeof(config)); + config.context = (void *)this; + config.read = &static_read; + config.prog = &static_prog; + config.erase = &static_erase; + config.sync = &static_sync; + config.read_size = info->progsize; + config.prog_size = info->progsize; + config.block_size = info->erasesize; + config.block_count = info->chipsize / info->erasesize; + config.block_cycles = 400; + config.cache_size = info->progsize; + config.lookahead_size = info->progsize; + //config.lookahead_size = config.block_count/8; + config.name_max = LFS_NAME_MAX; + addrbits = info->addrbits; + progtime = info->progtime; + erasetime = info->erasetime; + configured = true; + + // configure FlexSPI2 for chip's size + FLEXSPI2_FLSHA2CR0 = info->chipsize / 1024; + + FLEXSPI2_LUTKEY = FLEXSPI_LUTKEY_VALUE; + FLEXSPI2_LUTCR = FLEXSPI_LUTCR_UNLOCK; + + // TODO: is this Winbond specific? Diable for non-Winbond chips... + FLEXSPI2_LUT40 = LUT0(CMD_SDR, PINS1, 0x50); + flexspi2_ip_command(10, 0x00800000); // volatile write status enable + FLEXSPI2_LUT40 = LUT0(CMD_SDR, PINS1, 0x31) | LUT1(CMD_SDR, PINS1, 0x02); + FLEXSPI2_LUT41 = 0; + flexspi2_ip_command(10, 0x00800000); // enable quad mode + + if (addrbits == 24) { + // cmd index 9 = read QSPI (1-1-4) + FLEXSPI2_LUT36 = LUT0(CMD_SDR, PINS1, 0x6B) | LUT1(ADDR_SDR, PINS1, 24); + FLEXSPI2_LUT37 = LUT0(DUMMY_SDR, PINS4, 8) | LUT1(READ_SDR, PINS4, 1); + FLEXSPI2_LUT38 = 0; + // cmd index 11 = program QSPI (1-1-4) + FLEXSPI2_LUT44 = LUT0(CMD_SDR, PINS1, 0x32) | LUT1(ADDR_SDR, PINS1, 24); + FLEXSPI2_LUT45 = LUT0(WRITE_SDR, PINS4, 1); + // cmd index 12 = sector erase + FLEXSPI2_LUT48 = LUT0(CMD_SDR, PINS1, 0x20) | LUT1(ADDR_SDR, PINS1, 24); + FLEXSPI2_LUT49 = 0; + } else { + // cmd index 9 = read QSPI (1-1-4) + FLEXSPI2_LUT36 = LUT0(CMD_SDR, PINS1, 0x6C) | LUT1(ADDR_SDR, PINS1, 32); + FLEXSPI2_LUT37 = LUT0(DUMMY_SDR, PINS4, 8) | LUT1(READ_SDR, PINS4, 1); + FLEXSPI2_LUT38 = 0; + // cmd index 11 = program QSPI (1-1-4) + FLEXSPI2_LUT44 = LUT0(CMD_SDR, PINS1, 0x34) | LUT1(ADDR_SDR, PINS1, 32); + FLEXSPI2_LUT45 = LUT0(WRITE_SDR, PINS4, 1); + // cmd index 12 = sector erase + FLEXSPI2_LUT48 = LUT0(CMD_SDR, PINS1, 0x21) | LUT1(ADDR_SDR, PINS1, 32); + FLEXSPI2_LUT49 = 0; + // cmd index 9 = read SPI (1-1-1) + //FLEXSPI2_LUT36 = LUT0(CMD_SDR, PINS1, 0x13) | LUT1(ADDR_SDR, PINS1, 32); + //FLEXSPI2_LUT37 = LUT0(READ_SDR, PINS1, 1); + // cmd index 11 = program SPI (1-1-1) + //FLEXSPI2_LUT44 = LUT0(CMD_SDR, PINS1, 0x12) | LUT1(ADDR_SDR, PINS1, 32); + //FLEXSPI2_LUT45 = LUT0(WRITE_SDR, PINS1, 1); + } + // cmd index 10 = write enable + FLEXSPI2_LUT40 = LUT0(CMD_SDR, PINS1, 0x06); + // cmd index 13 = get status + FLEXSPI2_LUT52 = LUT0(CMD_SDR, PINS1, 0x05) | LUT1(READ_SDR, PINS1, 1); + FLEXSPI2_LUT53 = 0; + + + //Serial.println("attempting to mount existing media"); + if (lfs_mount(&lfs, &config) < 0) { + //Serial.println("couldn't mount media, attemping to format"); + if (lfs_format(&lfs, &config) < 0) { + //Serial.println("format failed :("); + return false; + } + //Serial.println("attempting to mount freshly formatted media"); + if (lfs_mount(&lfs, &config) < 0) { + //Serial.println("mount after format failed :("); + return false; + } + } + mounted = true; + //Serial.println("success"); + return true; +} + +int LittleFS_QSPIFlash::read(lfs_block_t block, lfs_off_t offset, void *buf, lfs_size_t size) +{ + const uint32_t addr = block * config.block_size + offset; + flexspi2_ip_read(9, 0x00800000 + addr, buf, size); + // TODO: detect errors, return LFS_ERR_IO + //printtbuf(buf, 20); + return 0; +} + +int LittleFS_QSPIFlash::prog(lfs_block_t block, lfs_off_t offset, const void *buf, lfs_size_t size) +{ + flexspi2_ip_command(10, 0x00800000); + const uint32_t addr = block * config.block_size + offset; + //printtbuf(buf, 20); + flexspi2_ip_write(11, 0x00800000 + addr, buf, size); + // TODO: detect errors, return LFS_ERR_IO + return wait(progtime); +} + +int LittleFS_QSPIFlash::erase(lfs_block_t block) +{ + void *buffer = malloc(config.read_size); + if ( buffer != nullptr) { + if ( blockIsBlank(&config, block, buffer)) { + free(buffer); + return 0; // Already formatted exit no wait + } + free(buffer); + } + flexspi2_ip_command(10, 0x00800000); + const uint32_t addr = block * config.block_size; + flexspi2_ip_command(12, 0x00800000 + addr); + // TODO: detect errors, return LFS_ERR_IO + return wait(erasetime); +} + +int LittleFS_QSPIFlash::wait(uint32_t microseconds) +{ + elapsedMicros usec = 0; + while (1) { + uint8_t status; + flexspi2_ip_read(13, 0x00800000, &status, 1); + if (!(status & 1)) break; + if (usec > microseconds) return LFS_ERR_IO; // timeout + yield(); + } + //Serial.printf(" waited %u us\n", (unsigned int)usec); + return 0; // success +} + +#endif // __IMXRT1062__ + + + + + + + +#if defined(__IMXRT1062__) + +#if defined(ARDUINO_TEENSY40) +#define FLASH_SIZE 0x1F0000 +#elif defined(ARDUINO_TEENSY41) +#define FLASH_SIZE 0x7C0000 +#elif defined(ARDUINO_TEENSY_MICROMOD) +#define FLASH_SIZE 0xFC0000 +#endif +extern unsigned long _flashimagelen; +uint32_t LittleFS_Program::baseaddr = 0; + +FLASHMEM +bool LittleFS_Program::begin(uint32_t size) +{ + //Serial.println("Program flash begin"); + configured = false; + baseaddr = 0; + size = size & 0xFFFF0000; + if (size == 0) return false; + const uint32_t program_size = (uint32_t)&_flashimagelen + 4096; // extra 4K for CSF + if (program_size >= FLASH_SIZE) return false; + const uint32_t available_space = FLASH_SIZE - program_size; + //Serial.printf("available_space = %u\n", available_space); + if (size > available_space) return false; + + baseaddr = 0x60000000 + FLASH_SIZE - size; + //Serial.printf("baseaddr = %x\n", baseaddr); + + memset(&lfs, 0, sizeof(lfs)); + memset(&config, 0, sizeof(config)); + config.context = (void *)baseaddr; + config.read = &static_read; + config.prog = &static_prog; + config.erase = &static_erase; + config.sync = &static_sync; + config.read_size = 128; + config.prog_size = 128; + config.block_size = 4096; + config.block_count = size >> 12; + config.block_cycles = 800; + config.cache_size = 128; + config.lookahead_size = 128; + config.name_max = LFS_NAME_MAX; + configured = true; + + //Serial.println("attempting to mount existing media"); + if (lfs_mount(&lfs, &config) < 0) { + //Serial.println("couldn't mount media, attemping to format"); + if (lfs_format(&lfs, &config) < 0) { + //Serial.println("format failed :("); + return false; + } + //Serial.println("attempting to mount freshly formatted media"); + if (lfs_mount(&lfs, &config) < 0) { + //Serial.println("mount after format failed :("); + return false; + } + } + mounted = true; + return true; +} + +int LittleFS_Program::static_read(const struct lfs_config *c, lfs_block_t block, + lfs_off_t offset, void *buffer, lfs_size_t size) +{ + //Serial.printf(" prog rd: block=%d, offset=%d, size=%d\n", block, offset, size); + const uint8_t *p = (uint8_t *)(baseaddr + block * 4096 + offset); + memcpy(buffer, p, size); + return 0; +} + +// from eeprom.c +extern "C" void eepromemu_flash_write(void *addr, const void *data, uint32_t len); +extern "C" void eepromemu_flash_erase_sector(void *addr); + +int LittleFS_Program::static_prog(const struct lfs_config *c, lfs_block_t block, + lfs_off_t offset, const void *buffer, lfs_size_t size) +{ + //Serial.printf(" prog wr: block=%d, offset=%d, size=%d\n", block, offset, size); + uint8_t *p = (uint8_t *)(baseaddr + block * 4096 + offset); + eepromemu_flash_write(p, buffer, size); + return 0; +} + +int LittleFS_Program::static_erase(const struct lfs_config *c, lfs_block_t block) +{ + //Serial.printf(" prog er: block=%d\n", block); + uint8_t *p = (uint8_t *)(baseaddr + block * 4096); + eepromemu_flash_erase_sector(p); + return 0; +} + + +#endif // __IMXRT1062__ + + + + + diff --git a/Firmware_V3/lib/LittleFS/LittleFS.h b/Firmware_V3/lib/LittleFS/LittleFS.h new file mode 100644 index 0000000..4c326da --- /dev/null +++ b/Firmware_V3/lib/LittleFS/LittleFS.h @@ -0,0 +1,524 @@ +/* LittleFS for Teensy + * Copyright (c) 2020, Paul Stoffregen, paul@pjrc.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice, development funding notice, and this permission + * notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once +#include +#include +#include +#include "littlefs/lfs.h" + +class LittleFSFile : public File +{ +private: + // Classes derived from File are never meant to be constructed from + // anywhere other than openNextFile() and open() in their parent FS + // class. Only the abstract File class which references these + // derived classes is meant to have a public constructor! + LittleFSFile(lfs_t *lfsin, lfs_file_t *filein, const char *name) { + lfs = lfsin; + file = filein; + dir = nullptr; + strlcpy(fullpath, name, sizeof(fullpath)); + //Serial.printf(" LittleFSFile ctor (file), this=%x\n", (int)this); + } + LittleFSFile(lfs_t *lfsin, lfs_dir_t *dirin, const char *name) { + lfs = lfsin; + dir = dirin; + file = nullptr; + strlcpy(fullpath, name, sizeof(fullpath)); + //Serial.printf(" LittleFSFile ctor (dir), this=%x\n", (int)this); + } + friend class LittleFS; +public: + virtual ~LittleFSFile() { + //Serial.printf(" LittleFSFile dtor, this=%x\n", (int)this); + close(); + } +#ifdef FILE_WHOAMI + virtual void whoami() { + Serial.printf(" LittleFSFile this=%x, refcount=%u\n", + (int)this, getRefcount()); + } +#endif + virtual size_t write(const void *buf, size_t size) { + //Serial.println("write"); + if (!file) return 0; + //Serial.println(" is regular file"); + return lfs_file_write(lfs, file, buf, size); + } + virtual int peek() { + return -1; // TODO... + } + virtual int available() { + if (!file) return 0; + lfs_soff_t pos = lfs_file_tell(lfs, file); + if (pos < 0) return 0; + lfs_soff_t size = lfs_file_size(lfs, file); + if (size < 0) return 0; + return size - pos; + } + virtual void flush() { + if (file) lfs_file_sync(lfs, file); + } + virtual size_t read(void *buf, size_t nbyte) { + if (file) { + lfs_ssize_t r = lfs_file_read(lfs, file, buf, nbyte); + if (r < 0) r = 0; + return r; + } + return 0; + } + virtual bool truncate(uint64_t size=0) { + if (!file) return false; + if (lfs_file_truncate(lfs, file, size) >= 0) return true; + return false; + } + virtual bool seek(uint64_t pos, int mode = SeekSet) { + if (!file) return false; + int whence; + if (mode == SeekSet) whence = LFS_SEEK_SET; + else if (mode == SeekCur) whence = LFS_SEEK_CUR; + else if (mode == SeekEnd) whence = LFS_SEEK_END; + else return false; + if (lfs_file_seek(lfs, file, pos, whence) >= 0) return true; + return false; + } + virtual uint64_t position() { + if (!file) return 0; + lfs_soff_t pos = lfs_file_tell(lfs, file); + if (pos < 0) pos = 0; + return pos; + } + virtual uint64_t size() { + if (!file) return 0; + lfs_soff_t size = lfs_file_size(lfs, file); + if (size < 0) size = 0; + return size; + } + virtual void close() { + if (file) { + //Serial.printf(" close file, this=%x, lfs=%x", (int)this, (int)lfs); + lfs_file_close(lfs, file); // we get stuck here, but why? + free(file); + file = nullptr; + } + if (dir) { + //Serial.printf(" close dir, this=%x, lfs=%x", (int)this, (int)lfs); + lfs_dir_close(lfs, dir); + free(dir); + dir = nullptr; + } + //Serial.println(" end of close"); + } + virtual operator bool() { + return file || dir; + } + virtual const char * name() { + const char *p = strrchr(fullpath, '/'); + if (p) return p + 1; + return fullpath; + } + virtual boolean isDirectory(void) { + return dir != nullptr; + } + virtual File openNextFile(uint8_t mode=0) { + if (!dir) return File(); + struct lfs_info info; + do { + memset(&info, 0, sizeof(info)); // is this necessary? + if (lfs_dir_read(lfs, dir, &info) <= 0) return File(); + } while (strcmp(info.name, ".") == 0 || strcmp(info.name, "..") == 0); + //Serial.printf(" next name = \"%s\"\n", info.name); + char pathname[128]; + strlcpy(pathname, fullpath, sizeof(pathname)); + size_t len = strlen(pathname); + if (len > 0 && pathname[len-1] != '/' && len < sizeof(pathname)-2) { + // add trailing '/', if not already present + pathname[len++] = '/'; + pathname[len] = 0; + } + strlcpy(pathname + len, info.name, sizeof(pathname) - len); + if (info.type == LFS_TYPE_REG) { + lfs_file_t *f = (lfs_file_t *)malloc(sizeof(lfs_file_t)); + if (!f) return File(); + if (lfs_file_open(lfs, f, pathname, LFS_O_RDONLY) >= 0) { + return File(new LittleFSFile(lfs, f, pathname)); + } + free(f); + } else { // LFS_TYPE_DIR + lfs_dir_t *d = (lfs_dir_t *)malloc(sizeof(lfs_dir_t)); + if (!d) return File(); + if (lfs_dir_open(lfs, d, pathname) >= 0) { + return File(new LittleFSFile(lfs, d, pathname)); + } + free(d); + } + return File(); + } + virtual void rewindDirectory(void) { + if (dir) lfs_dir_rewind(lfs, dir); + } + + using Print::write; +private: + lfs_t *lfs; + lfs_file_t *file; + lfs_dir_t *dir; + char *filename; + char fullpath[128]; +}; + + + + +class LittleFS : public FS +{ +public: + LittleFS() { + configured = false; + mounted = false; + config.context = nullptr; + } + bool quickFormat(); + bool lowLevelFormat(char progressChar=0); + uint32_t formatUnused(uint32_t blockCnt, uint32_t blockStart); + File open(const char *filepath, uint8_t mode = FILE_READ) { + //Serial.println("LittleFS open"); + if (!mounted) return File(); + if (mode == FILE_READ) { + struct lfs_info info; + if (lfs_stat(&lfs, filepath, &info) < 0) return File(); + //Serial.printf("LittleFS open got info, name=%s\n", info.name); + if (info.type == LFS_TYPE_REG) { + //Serial.println(" regular file"); + lfs_file_t *file = (lfs_file_t *)malloc(sizeof(lfs_file_t)); + if (!file) return File(); + if (lfs_file_open(&lfs, file, filepath, LFS_O_RDONLY) >= 0) { + return File(new LittleFSFile(&lfs, file, filepath)); + } + free(file); + } else { // LFS_TYPE_DIR + //Serial.println(" directory"); + lfs_dir_t *dir = (lfs_dir_t *)malloc(sizeof(lfs_dir_t)); + if (!dir) return File(); + if (lfs_dir_open(&lfs, dir, filepath) >= 0) { + return File(new LittleFSFile(&lfs, dir, filepath)); + } + free(dir); + } + } else { + lfs_file_t *file = (lfs_file_t *)malloc(sizeof(lfs_file_t)); + if (!file) return File(); + if (lfs_file_open(&lfs, file, filepath, LFS_O_RDWR | LFS_O_CREAT) >= 0) { + if (mode == FILE_WRITE) { + // FILE_WRITE opens at end of file + lfs_file_seek(&lfs, file, 0, LFS_SEEK_END); + } // else FILE_WRITE_BEGIN + return File(new LittleFSFile(&lfs, file, filepath)); + } + } + return File(); + } + bool exists(const char *filepath) { + if (!mounted) return false; + struct lfs_info info; + if (lfs_stat(&lfs, filepath, &info) < 0) return false; + return true; + } + bool mkdir(const char *filepath) { + if (!mounted) return false; + if (lfs_mkdir(&lfs, filepath) < 0) return false; + return true; + } + bool rename(const char *oldfilepath, const char *newfilepath) { + if (!mounted) return false; + if (lfs_rename(&lfs, oldfilepath, newfilepath) < 0) return false; + return true; + } + bool remove(const char *filepath) { + if (!mounted) return false; + if (lfs_remove(&lfs, filepath) < 0) return false; + return true; + } + bool rmdir(const char *filepath) { + return remove(filepath); + } + uint64_t usedSize() { + if (!mounted) return 0; + int blocks = lfs_fs_size(&lfs); + if (blocks < 0 || (lfs_size_t)blocks > config.block_count) return totalSize(); + return blocks * config.block_size; + } + uint64_t totalSize() { + if (!mounted) return 0; + return config.block_count * config.block_size; + } +protected: + bool configured; + bool mounted; + lfs_t lfs; + lfs_config config; +}; + + + + + +class LittleFS_RAM : public LittleFS +{ +public: + LittleFS_RAM() { } + bool begin(uint32_t size) { +#if defined(__IMXRT1062__) + return begin(extmem_malloc(size), size); +#else + return begin(malloc(size), size); +#endif + } + bool begin(void *ptr, uint32_t size) { + //Serial.println("configure "); delay(5); + configured = false; + if (!ptr) return false; + memset(ptr, 0xFF, size); // always start with blank slate + size = size & 0xFFFFFF00; + memset(&lfs, 0, sizeof(lfs)); + memset(&config, 0, sizeof(config)); + config.context = ptr; + config.read = &static_read; + config.prog = &static_prog; + config.erase = &static_erase; + config.sync = &static_sync; + if ( size > 1024*1024 ) { + config.read_size = 256; // Must set cache_size. If read_buffer or prog_buffer are provided manually, these must be cache_size. + config.prog_size = 256; + config.block_size = 2048; + config.block_count = size / config.block_size; + config.block_cycles = 900; + config.cache_size = 256; + config.lookahead_size = 512; // (config.block_count / 8 + 7) & 0xFFFFFFF8; + } + else { + config.read_size = 64; + config.prog_size = 64; + config.block_size = 256; + config.block_count = size / 256; + config.block_cycles = 50; + config.cache_size = 64; + config.lookahead_size = 64; + } + config.name_max = LFS_NAME_MAX; + config.file_max = 0; + config.attr_max = 0; + configured = true; + if (lfs_format(&lfs, &config) < 0) return false; + //Serial.println("formatted"); + if (lfs_mount(&lfs, &config) < 0) return false; + //Serial.println("mounted atfer format"); + mounted = true; + return true; + } + FLASHMEM + uint32_t formatUnused(uint32_t blockCnt, uint32_t blockStart) { + return 0; + } +private: + static int static_read(const struct lfs_config *c, lfs_block_t block, + lfs_off_t offset, void *buffer, lfs_size_t size) { + //Serial.printf(" ram rd: block=%d, offset=%d, size=%d\n", block, offset, size); + uint32_t index = block * c->block_size + offset; + memcpy(buffer, (uint8_t *)(c->context) + index, size); + return 0; + } + static int static_prog(const struct lfs_config *c, lfs_block_t block, + lfs_off_t offset, const void *buffer, lfs_size_t size) { + //Serial.printf(" ram wr: block=%d, offset=%d, size=%d\n", block, offset, size); + uint32_t index = block * c->block_size + offset; + memcpy((uint8_t *)(c->context) + index, buffer, size); + return 0; + } + static int static_erase(const struct lfs_config *c, lfs_block_t block) { + uint32_t index = block * c->block_size; + memset((uint8_t *)(c->context) + index, 0xFF, c->block_size); + return 0; + } + static int static_sync(const struct lfs_config *c) { + return 0; + } +}; + + +class LittleFS_SPIFlash : public LittleFS +{ +public: + LittleFS_SPIFlash() { + port = nullptr; + } + bool begin(uint8_t cspin, SPIClass &spiport=SPI); +private: + int read(lfs_block_t block, lfs_off_t offset, void *buf, lfs_size_t size); + int prog(lfs_block_t block, lfs_off_t offset, const void *buf, lfs_size_t size); + int erase(lfs_block_t block); + int wait(uint32_t microseconds); + static int static_read(const struct lfs_config *c, lfs_block_t block, + lfs_off_t offset, void *buffer, lfs_size_t size) { + //Serial.printf(" flash rd: block=%d, offset=%d, size=%d\n", block, offset, size); + return ((LittleFS_SPIFlash *)(c->context))->read(block, offset, buffer, size); + } + static int static_prog(const struct lfs_config *c, lfs_block_t block, + lfs_off_t offset, const void *buffer, lfs_size_t size) { + //Serial.printf(" flash wr: block=%d, offset=%d, size=%d\n", block, offset, size); + return ((LittleFS_SPIFlash *)(c->context))->prog(block, offset, buffer, size); + } + static int static_erase(const struct lfs_config *c, lfs_block_t block) { + //Serial.printf(" flash er: block=%d\n", block); + return ((LittleFS_SPIFlash *)(c->context))->erase(block); + } + static int static_sync(const struct lfs_config *c) { + return 0; + } + SPIClass *port; + uint8_t pin; + uint8_t addrbits; + uint32_t progtime; + uint32_t erasetime; +}; + + + +class LittleFS_SPIFram : public LittleFS +{ +public: + LittleFS_SPIFram() { + port = nullptr; + } + bool begin(uint8_t cspin, SPIClass &spiport=SPI); +private: + int read(lfs_block_t block, lfs_off_t offset, void *buf, lfs_size_t size); + int prog(lfs_block_t block, lfs_off_t offset, const void *buf, lfs_size_t size); + int erase(lfs_block_t block); + int wait(uint32_t microseconds); + static int static_read(const struct lfs_config *c, lfs_block_t block, + lfs_off_t offset, void *buffer, lfs_size_t size) { + //Serial.printf(" flash rd: block=%d, offset=%d, size=%d\n", block, offset, size); + return ((LittleFS_SPIFram *)(c->context))->read(block, offset, buffer, size); + } + static int static_prog(const struct lfs_config *c, lfs_block_t block, + lfs_off_t offset, const void *buffer, lfs_size_t size) { + //Serial.printf(" flash wr: block=%d, offset=%d, size=%d\n", block, offset, size); + return ((LittleFS_SPIFram *)(c->context))->prog(block, offset, buffer, size); + } + static int static_erase(const struct lfs_config *c, lfs_block_t block) { + //Serial.printf(" flash er: block=%d\n", block); + return ((LittleFS_SPIFram *)(c->context))->erase(block); + } + static int static_sync(const struct lfs_config *c) { + return 0; + } + + + + SPIClass *port; + uint8_t pin; + uint8_t addrbits; + uint32_t progtime; + uint32_t erasetime; +}; + + +#if defined(__IMXRT1062__) +class LittleFS_QSPIFlash : public LittleFS +{ +public: + LittleFS_QSPIFlash() { } + bool begin(); +private: + int read(lfs_block_t block, lfs_off_t offset, void *buf, lfs_size_t size); + int prog(lfs_block_t block, lfs_off_t offset, const void *buf, lfs_size_t size); + int erase(lfs_block_t block); + int wait(uint32_t microseconds); + static int static_read(const struct lfs_config *c, lfs_block_t block, + lfs_off_t offset, void *buffer, lfs_size_t size) { + //Serial.printf(" qspi rd: block=%d, offset=%d, size=%d\n", block, offset, size); + return ((LittleFS_QSPIFlash *)(c->context))->read(block, offset, buffer, size); + } + static int static_prog(const struct lfs_config *c, lfs_block_t block, + lfs_off_t offset, const void *buffer, lfs_size_t size) { + //Serial.printf(" qspi wr: block=%d, offset=%d, size=%d\n", block, offset, size); + return ((LittleFS_QSPIFlash *)(c->context))->prog(block, offset, buffer, size); + } + static int static_erase(const struct lfs_config *c, lfs_block_t block) { + //Serial.printf(" qspi er: block=%d\n", block); + return ((LittleFS_QSPIFlash *)(c->context))->erase(block); + } + static int static_sync(const struct lfs_config *c) { + return 0; + } + uint8_t addrbits; + uint32_t progtime; + uint32_t erasetime; +}; +#else +class LittleFS_QSPIFlash : public LittleFS +{ +public: + LittleFS_QSPIFlash() { } + bool begin() { return false; } +}; +#endif + + + +#if defined(__IMXRT1062__) +class LittleFS_Program : public LittleFS +{ +public: + LittleFS_Program() { } + bool begin(uint32_t size); +private: + static int static_read(const struct lfs_config *c, lfs_block_t block, + lfs_off_t offset, void *buffer, lfs_size_t size); + static int static_prog(const struct lfs_config *c, lfs_block_t block, + lfs_off_t offset, const void *buffer, lfs_size_t size); + static int static_erase(const struct lfs_config *c, lfs_block_t block); + static int static_sync(const struct lfs_config *c) { return 0; } + static uint32_t baseaddr; +}; +#else +// TODO: implement for Teensy 3.x... +class LittleFS_Program : public LittleFS +{ +public: + LittleFS_Program() { } + bool begin(uint32_t size) { return false; } +}; +#endif + + + + + + + + + + + diff --git a/Firmware_V3/lib/LittleFS/LittleFS_NAND.cpp b/Firmware_V3/lib/LittleFS/LittleFS_NAND.cpp new file mode 100644 index 0000000..0250780 --- /dev/null +++ b/Firmware_V3/lib/LittleFS/LittleFS_NAND.cpp @@ -0,0 +1,1436 @@ +/* LittleFS for Teensy + * Copyright (c) 2020, Paul Stoffregen, paul@pjrc.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice, development funding notice, and this permission + * notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#define SPICONFIG_NAND SPISettings(55000000, MSBFIRST, SPI_MODE0) + + +PROGMEM static const struct chipinfo { + uint8_t id[3]; + uint8_t addrbits; // number of address bits, 24 or 32 + uint16_t progsize; // page size for programming, in bytes + uint32_t erasesize; // sector size for erasing, in bytes + uint32_t chipsize; // total number of bytes in the chip + uint32_t progtime; // maximum microseconds to wait for page programming + uint32_t erasetime; // maximum microseconds to wait for sector erase +} known_chips[] = { + //NAND + //{{0xEF, 0xAA, 0x21}, 24, 2048, 131072, 134217728, 2000, 15000}, //Winbond W25N01G + //Upper 24 blocks * 128KB/block will be used for bad block replacement area + //so reducing total chip size: 134217728 - 24*131072 + {{0xEF, 0xAA, 0x21}, 24, 2048, 131072, 131596288, 2000, 15000}, //Winbond W25N01G + //{{0xEF, 0xAA, 0x22}, 24, 2048, 131072, 134217728*2, 2000, 15000}, //Winbond W25N02G + {{0xEF, 0xAA, 0x22}, 24, 2048, 131072, 265289728, 2000, 15000}, //Winbond W25N02G + {{0xEF, 0xBB, 0x21}, 24, 2048, 131072, 265289728, 2000, 15000}, //Winbond W25M02 +}; + +volatile uint32_t currentPage = UINT32_MAX; +volatile uint32_t currentPageRead = UINT32_MAX; + + +static const struct chipinfo * chip_lookup(const uint8_t *id) +{ + const unsigned int numchips = sizeof(known_chips) / sizeof(struct chipinfo); + for (unsigned int i=0; i < numchips; i++) { + const uint8_t *chip = known_chips[i].id; + if (id[0] == chip[0] && id[1] == chip[1] && id[2] == chip[2]) { + return known_chips + i; + } + } + return nullptr; +} + + +FLASHMEM +bool LittleFS_SPINAND::begin(uint8_t cspin, SPIClass &spiport) +{ + pin = cspin; + port = &spiport; + + //Serial.println("flash begin"); + configured = false; + digitalWrite(pin, HIGH); + pinMode(pin, OUTPUT); + port->begin(); + + uint8_t buf[5] = {0x9F, 0, 0, 0, 0}; + port->beginTransaction(SPICONFIG_NAND); + digitalWrite(pin, LOW); + port->transfer(buf, 5); + digitalWrite(pin, HIGH); + port->endTransaction(); + + //Serial.printf("Flash ID: %02X %02X %02X\n", buf[2], buf[3], buf[4]); + const struct chipinfo *info = chip_lookup(buf+2); + if (!info) return false; + //Serial.printf("Flash size is %.2f Mbyte\n", (float)info->chipsize / 1048576.0f); + + //capacityID = buf[3]; //W25N01G has 1 die, W25N02G had 2 dies + deviceID = (buf[2] << 16) | (buf[3] << 8) | (buf[4]); + //Serial.printf("Device ID: 0x%6X\n", deviceID); + + if(deviceID == W25N01) { + die = 1; + eccSize = 64; + PAGE_ECCSIZE = 2112; + } else if(deviceID == W25N02) { + die = 1; + eccSize = 128; + PAGE_ECCSIZE = 2176; + } else if(deviceID == W25M02) { + dies = 2; + eccSize = 64; + PAGE_ECCSIZE = 2112; + } + + + //uint8_t status; + // No protection, WP-E off, WP-E prevents use of IO2. PROT_REG(0xAO), PROT_CLEAR(0) + writeStatusRegister(0xA0, 0); + readStatusRegister(0xA0, false); + + // Buffered read mode (BUF = 1), ECC enabled (ECC = 1), 0xB0(0xB0), ECC_ENABLE((1 << 4)), ReadMode((1 << 3)) + writeStatusRegister(0xB0, (1 << 4) | (1 << 3)); + readStatusRegister(0xB0, false); + + memset(&lfs, 0, sizeof(lfs)); + memset(&config, 0, sizeof(config)); + config.context = (void *)this; + config.read = &static_read; + config.prog = &static_prog; + config.erase = &static_erase; + config.sync = &static_sync; + config.read_size = info->progsize; + config.prog_size = info->progsize; + config.block_size = info->erasesize; + config.block_count = info->chipsize / info->erasesize; + config.block_cycles = 400; + config.cache_size = info->progsize; + config.lookahead_size = info->progsize; + config.name_max = LFS_NAME_MAX; + addrbits = info->addrbits; + progtime = info->progtime; + erasetime = info->erasetime; + configured = true; + blocksize = info->erasesize; + chipsize = info->chipsize; + + //Serial.println("attempting to mount existing media"); + if (lfs_mount(&lfs, &config) < 0) { + //Serial.println("couldn't mount media, attemping to format"); + if (lfs_format(&lfs, &config) < 0) { + //Serial.println("format failed :("); + port = nullptr; + return false; + } + //Serial.println("attempting to mount freshly formatted media"); + if (lfs_mount(&lfs, &config) < 0) { + //Serial.println("mount after format failed :("); + port = nullptr; + return false; + } + } + mounted = true; + //Serial.println("success"); + return true; +} + + +static void printtbuf(const void *buf, unsigned int len) __attribute__((unused)); +static void printtbuf(const void *buf, unsigned int len) +{ + //const uint8_t *p = (const uint8_t *)buf; + //Serial.print(" "); + //while (len--) Serial.printf("%02X ", *p++); + //Serial.println(); +} + +static bool blockIsBlank(struct lfs_config *config, lfs_block_t block, void *readBuf, bool full=true ); +static bool blockIsBlank(struct lfs_config *config, lfs_block_t block, void *readBuf, bool full) +{ + if (!readBuf) return false; + for (lfs_off_t offset=0; offset < config->block_size; offset += config->read_size) { + memset(readBuf, 0, config->read_size); + config->read(config, block, offset, readBuf, config->read_size); + const uint8_t *buf = (uint8_t *)readBuf; + //printtbuf(buf, 20); + for (unsigned int i=0; i < config->read_size; i++) { + if (buf[i] != 0xFF) return false; + } + if ( !full ) + return true; // first bytes read as 0xFF + } + return true; // all bytes read as 0xFF +} + +int LittleFS_SPINAND::read(lfs_block_t block, lfs_off_t offset, void *buf, lfs_size_t size) +{ + if (!port) return LFS_ERR_IO; + const uint32_t addr = block * config.block_size + offset; + + if(deviceID == W25N01){ + uint16_t targetPage = LINEAR_TO_PAGE(addr); + if(currentPageRead != targetPage){ + loadPage(addr); + currentPageRead = targetPage; + } + } else { + loadPage(addr); + } + + wait(progtime); + + uint16_t column = LINEAR_TO_COLUMN(addr); + + uint8_t cmd[4]; + cmd[0] = 0x03; //0x03, READ Data + cmd[1] = column >> 8; + cmd[2] = column; + cmd[3] = 0; + + port->beginTransaction(SPICONFIG_NAND); + digitalWrite(pin, LOW); + port->transfer(cmd, 4); + port->transfer(buf, size); + digitalWrite(pin, HIGH); + port->endTransaction(); + + wait(progtime); + + // Check ECC + uint8_t statReg = readStatusRegister(0xC0, false); + uint8_t eccCode = (((statReg) & ((1 << 5)|(1 << 4))) >> 4); + + wait(progtime); + + switch (eccCode) { + case 0: // Successful read, no ECC correction + break; + case 1: // Successful read with ECC correction + //Serial.printf("Successful read with ECC correction (addr, code): %x, %x\n", addr, eccCode); + case 2: // Uncorrectable ECC in a single page + //Serial.printf("Uncorrectable ECC in a single page (addr, code): %x, %x\n", addr, eccCode); + case 3: // Uncorrectable ECC in multiple pages + addBBLUT(LINEAR_TO_BLOCK(addr)); + //deviceReset(); + //Serial.printf("Uncorrectable ECC in a multipe pages (addr, code): %x, %x\n", addr, eccCode); + break; + } + + //printtbuf(buf, 20); + return 0; +} + +int LittleFS_SPINAND::prog(lfs_block_t block, lfs_off_t offset, const void *buf, lfs_size_t size) +{ + if (!port) return LFS_ERR_IO; + + uint8_t cmd1[4], die_select; + + const uint32_t address = block * config.block_size + offset; + + //Program Data Load + uint16_t columnAddress = LINEAR_TO_COLUMN(address); + uint32_t pageAddress = LINEAR_TO_PAGE(address); + + if(deviceID == W25M02 || deviceID == W25N02) { + if(pageAddress > pagesPerDie) { + //pageAddress -= pagesPerDie; + die_select = 1; + cmd1[1] = 1; + } else { + cmd1[1] = 0; + die_select = 0; + } + } else { + cmd1[1] = 0; + } + + if(deviceID == W25M02) { + //issue Select Die command before issuing a page load + port->beginTransaction(SPICONFIG_NAND); + digitalWrite(pin, LOW); + port -> transfer(0xC2); + port -> transfer(die_select); + digitalWrite(pin, HIGH); + port->endTransaction(); + if(pageAddress > pagesPerDie) + pageAddress -= pagesPerDie; //W25M02 has 2 separate W25N01 dies addressed individually + cmd1[1] = 0; //dummy block for write is 0. + wait(progtime); + } + + writeEnable(); //sets the WEL in Status Reg to 1 (bit 2) + + uint8_t cmd[3]; + cmd[0] = 0x02; //program data load, 0x02, write data to the data buffer + cmd[1] = columnAddress >> 8; + cmd[2] = columnAddress; + + port->beginTransaction(SPICONFIG_NAND); + digitalWrite(pin, LOW); + port->transfer(cmd, 3); + port->transfer(buf, nullptr, size); + digitalWrite(pin, HIGH); + port->endTransaction(); + + wait(progtime); + //uint8_t status = readStatusRegister(0xA0, false ); //0xA0 - status register + //if ((status & (1 << 3)) == 1) //Status Program Fail + // Serial.println( "Programed Status: FAILED" ); + + //Program Execute, 0x10 + cmd1[0] = 0x10; //Program Execute, write from data buffer to physical memory page sepc + //cmd1[1] defined above + cmd1[2] = pageAddress >> 8; + cmd1[3] = pageAddress; + port->beginTransaction(SPICONFIG_NAND); + digitalWrite(pin, LOW); + port->transfer(cmd1, 4); + digitalWrite(pin, HIGH); + port->endTransaction(); + + return wait(progtime); +} + +int LittleFS_SPINAND::erase(lfs_block_t block) +{ + if (!port) return LFS_ERR_IO; + + const uint32_t addr = block * config.block_size; + + void *buffer = malloc(config.read_size); + if ( buffer != nullptr) { + if ( blockIsBlank(&config, block, buffer)) { + free(buffer); + return 0; // Already formatted exit no wait + } + free(buffer); + } + + eraseSector(addr); + return wait(erasetime); +} + +bool LittleFS_SPINAND::isReady() +{ + uint8_t val; + port->beginTransaction(SPICONFIG_NAND); + digitalWrite(pin, LOW); + port->transfer(0x05); //0x05 - read status register + port->transfer(0xC0); + val = port->transfer(0x00); + digitalWrite(pin, HIGH); + port->endTransaction(); + return ((val & (1 << 0)) == 0); +} + +int LittleFS_SPINAND::wait(uint32_t microseconds) +{ + elapsedMicros usec = 0; + while (1) { + if (isReady()) break; + if (usec > microseconds) return LFS_ERR_IO; // timeout + yield(); + } + //Serial.printf(" waited %u us\n", (unsigned int)usec); + return 0; // success +} + + + bool LittleFS_SPINAND::writeEnable() + { + uint8_t status; + port->beginTransaction(SPICONFIG_NAND); + digitalWrite(pin, LOW); + port->transfer(0x06); //Write Enable 0x06 + digitalWrite(pin, HIGH); + port->endTransaction(); + wait(progtime); + + status = readStatusRegister(0xC0, false); + return status & (0x02); + } + + +void LittleFS_SPINAND::eraseSector(uint32_t address) +{ + uint32_t pageAddr = LINEAR_TO_PAGE(address) ; + + //if(pageAddr > pagesPerDie) pageAddr -= pagesPerDie; + + uint8_t cmd[4]; + uint8_t die_select = 0; + + if(deviceID == W25M02 || deviceID == W25N02) { + if(pageAddr > pagesPerDie) { + cmd[1] = 1; + die_select = 1; + } else { + cmd[1] = 0; + die_select = 0; + } + } else { + cmd[1] = 0; + } + + if(deviceID == W25M02) { + //setup new LUT to issue Select Die command before issuing a page load + port->beginTransaction(SPICONFIG_NAND); + digitalWrite(pin, LOW); + port -> transfer(0xC2); //die select + port -> transfer(die_select); + digitalWrite(pin, HIGH); + port->endTransaction(); + if(pageAddr > pagesPerDie) + pageAddr -= pagesPerDie; //W25M02 has 2 separate W25N01 dies addressed individually + cmd[1] = 0; //dummy block for write is 0. + + wait(progtime); + } + + cmd[0] = 0xD8; //Block erase, 0xD8 + cmd[2] = pageAddr >> 8; + cmd[3] = pageAddr; + + writeEnable(); + + port->beginTransaction(SPICONFIG_NAND); + digitalWrite(pin, LOW); + port->transfer(cmd, 4); + digitalWrite(pin, HIGH); + port->endTransaction(); + + //uint16_t status = readStatusRegister(0x05,false); + //if ((status & (1 << 2)) == 1) //Status erase Fail + // Serial.println( "erase Status: FAILED "); +} + + +void LittleFS_SPINAND::writeStatusRegister(uint8_t reg, uint8_t data) +{ + port->beginTransaction(SPICONFIG_NAND); + digitalWrite(pin, LOW); + port->transfer(0x01); //0x01 - write status register + port->transfer(reg); + port->transfer(data); + digitalWrite(pin, HIGH); + port->endTransaction(); + +} + + +uint8_t LittleFS_SPINAND::readStatusRegister(uint16_t reg, bool dump) +{ + uint8_t val; + port->beginTransaction(SPICONFIG_NAND); + digitalWrite(pin, LOW); + port->transfer(0x05); //0x05 - read status register + port->transfer(reg); + val = port->transfer(0x00); + digitalWrite(pin, HIGH); + port->endTransaction(); + + if(dump) { + //Serial.printf("Status of reg 0x%x: \n", reg); + //Serial.printf("(HEX: ) 0x%02X, (Binary: )", val); + //Serial.println(val, BIN); + //Serial.println(); + } + + return val; + +} + + + +//////////////////////////////////////////////////////////// +void LittleFS_SPINAND::loadPage(uint32_t address) +{ + uint32_t targetPage = LINEAR_TO_PAGE(address); + + uint8_t cmd[4], die_select; + + if(targetPage > pagesPerDie) { + die_select = 1; + cmd[1] = 1; + } else { + die_select = 0; + cmd[1] = 0; + } + + if(deviceID == W25M02) { + //setup new LUT to issue Select Die command before issuing a page load + port->beginTransaction(SPICONFIG_NAND); + digitalWrite(pin, LOW); + port -> transfer(0xC2); + port -> transfer(die_select); + digitalWrite(pin, HIGH); + port->endTransaction(); + if(targetPage > pagesPerDie) + targetPage -= pagesPerDie; //W25M02 has 2 separate W25N01 dies addressed individually + cmd[1] = 0; //dummy block for write is 0. + wait(progtime); + } + + cmd[0] = 0x13; //Page Data Read + //cmd[1] defined above + cmd[2] = targetPage >> 8; + cmd[3] = targetPage; + + port->beginTransaction(SPICONFIG_NAND); + digitalWrite(pin, LOW); + port->transfer(cmd, 4); + digitalWrite(pin, HIGH); + port->endTransaction(); + +} + +uint8_t LittleFS_SPINAND::readECC(uint32_t targetPage, uint8_t *data, int length) +{ + + uint16_t column = LINEAR_TO_COLUMNECC(targetPage*eccSize); + targetPage = LINEAR_TO_PAGEECC(targetPage*eccSize); + + uint8_t cmd[4], die_select; + + if(targetPage > pagesPerDie) { + die_select = 1; + cmd[1] = 1; + } else { + die_select = 0; + cmd[1] = 0; + } + + if(deviceID == W25M02) { + //setup new LUT to issue Select Die command before issuing a page load + port->beginTransaction(SPICONFIG_NAND); + digitalWrite(pin, LOW); + port -> transfer(0xC2); + port -> transfer(die_select); + digitalWrite(pin, HIGH); + port->endTransaction(); + if(targetPage > pagesPerDie) + targetPage -= pagesPerDie; //W25M02 has 2 separate W25N01 dies addressed individually + cmd[1] = 0; //dummy block for write is 0. + } + + cmd[0] = 0x13; //Page Data Read + //cmd[1] defined above + cmd[2] = targetPage >> 8; + cmd[3] = targetPage; + + port->beginTransaction(SPICONFIG_NAND); + digitalWrite(pin, LOW); + port->transfer(cmd, 4); + digitalWrite(pin, HIGH); + port->endTransaction(); + + wait(progtime); + + uint8_t cmd1[4]; + cmd1[0] = 0x03; //0x03, READ Data + cmd1[1] = 0; + cmd1[2] = column >> 8; + cmd1[3] = column; + + port->beginTransaction(SPICONFIG_NAND); + digitalWrite(pin, LOW); + port->transfer(cmd1, 4); + port->transfer(data, length); + digitalWrite(pin, HIGH); + port->endTransaction(); + + wait(progtime); + + // Check ECC + uint8_t statReg = readStatusRegister(0xC0, false); + uint8_t eccCode = (((statReg) & ((1 << 5)|(1 << 4))) >> 4); + + switch (eccCode) { + case 0: // Successful read, no ECC correction + break; + case 1: // Successful read with ECC correction + case 2: // Uncorrectable ECC in a single page + case 3: // Uncorrectable ECC in multiple pages + //addError(address, eccCode); + //Serial.printf("ECC Error (addr, code): %x, %x\n", address, eccCode); + addBBLUT(LINEAR_TO_BLOCK(targetPage*eccSize)); + //deviceReset(); + break; + } + + //printtbuf(buf, 20); + return eccCode; +} + +void LittleFS_SPINAND::readBBLUT(uint16_t *LBA, uint16_t *PBA, uint8_t *linkStatus) +{ + //uint16_t LBA, PBA; + //uint16_t temp; + //uint16_t openEntries = 0; + //BBLUT_TABLE_ENTRY_COUNT 20 + //BBLUT_TABLE_ENTRY_SIZE 4 // in bytes + + uint8_t data[20 * 4]; + + port->beginTransaction(SPICONFIG_NAND); + digitalWrite(pin, LOW); + port->transfer(0xA5); //Read BBM_LUT 0xA5 + port->transfer(0); + for(uint32_t i = 0; i < (80); i++) { + data[i] = port->transfer(0); + } + digitalWrite(pin, HIGH); + port->endTransaction(); + + //See page 33 of the reference manual for W25N01G + //Serial.println("Status of the links"); + for(int i = 0, offset = 0 ; i < 20 ; i++, offset += 4) { + LBA[i] = data[offset+ 0] << 8 | data[offset+ 1]; + PBA[i] = data[offset+ 2] << 8 | data[offset+ 3]; + + if (LBA[i] == 0x0000) { + linkStatus[i] = 0; + //openEntries++; + } else { + //Serial.printf("\tEntry: %d: Logical BA - %d, Physical BA - %d\n", i, LBA[i], PBA[i]); + linkStatus[i] = (uint8_t) (LBA[i] >> 14); + //if(linkStatus[i] == 3) Serial.println("\t This link is enabled and its a Valid Link!"); + //if(linkStatus[i] == 4) Serial.println("\t This link was enabled but its not valid any more!"); + //if(linkStatus[i] == 1) Serial.println("\t Not Applicable!"); + } + + } + //Serial.printf("OpenEntries: %d\n", openEntries); +} + +uint8_t LittleFS_SPINAND::addBBLUT(uint32_t block_address) +{ + if(deviceID == W25N01) { + //check BBLUT FULL + uint8_t lutFull = 0; + lutFull = readStatusRegister(0xC0, false); + + if(lutFull & (1 << 6)) { + Serial.printf("Lut Full!!!!"); + exit(1); + } + + //Read BBLUT + uint16_t LBA[20], PBA[20], openEntries = 0; + uint8_t LUT_STATUS[20]; + uint8_t firstOpenEntry = 0; + + readBBLUT(LBA, PBA, LUT_STATUS); + + Serial.println("Status of the links"); + for(uint16_t i = 0; i < 20; i++){ + if(LUT_STATUS[i] > 0) { + Serial.printf("\tEntry: %d: Logical BA - %d, Physical BA - %d\n", i, LBA[i], PBA[i]); + LUT_STATUS[i] = (uint8_t) (LBA[i] >> 14); + if(LUT_STATUS[i] == 3) Serial.println("\t This link is enabled and its a Valid Link!"); + if(LUT_STATUS[i] == 4) Serial.println("\t This link was enabled but its not valid any more!"); + if(LUT_STATUS[i] == 1) Serial.println("\t Not Applicable!"); + } else { + openEntries++; + } + } Serial.printf("OpenEntries: %d\n", openEntries); + + + //Need to determine if the address is already in the list and what the first open entry is. + for(uint16_t i = 0; i < 20; i++){ + if(LUT_STATUS[i] > 0) { + if(LBA[i] == block_address) { + Serial.printf("Address: %d, already in BBLUT!\n", block_address); + return 0; + } + } + } + + firstOpenEntry = 20 - openEntries; + Serial.printf("First Open Entry: %d\n", firstOpenEntry); + + uint16_t pba, lba; + pba = block_address; + lba = LINEAR_TO_BLOCK((firstOpenEntry+1)*blocksize + chipsize); + Serial.printf("PBA: %d, LBA: %d\n", pba, lba); + + wait(progtime); + + } + return 0; +} + +void LittleFS_SPINAND::deviceReset() +{ + + port->beginTransaction(SPICONFIG_NAND); + digitalWrite(pin, LOW); + port->transfer(0xFF); + digitalWrite(pin, HIGH); + port->endTransaction(); + + wait(500000); + + // No protection, WP-E off, WP-E prevents use of IO2. PROT_REG(0xAO), PROT_CLEAR(0) + writeStatusRegister(0xA0, 0); + readStatusRegister(0xA0, false); + + // Buffered read mode (BUF = 1), ECC enabled (ECC = 1), 0xB0(0xB0), ECC_ENABLE((1 << 4)), ReadMode((1 << 3)) + writeStatusRegister(0xB0, (1 << 4) | (1 << 3)); + readStatusRegister(0xB0, false); + +} + +bool LittleFS_SPINAND::lowLevelFormat(char progressChar) +{ + uint32_t eraseAddr; + bool val; + val = LittleFS::lowLevelFormat(progressChar); + + for(uint16_t blocks = 0; blocks < reservedBBMBlocks; blocks++) { + eraseAddr = (config.block_count + blocks) * config.block_size; + eraseSector(eraseAddr); + } + + return val; +} + + +#if defined(__IMXRT1062__) + +#define LUT0(opcode, pads, operand) (FLEXSPI_LUT_INSTRUCTION((opcode), (pads), (operand))) +#define LUT1(opcode, pads, operand) (FLEXSPI_LUT_INSTRUCTION((opcode), (pads), (operand)) << 16) +#define CMD_SDR FLEXSPI_LUT_OPCODE_CMD_SDR +#define ADDR_SDR FLEXSPI_LUT_OPCODE_RADDR_SDR +#define CADDR_SDR FLEXSPI_LUT_OPCODE_CADDR_SDR +#define READ_SDR FLEXSPI_LUT_OPCODE_READ_SDR +#define WRITE_SDR FLEXSPI_LUT_OPCODE_WRITE_SDR +#define DUMMY_SDR FLEXSPI_LUT_OPCODE_DUMMY_SDR +#define PINS1 FLEXSPI_LUT_NUM_PADS_1 +#define PINS4 FLEXSPI_LUT_NUM_PADS_4 + +static const uint32_t flashBaseAddr = 0x00800000; + //static const uint32_t flashBaseAddr = 0x01000000u; + + +static void flexspi2_ip_command(uint32_t index, uint32_t addr) +{ + uint32_t n; + FLEXSPI2_IPCR0 = addr; + FLEXSPI2_IPCR1 = FLEXSPI_IPCR1_ISEQID(index); + FLEXSPI2_IPCMD = FLEXSPI_IPCMD_TRG; + while (!((n = FLEXSPI2_INTR) & FLEXSPI_INTR_IPCMDDONE)); // wait + if (n & FLEXSPI_INTR_IPCMDERR) { + FLEXSPI2_INTR = FLEXSPI_INTR_IPCMDERR; + //Serial.printf("Error: FLEXSPI2_IPRXFSTS=%08lX\n", FLEXSPI2_IPRXFSTS); + } + FLEXSPI2_INTR = FLEXSPI_INTR_IPCMDDONE; +} + +static void flexspi2_ip_read(uint32_t index, uint32_t addr, void *data, uint32_t length) +{ + uint8_t *p = (uint8_t *)data; + + FLEXSPI2_INTR = FLEXSPI_INTR_IPRXWA; + // Clear RX FIFO and set watermark to 16 bytes + FLEXSPI2_IPRXFCR = FLEXSPI_IPRXFCR_CLRIPRXF | FLEXSPI_IPRXFCR_RXWMRK(1); + FLEXSPI2_IPCR0 = addr; + FLEXSPI2_IPCR1 = FLEXSPI_IPCR1_ISEQID(index) | FLEXSPI_IPCR1_IDATSZ(length); + FLEXSPI2_IPCMD = FLEXSPI_IPCMD_TRG; +// page 1649 : Reading Data from IP RX FIFO +// page 1706 : Interrupt Register (INTR) +// page 1723 : IP RX FIFO Control Register (IPRXFCR) +// page 1732 : IP RX FIFO Status Register (IPRXFSTS) + + while (1) { + if (length >= 16) { + if (FLEXSPI2_INTR & FLEXSPI_INTR_IPRXWA) { + volatile uint32_t *fifo = &FLEXSPI2_RFDR0; + uint32_t a = *fifo++; + uint32_t b = *fifo++; + uint32_t c = *fifo++; + uint32_t d = *fifo++; + *(uint32_t *)(p+0) = a; + *(uint32_t *)(p+4) = b; + *(uint32_t *)(p+8) = c; + *(uint32_t *)(p+12) = d; + p += 16; + length -= 16; + FLEXSPI2_INTR = FLEXSPI_INTR_IPRXWA; + } + } else if (length > 0) { + if ((FLEXSPI2_IPRXFSTS & 0xFF) >= ((length + 7) >> 3)) { + volatile uint32_t *fifo = &FLEXSPI2_RFDR0; + while (length >= 4) { + *(uint32_t *)(p) = *fifo++; + p += 4; + length -= 4; + } + uint32_t a = *fifo; + if (length >= 1) { + *p++ = a & 0xFF; + a = a >> 8; + } + if (length >= 2) { + *p++ = a & 0xFF; + a = a >> 8; + } + if (length >= 3) { + *p++ = a & 0xFF; + a = a >> 8; + } + length = 0; + } + } else { + if (FLEXSPI2_INTR & FLEXSPI_INTR_IPCMDDONE) break; + } + // TODO: timeout... + } + if (FLEXSPI2_INTR & FLEXSPI_INTR_IPCMDERR) { + FLEXSPI2_INTR = FLEXSPI_INTR_IPCMDERR; + //Serial.printf("Error: FLEXSPI2_IPRXFSTS=%08lX\r\n", FLEXSPI2_IPRXFSTS); + } + FLEXSPI2_INTR = FLEXSPI_INTR_IPCMDDONE; +} + +static void flexspi2_ip_write(uint32_t index, uint32_t addr, const void *data, uint32_t length) +{ + const uint8_t *src; + uint32_t n, wrlen; + + FLEXSPI2_IPCR0 = addr; + FLEXSPI2_IPCR1 = FLEXSPI_IPCR1_ISEQID(index) | FLEXSPI_IPCR1_IDATSZ(length); + src = (const uint8_t *)data; + FLEXSPI2_IPCMD = FLEXSPI_IPCMD_TRG; + while (!((n = FLEXSPI2_INTR) & FLEXSPI_INTR_IPCMDDONE)) { + if (n & FLEXSPI_INTR_IPTXWE) { + wrlen = length; + if (wrlen > 8) wrlen = 8; + if (wrlen > 0) { + //Serial.print("%"); + memcpy((void *)&FLEXSPI2_TFDR0, src, wrlen); + src += wrlen; + length -= wrlen; + FLEXSPI2_INTR = FLEXSPI_INTR_IPTXWE; + } + } + } + if (n & FLEXSPI_INTR_IPCMDERR) { + FLEXSPI2_INTR = FLEXSPI_INTR_IPCMDERR; + //Serial.printf("Error: FLEXSPI2_IPRXFSTS=%08lX\r\n", FLEXSPI2_IPRXFSTS); + } + FLEXSPI2_INTR = FLEXSPI_INTR_IPCMDDONE; +} + + + +bool LittleFS_QPINAND::begin() { + + //Serial.println("QSPI flash begin"); + + configured = false; + + uint8_t buf[5] = {0, 0, 0, 0, 0}; + + //if following is uncommented NANDs wont work? + //FLEXSPI2_FLSHA2CR1 = FLEXSPI_FLSHCR1_CSINTERVAL(2) //minimum interval between flash device Chip selection deassertion and flash device Chip selection assertion. + // | FLEXSPI_FLSHCR1_CAS(11) //sets up 14 bit column address + // | FLEXSPI_FLSHCR1_TCSH(3) //Serial Flash CS Hold time. + // | FLEXSPI_FLSHCR1_TCSS(3); //Serial Flash CS setup time + + //Reset clock to 102.85714 Mhz +/* CCM_CCGR7 |= CCM_CCGR7_FLEXSPI2(CCM_CCGR_OFF); + CCM_CBCMR = (CCM_CBCMR & ~(CCM_CBCMR_FLEXSPI2_PODF_MASK | CCM_CBCMR_FLEXSPI2_CLK_SEL_MASK)) + | CCM_CBCMR_FLEXSPI2_PODF(6) | CCM_CBCMR_FLEXSPI2_CLK_SEL(1); + CCM_CCGR7 |= CCM_CCGR7_FLEXSPI2(CCM_CCGR_ON); +*/ + FLEXSPI2_LUTKEY = FLEXSPI_LUTKEY_VALUE; + FLEXSPI2_LUTCR = FLEXSPI_LUTCR_UNLOCK; + + // cmd index 8 = read ID bytes + FLEXSPI2_LUT32 = LUT0(CMD_SDR, PINS1, 0x9F) | LUT1(READ_SDR, PINS1, 1); + FLEXSPI2_LUT33 = 0; + flexspi2_ip_read(8, 0x00800000, buf, 4); + + //Serial.printf("Flash ID: %02X %02X %02X\n", buf[1], buf[2], buf[3]); + const struct chipinfo *info = chip_lookup(buf+1); + if (!info) return false; + //Serial.printf("Flash size is %.2f Mbyte\n", (float)info->chipsize / 1048576.0f); + + // configure FlexSPI2 for chip's size + FLEXSPI2_FLSHA2CR0 = info->chipsize / 1024; + + //capacityID = buf[3]; //W25N01G has 1 die, W25N02G had 2 dies + deviceID = (buf[1] << 16) | (buf[2] << 8) | (buf[3]); + //Serial.printf("Device ID: 0x%6X\n", deviceID); + + if(deviceID == W25N01) { + die = 1; + eccSize = 64; + PAGE_ECCSIZE = 2112; + } else if(deviceID == W25N02) { + die = 1; + eccSize = 128; + PAGE_ECCSIZE = 2176; + } else if(deviceID == W25M02) { + dies = 2; + eccSize = 64; + PAGE_ECCSIZE = 2112; + } + + memset(&lfs, 0, sizeof(lfs)); + memset(&config, 0, sizeof(config)); + config.context = (void *)this; + config.read = &static_read; + config.prog = &static_prog; + config.erase = &static_erase; + config.sync = &static_sync; + config.read_size = info->progsize; + config.prog_size = info->progsize; + config.block_size = info->erasesize; + config.block_count = info->chipsize / info->erasesize; + config.block_cycles = 400; + config.cache_size = info->progsize; + config.lookahead_size = info->progsize; + config.name_max = LFS_NAME_MAX; + addrbits = info->addrbits; + progtime = info->progtime; + erasetime = info->erasetime; + configured = true; + blocksize = info->erasesize; + chipsize = info->chipsize; + + // cmd index 8 = read Status register + // set in function readStatusRegister(uint8_t reg, bool dump) + // cmd index 8 = write Status register + // see function writeStatusRegister(uint8_t reg, uint8_t data) + + //cmd index 9 - WG reset, see function deviceReset() + FLEXSPI2_LUT36 = LUT0(CMD_SDR, PINS1, 0xFF); //0xFF Device Reset + + //cmd index 10 - read BBLUT + // cmd 11 index write enable cmd + // see function writeEnable() + //cmd 12 Command based on PageAddress + // see functions: + // eraseSector(uint32_t addr) and + // readBytes(uint32_t address, uint8_t *data, int length) + //cmd 13 program load Data + // see functions: + // programDataLoad(uint16_t columnAddress, const uint8_t *data, int length) + // and + // randomProgramDataLoad(uint16_t columnAddress, const uint8_t *data, int length) + + + //cmd 14 program read Data -- reserved. 0xEB FAST_READ_QUAD_IO + FLEXSPI2_LUT56 = LUT0(CMD_SDR, PINS1, 0xEB) | LUT1(CADDR_SDR, PINS4, 0x10); + FLEXSPI2_LUT57 = LUT0(DUMMY_SDR, PINS4, 4) | LUT1(READ_SDR, PINS4, 1); + + //cmd 15 - program execute - 0x10 + FLEXSPI2_LUT60 = LUT0(CMD_SDR, PINS1, 0x10) | LUT1(ADDR_SDR, PINS1, 0x18); + + + // No protection, WP-E off, WP-E prevents use of IO2. PROT_REG(0xAO), PROT_CLEAR(0) + writeStatusRegister(0xA0, 0); + readStatusRegister(0xA0, false); + + // Buffered read mode (BUF = 1), ECC enabled (ECC = 1), 0xB0(0xB0), ECC_ENABLE((1 << 4)), ReadMode((1 << 3)) + //writeStatusRegister(0xB0, (1 << 3)); + writeStatusRegister(0xB0, (1 << 4) | (1 << 3)); + readStatusRegister(0xB0, false); + + //Serial.println("attempting to mount existing media"); + if (lfs_mount(&lfs, &config) < 0) { + Serial.println("couldn't mount media, attemping to format"); + if (lfs_format(&lfs, &config) < 0) { + Serial.println("format failed :("); + return false; + } + Serial.println("attempting to mount freshly formatted media"); + if (lfs_mount(&lfs, &config) < 0) { + Serial.println("mount after format failed :("); + return false; + } + } + mounted = true; + //Serial.println("success"); + return true; + +} + +int LittleFS_QPINAND::read(lfs_block_t block, lfs_off_t offset, void *buf, lfs_size_t size) +{ + const uint32_t address = block * config.block_size + offset; + uint32_t newTargetPage; + uint32_t targetPage = LINEAR_TO_PAGE(address); + uint8_t val; + + //Page Data Read - 0x13 + FLEXSPI2_LUT48 = LUT0(CMD_SDR, PINS1, 0x13) | LUT1(ADDR_SDR, PINS1, 0x18); + + if(currentPageRead != targetPage){ + //need to create LUT for W25M02 Die Select command, + if(deviceID == W25M02) { + if(targetPage >= pagesPerDie ) { + targetPage -= pagesPerDie; + val = 1; + } else { + val = 0; + } + newTargetPage = targetPage; + + // die select 0xc2 + FLEXSPI2_LUT44 = LUT0(CMD_SDR, PINS1, 0xC2) | LUT1(WRITE_SDR, PINS1, 1); + flexspi2_ip_write(11, 0x00800000, &val, 1); + } else { + if(targetPage > pagesPerDie ) { + //targetPage -= sectorSize; + newTargetPage = (1 << 16) | ((uint8_t)targetPage >> 8) | ((uint8_t) targetPage); + } else { + newTargetPage = targetPage; + } + } + + + flexspi2_ip_command(12, 0x00800000 + newTargetPage); // Page data read Lut + wait(progtime); + + currentPageRead = targetPage; + } + + uint16_t column = LINEAR_TO_COLUMN(address); + flexspi2_ip_read(14, 0x00800000 + column, buf, size); + + + + //printtbuf(buf, 20); + + // Check ECC + uint8_t statReg = readStatusRegister(0xC0, false); + uint8_t eccCode = (((statReg) & ((1 << 5)|(1 << 4))) >> 4); + + + switch (eccCode) { + case 0: // Successful read, no ECC correction + break; + case 1: // Successful read with ECC correction + //Serial.printf("Successful read with ECC correction (addr, code): %x, %x\n", addr, eccCode); + case 2: // Uncorrectable ECC in a single page + //Serial.printf("Uncorrectable ECC in a single page (addr, code): %x, %x\n", address, eccCode); + case 3: // Uncorrectable ECC in multiple pages + //Serial.printf("Uncorrectable ECC in a single page (addr, code): %x, %x\n", address, eccCode); + addBBLUT(LINEAR_TO_BLOCK(address)); + //deviceReset(); + break; + + } + + //Serial.print("Read: "); printtbuf(buf, 40); + return 0; +} + +int LittleFS_QPINAND::prog(lfs_block_t block, lfs_off_t offset, const void *buf, lfs_size_t size) +{ + const uint32_t address = block * config.block_size + offset; + uint32_t newTargetPage; + uint8_t val; + + uint32_t pageAddress = LINEAR_TO_PAGE(address); + uint16_t columnAddress = LINEAR_TO_COLUMN(address); + + + //need to create LUT for W25M02 Die Select command, + if(deviceID == W25M02) { + if(pageAddress > pagesPerDie ) { + pageAddress -= pagesPerDie; + val = 1; + } else { + val = 0; + } + newTargetPage = pageAddress; + + // die select 0xc2 + FLEXSPI2_LUT44 = LUT0(CMD_SDR, PINS1, 0xC2) | LUT1(WRITE_SDR, PINS1, 1); + flexspi2_ip_write(11, 0x00800000, &val, 1); + wait(progtime); + } else { + if(pageAddress > pagesPerDie ) { + //targetPage -= sectorSize; + newTargetPage = (1 << 16) | ((uint8_t)pageAddress >> 8) | ((uint8_t) pageAddress); + } else { + newTargetPage = pageAddress; + } + } + + writeEnable(); //sets the WEL in Status Reg to 1 (bit 2) + + //Program Data Load - 0x32 + FLEXSPI2_LUT52 = LUT0(CMD_SDR, PINS1, 0x32) | LUT1(CADDR_SDR, PINS1, 0x10); + FLEXSPI2_LUT53 = LUT0(WRITE_SDR, PINS4, 1); + flexspi2_ip_write(13, 0x00800000 + columnAddress, buf, size); + wait(progtime); + + //uint8_t status = readStatusRegister(0xC0, false ); //Status Register + //if ((status & (1 << 3)) == 1) //Status Program Fail + // Serial.println( "Programed Status: FAILED" ); + + + //Serial.printf("PE pageAddress: %d\n", pageAddress); + //cmd 15 - program execute - 0x10 + flexspi2_ip_command(15, 0x00800000 + newTargetPage); + + return wait(progtime); +} + +int LittleFS_QPINAND::erase(lfs_block_t block) +{ + const uint32_t addr = block * config.block_size; + + void *buffer = malloc(config.read_size); + if ( buffer != nullptr) { + if ( blockIsBlank(&config, block, buffer)) { + free(buffer); + return 0; // Already formatted exit no wait + } + free(buffer); + } + + eraseSector(addr); + wait(erasetime); + return 0; +} + + + +uint8_t LittleFS_QPINAND::readStatusRegister(uint16_t reg, bool dump) +{ + uint8_t val; + + // cmd index 8 = read Status register #1 SPI, 0x05 + FLEXSPI2_LUT32 = LUT0(CMD_SDR, PINS1, 0x05) | LUT1(CMD_SDR, PINS1, reg); + FLEXSPI2_LUT33 = LUT0(READ_SDR, PINS1, 1); + + flexspi2_ip_read(8, 0x00800000, &val, 1 ); + + if (dump) { + //Serial.printf("Status of reg 0x%x: \n", reg); + //Serial.printf("(HEX: ) 0x%02X, (Binary: )", val); + //Serial.println(val, BIN); + //Serial.println(); + } + + return val; + +} + +void LittleFS_QPINAND::writeStatusRegister(uint8_t reg, uint8_t data) +{ + uint8_t buf[1]; + buf[0] = data; + // cmd index 8 = write Status register, 0x01 + FLEXSPI2_LUT32 = LUT0(CMD_SDR, PINS1, 0x01) | LUT1(CMD_SDR, PINS1, reg); + FLEXSPI2_LUT33 = LUT0(WRITE_SDR, PINS1, 1); + + flexspi2_ip_write(8, 0x00800000, buf, 1); + +} + + +bool LittleFS_QPINAND::isReady() +{ + uint8_t status = readStatusRegister(0xC0, false); + return ((status & (1 << 0)) == 0); +} + +int LittleFS_QPINAND::wait(uint32_t microseconds) +{ + elapsedMicros usec = 0; + while (1) { + if (isReady()) break; + if (usec > microseconds) return LFS_ERR_IO; // timeout + yield(); + } + //Serial.printf(" waited %u us\n", (unsigned int)usec); + return 0; // success +} + + +/** + * The flash requires this write enable command to be sent before commands that would cause + * a write like program and erase. + */ +bool LittleFS_QPINAND::writeEnable() +{ + uint8_t status; + FLEXSPI2_LUT44 = LUT0(CMD_SDR, PINS1, 0x06); //Write enable 0x06 + flexspi2_ip_command(11, 0x00800000); //Write Enable + // Assume that we're about to do some writing, so the device is just about to become busy + wait(progtime); + + status = readStatusRegister(0xC0, false); + return status & (0x02); +} + + +/** + * Erase a sector full of bytes to all 1's at the given byte offset in the flash chip. + */ +void LittleFS_QPINAND::eraseSector(uint32_t address) +{ + + + + uint32_t pageAddr = LINEAR_TO_PAGE(address) ; + //if(pageAddr > sectorSize) pageAddr -= sectorSize; + + uint32_t newTargetPage; + uint8_t val; + + //need to create LUT for W25M02 Die Select command, + if(deviceID == W25M02) { + if(pageAddr > pagesPerDie ) { + pageAddr -= pagesPerDie; + val = 1; + } else { + val = 0; + } + newTargetPage = pageAddr; + + // die select 0xc2 + FLEXSPI2_LUT44 = LUT0(CMD_SDR, PINS1, 0xC2) | LUT1(WRITE_SDR, PINS1, 1); + flexspi2_ip_write(11, 0x00800000, &val, 1); + wait(progtime); + } else { + if(pageAddr > pagesPerDie ) { + //targetPage -= sectorSize; + newTargetPage = (1 << 16) | ((uint8_t)pageAddr >> 8) | ((uint8_t) pageAddr); + } else { + newTargetPage = pageAddr; + } + } + + writeEnable(); //sets the WEL in Status Reg to 1 (bit 2) + // cmd index 12, Block Erase 0xD8 + FLEXSPI2_LUT48 = LUT0(CMD_SDR, PINS1, 0xD8) | LUT1(ADDR_SDR, PINS1, 0x18); + flexspi2_ip_command(12, 0x00800000 + newTargetPage); + + //uint8_t status = readStatusRegister(0xC0, false ); + //if ((status & (1 << 2)) == 1) //Status erase fail + // Serial.println( "erase Status: FAILED "); + +} + + +uint8_t LittleFS_QPINAND::readECC(uint32_t targetPage, uint8_t *buf, int size) +{ + + uint16_t column = LINEAR_TO_COLUMNECC(targetPage*eccSize); + targetPage = LINEAR_TO_PAGEECC(targetPage*eccSize); + + uint32_t newTargetPage; + uint8_t val; + + //Page Data Read - 0x13 + FLEXSPI2_LUT48 = LUT0(CMD_SDR, PINS1, 0x13) | LUT1(ADDR_SDR, PINS1, 0x18); + + //need to create LUT for W25M02 Die Select command, + if(deviceID == W25M02) { + if(targetPage >= pagesPerDie ) { + targetPage -= pagesPerDie; + val = 1; + } else { + val = 0; + } + newTargetPage = targetPage; + + // die select 0xc2 + FLEXSPI2_LUT44 = LUT0(CMD_SDR, PINS1, 0xC2) | LUT1(WRITE_SDR, PINS1, 1); + flexspi2_ip_write(11, 0x00800000, &val, 1); + } else { + if(targetPage > pagesPerDie ) { + //targetPage -= sectorSize; + newTargetPage = (1 << 16) | ((uint8_t)targetPage >> 8) | ((uint8_t) targetPage); + } else { + newTargetPage = targetPage; + } + } + + + flexspi2_ip_command(12, 0x00800000 + newTargetPage); // Page data read Lut + wait(progtime); + + currentPageRead = targetPage; + + flexspi2_ip_read(14, 0x00800000 + column, buf, size); + + // Check ECC + uint8_t statReg = readStatusRegister(0xC0, false); + uint8_t eccCode = (((statReg) & ((1 << 5)|(1 << 4))) >> 4); + + switch (eccCode) { + case 0: // Successful read, no ECC correction + break; + case 1: // Successful read with ECC correction + case 2: // Uncorrectable ECC in a single page + case 3: // Uncorrectable ECC in multiple pages + //addError(address, eccCode); + //Serial.printf("ECC Error (addr, code): %x, %x\n", address, eccCode); + addBBLUT(LINEAR_TO_BLOCK(targetPage*eccSize)); + //deviceReset(); + break; + } + + + return eccCode; +} + +void LittleFS_QPINAND::readBBLUT(uint16_t *LBA, uint16_t *PBA, uint8_t *linkStatus) +{ + //uint16_t LBA, PBA; + //uint16_t linkStatus[20]; + //uint16_t openEntries = 0; + //BBLUT_TABLE_ENTRY_COUNT 20 + //BBLUT_TABLE_ENTRY_SIZE 4 // in bytes + + uint8_t data[20 * 4]; + + FLEXSPI2_LUT40 = LUT0(CMD_SDR, PINS1, 0xA5) | LUT1(DUMMY_SDR, 8, 1); //Read BBM_LUT 0xA5 + FLEXSPI2_LUT41 = LUT0(READ_SDR, PINS1, 1); + flexspi2_ip_read(10, 0x00800000, data, sizeof(data)); + + + //See page 33 of the reference manual for W25N01G + //Serial.println("Status of the links"); + for(int i = 0, offset = 0 ; i < 20 ; i++, offset += 4) { + LBA[i] = data[offset+ 0] << 8 | data[offset+ 1]; + PBA[i] = data[offset+ 2] << 8 | data[offset+ 3]; + + if (LBA[i] == 0x0000) { + linkStatus[i] = 0; + //openEntries++; + } else { + //Serial.printf("\tEntry: %d: Logical BA - %d, Physical BA - %d\n", i, LBA[i], PBA[i]); + linkStatus[i] = (uint8_t) (LBA[i] >> 14); + //if(linkStatus[i] == 3) Serial.println("\t This link is enabled and its a Valid Link!"); + //if(linkStatus[i] == 4) Serial.println("\t This link was enabled but its not valid any more!"); + //if(linkStatus[i] == 1) Serial.println("\t Not Applicable!"); + } + + } + //Serial.printf("OpenEntries: %d\n", openEntries); +} + +uint8_t LittleFS_QPINAND::addBBLUT(uint32_t block_address) +{ + if(deviceID == W25N01) { + //check BBLUT FULL + uint8_t lutFull = 0; + lutFull = readStatusRegister(0xC0, false); + + if(lutFull & (1 << 6)) { + Serial.printf("Lut Full!!!!"); + exit(1); + } + + //Read BBLUT + uint16_t LBA[20], PBA[20], openEntries = 0; + uint8_t LUT_STATUS[20]; + uint8_t firstOpenEntry = 0; + + readBBLUT(LBA, PBA, LUT_STATUS); + + Serial.println("Status of the links"); + for(uint16_t i = 0; i < 20; i++){ + if(LUT_STATUS[i] > 0) { + Serial.printf("\tEntry: %d: Logical BA - %d, Physical BA - %d\n", i, LBA[i], PBA[i]); + LUT_STATUS[i] = (uint8_t) (LBA[i] >> 14); + if(LUT_STATUS[i] == 3) Serial.println("\t This link is enabled and its a Valid Link!"); + if(LUT_STATUS[i] == 4) Serial.println("\t This link was enabled but its not valid any more!"); + if(LUT_STATUS[i] == 1) Serial.println("\t Not Applicable!"); + } else { + openEntries++; + } + } Serial.printf("OpenEntries: %d\n", openEntries); + + + //Need to determine if the address is already in the list and what the first open entry is. + for(uint16_t i = 0; i < 20; i++){ + if(LUT_STATUS[i] > 0) { + if(LBA[i] == block_address) { + Serial.printf("Address: %d, already in BBLUT!\n", block_address); + return 0; + } + } + } + + firstOpenEntry = 20 - openEntries; + Serial.printf("First Open Entry: %d\n", firstOpenEntry); + + //Write BBLUT with next sequential block + uint16_t pba, lba; + pba = block_address; + //lba = LINEAR_TO_BLOCK((firstOpenEntry+1)*config.block_size); + lba = LINEAR_TO_BLOCK((firstOpenEntry+1)*blocksize + chipsize); + Serial.printf("PBA: %d, LBA: %d\n", pba, lba); + + wait(progtime); + } + return 0; +} + +void LittleFS_QPINAND::deviceReset() +{ + //cmd index 9 - WG reset, see function deviceReset() + FLEXSPI2_LUT36 = LUT0(CMD_SDR, PINS1, 0xFF); + flexspi2_ip_command(9, 0x00800000); //reset + + wait(500000); + + // No protection, WP-E off, WP-E prevents use of IO2. PROT_REG(0xAO), PROT_CLEAR(0) + writeStatusRegister(0xA0, 0); + readStatusRegister(0xA0, false); + + // Buffered read mode (BUF = 1), ECC enabled (ECC = 1), 0xB0(0xB0), ECC_ENABLE((1 << 4)), ReadMode((1 << 3)) + writeStatusRegister(0xB0, (1 << 4) | (1 << 3)); + readStatusRegister(0xB0, false); + +} + +bool LittleFS_QPINAND::lowLevelFormat(char progressChar) +{ + uint32_t eraseAddr; + bool val; + val = LittleFS::lowLevelFormat(progressChar); + + for(uint16_t blocks = 0; blocks < reservedBBMBlocks; blocks++) { + eraseAddr = (config.block_count + blocks) * config.block_size; + eraseSector(eraseAddr); + } + + return val; +} + +#endif // __IMXRT1062__ \ No newline at end of file diff --git a/Firmware_V3/lib/LittleFS/LittleFS_NAND.h b/Firmware_V3/lib/LittleFS/LittleFS_NAND.h new file mode 100644 index 0000000..12d6e16 --- /dev/null +++ b/Firmware_V3/lib/LittleFS/LittleFS_NAND.h @@ -0,0 +1,202 @@ +/* LittleFS for Teensy + * Copyright (c) 2020, Paul Stoffregen, paul@pjrc.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice, development funding notice, and this permission + * notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include +#include "littlefs/lfs.h" + +#include +#include +#include + +// Bits in LBA for BB LUT +#define BBLUT_STATUS_ENABLED (1 << 15) +#define BBLUT_STATUS_INVALID (1 << 14) +#define BBLUT_STATUS_MASK (BBLUT_STATUS_ENABLED | BBLUT_STATUS_INVALID) + +////////////////////////////////////////////////////// +// Some useful defs and macros +#define LINEAR_TO_COLUMNECC(laddr) ((laddr) % PAGE_ECCSIZE) +#define LINEAR_TO_COLUMN(laddr) ((laddr) % pageSize) +#define LINEAR_TO_PAGE(laddr) ((laddr) / pageSize) +#define LINEAR_TO_PAGEECC(laddr) ((laddr) / PAGE_ECCSIZE) +#define LINEAR_TO_BLOCK(laddr) (LINEAR_TO_PAGE(laddr) / PAGES_PER_BLOCK) +#define BLOCK_TO_PAGE(block) ((block) * PAGES_PER_BLOCK) +#define BLOCK_TO_LINEAR(block) (BLOCK_TO_PAGE(block) * pageSize) + +////////////////////////////////////////////////////// +//Chip ID +#define W25N01 0xEFAA21 +#define W25N02 0xEFAA22 +#define W25M02 0xEFBB21 + + +//Geometry +#define sectors_w25n0x 1024 +#define pagesPerSector_w25n0x 64 +#define pageSize 2048 +#define sectorSize pagesPerSector_w25n0x * pageSize +#define totalSize_w25n0x sectorSize * sectors_w25n0x +#define pagesPerDie 65534 + +// Device size parameters +#define PAGE_SIZE 2048 +#define PAGES_PER_BLOCK 64 +#define BLOCKS_PER_DIE 1024 + +#define reservedBBMBlocks 24 + + +class LittleFS_SPINAND : public LittleFS +{ +public: + LittleFS_SPINAND() { + port = nullptr; + } + bool begin(uint8_t cspin, SPIClass &spiport=SPI); + uint8_t readECC(uint32_t address, uint8_t *data, int length); + void readBBLUT(uint16_t *LBA, uint16_t *PBA, uint8_t *linkStatus); + bool lowLevelFormat(char progressChar); + uint8_t addBBLUT(uint32_t block_address); //temporary for testing + +private: + int read(lfs_block_t block, lfs_off_t offset, void *buf, lfs_size_t size); + int prog(lfs_block_t block, lfs_off_t offset, const void *buf, lfs_size_t size); + int erase(lfs_block_t block); + int wait(uint32_t microseconds); + static int static_read(const struct lfs_config *c, lfs_block_t block, + lfs_off_t offset, void *buffer, lfs_size_t size) { + //Serial.printf(" flash rd: block=%d, offset=%d, size=%d\n", block, offset, size); + return ((LittleFS_SPINAND *)(c->context))->read(block, offset, buffer, size); + } + static int static_prog(const struct lfs_config *c, lfs_block_t block, + lfs_off_t offset, const void *buffer, lfs_size_t size) { + //Serial.printf(" flash wr: block=%d, offset=%d, size=%d\n", block, offset, size); + return ((LittleFS_SPINAND *)(c->context))->prog(block, offset, buffer, size); + } + static int static_erase(const struct lfs_config *c, lfs_block_t block) { + //Serial.printf(" flash er: block=%d\n", block); + return ((LittleFS_SPINAND *)(c->context))->erase(block); + } + static int static_sync(const struct lfs_config *c) { + return 0; + } + bool isReady(); + bool writeEnable(); + void eraseSector(uint32_t address); + void writeStatusRegister(uint8_t reg, uint8_t data); + uint8_t readStatusRegister(uint16_t reg, bool dump); + void loadPage(uint32_t address); + + void deviceReset(); + + SPIClass *port; + uint8_t pin; + uint8_t addrbits; + uint32_t progtime; + uint32_t erasetime; + uint32_t chipsize; + uint32_t blocksize; + +private: + uint8_t die = 0; //die = 0: use first 1GB die PA[16], die = 1: use second 1GB die PA[16]. + uint8_t dies = 0; //used for W25M02 + uint32_t capacityID ; // capacity + uint32_t deviceID; + + uint16_t eccSize = 64; + uint16_t PAGE_ECCSIZE = 2112; + +}; + + +#if defined(__IMXRT1062__) +class LittleFS_QPINAND : public LittleFS +{ +public: + LittleFS_QPINAND() { } + bool begin(); + bool deviceErase(); + uint8_t readECC(uint32_t targetPage, uint8_t *buf, int size); + void readBBLUT(uint16_t *LBA, uint16_t *PBA, uint8_t *linkStatus); + bool lowLevelFormat(char progressChar); + uint8_t addBBLUT(uint32_t block_address); //temporary for testing + +private: + int read(lfs_block_t block, lfs_off_t offset, void *buf, lfs_size_t size); + int prog(lfs_block_t block, lfs_off_t offset, const void *buf, lfs_size_t size); + int erase(lfs_block_t block); + int wait(uint32_t microseconds); + static int static_read(const struct lfs_config *c, lfs_block_t block, + lfs_off_t offset, void *buffer, lfs_size_t size) { + //Serial.printf("..... flash rd: block=%d, offset=%d, size=%d\n", block, offset, size); + return ((LittleFS_QPINAND *)(c->context))->read(block, offset, buffer, size); + } + static int static_prog(const struct lfs_config *c, lfs_block_t block, + lfs_off_t offset, const void *buffer, lfs_size_t size) { + //Serial.printf("..... flash wr: block=%d, offset=%d, size=%d\n", block, offset, size); + return ((LittleFS_QPINAND *)(c->context))->prog(block, offset, buffer, size); + } + static int static_erase(const struct lfs_config *c, lfs_block_t block) { + //Serial.printf("..... flash er: block=%d\n", block); + return ((LittleFS_QPINAND *)(c->context))->erase(block); + } + static int static_sync(const struct lfs_config *c) { + return 0; + } + bool isReady(); + bool writeEnable(); + void deviceReset(); + void eraseSector(uint32_t address); + void writeStatusRegister(uint8_t reg, uint8_t data); + uint8_t readStatusRegister(uint16_t reg, bool dump); + + uint8_t addrbits; + uint32_t progtime; + uint32_t erasetime; + uint32_t chipsize; + uint32_t blocksize; + +private: + uint8_t die = 0; //die = 0: use first 1GB die, die = 1: use second 1GB die. + uint8_t dies; + uint32_t capacityID ; // capacity + uint32_t deviceID; + + uint16_t eccSize = 64; + uint16_t PAGE_ECCSIZE = 2112; + +}; + +#endif + + + + + + + + + + diff --git a/Firmware_V3/lib/LittleFS/littlefs/DESIGN.md b/Firmware_V3/lib/LittleFS/littlefs/DESIGN.md new file mode 100644 index 0000000..1d02ba3 --- /dev/null +++ b/Firmware_V3/lib/LittleFS/littlefs/DESIGN.md @@ -0,0 +1,2173 @@ +## The design of littlefs + +A little fail-safe filesystem designed for microcontrollers. + +``` + | | | .---._____ + .-----. | | +--|o |---| littlefs | +--| |---| | + '-----' '----------' + | | | +``` + +littlefs was originally built as an experiment to learn about filesystem design +in the context of microcontrollers. The question was: How would you build a +filesystem that is resilient to power-loss and flash wear without using +unbounded memory? + +This document covers the high-level design of littlefs, how it is different +than other filesystems, and the design decisions that got us here. For the +low-level details covering every bit on disk, check out [SPEC.md](SPEC.md). + +## The problem + +The embedded systems littlefs targets are usually 32-bit microcontrollers with +around 32 KiB of RAM and 512 KiB of ROM. These are often paired with SPI NOR +flash chips with about 4 MiB of flash storage. These devices are too small for +Linux and most existing filesystems, requiring code written specifically with +size in mind. + +Flash itself is an interesting piece of technology with its own quirks and +nuance. Unlike other forms of storage, writing to flash requires two +operations: erasing and programming. Programming (setting bits to 0) is +relatively cheap and can be very granular. Erasing however (setting bits to 1), +requires an expensive and destructive operation which gives flash its name. +[Wikipedia][wikipedia-flash] has more information on how exactly flash works. + +To make the situation more annoying, it's very common for these embedded +systems to lose power at any time. Usually, microcontroller code is simple and +reactive, with no concept of a shutdown routine. This presents a big challenge +for persistent storage, where an unlucky power loss can corrupt the storage and +leave a device unrecoverable. + +This leaves us with three major requirements for an embedded filesystem. + +1. **Power-loss resilience** - On these systems, power can be lost at any time. + If a power loss corrupts any persistent data structures, this can cause the + device to become unrecoverable. An embedded filesystem must be designed to + recover from a power loss during any write operation. + +1. **Wear leveling** - Writing to flash is destructive. If a filesystem + repeatedly writes to the same block, eventually that block will wear out. + Filesystems that don't take wear into account can easily burn through blocks + used to store frequently updated metadata and cause a device's early death. + +1. **Bounded RAM/ROM** - If the above requirements weren't enough, these + systems also have very limited amounts of memory. This prevents many + existing filesystem designs, which can lean on relatively large amounts of + RAM to temporarily store filesystem metadata. + + For ROM, this means we need to keep our design simple and reuse code paths + were possible. For RAM we have a stronger requirement, all RAM usage is + bounded. This means RAM usage does not grow as the filesystem changes in + size or number of files. This creates a unique challenge as even presumably + simple operations, such as traversing the filesystem, become surprisingly + difficult. + +## Existing designs? + +So, what's already out there? There are, of course, many different filesystems, +however they often share and borrow feature from each other. If we look at +power-loss resilience and wear leveling, we can narrow these down to a handful +of designs. + +1. First we have the non-resilient, block based filesystems, such as [FAT] and + [ext2]. These are the earliest filesystem designs and often the most simple. + Here storage is divided into blocks, with each file being stored in a + collection of blocks. Without modifications, these filesystems are not + power-loss resilient, so updating a file is a simple as rewriting the blocks + in place. + + ``` + .--------. + | root | + | | + | | + '--------' + .-' '-. + v v + .--------. .--------. + | A | | B | + | | | | + | | | | + '--------' '--------' + .-' .-' '-. + v v v + .--------. .--------. .--------. + | C | | D | | E | + | | | | | | + | | | | | | + '--------' '--------' '--------' + ``` + + Because of their simplicity, these filesystems are usually both the fastest + and smallest. However the lack of power resilience is not great, and the + binding relationship of storage location and data removes the filesystem's + ability to manage wear. + +2. In a completely different direction, we have logging filesystems, such as + [JFFS], [YAFFS], and [SPIFFS], storage location is not bound to a piece of + data, instead the entire storage is used for a circular log which is + appended with every change made to the filesystem. Writing appends new + changes, while reading requires traversing the log to reconstruct a file. + Some logging filesystems cache files to avoid the read cost, but this comes + at a tradeoff of RAM. + + ``` + v + .--------.--------.--------.--------.--------.--------.--------.--------. + | C | new B | new A | | A | B | + | | | |-> | | | + | | | | | | | + '--------'--------'--------'--------'--------'--------'--------'--------' + ``` + + Logging filesystem are beautifully elegant. With a checksum, we can easily + detect power-loss and fall back to the previous state by ignoring failed + appends. And if that wasn't good enough, their cyclic nature means that + logging filesystems distribute wear across storage perfectly. + + The main downside is performance. If we look at garbage collection, the + process of cleaning up outdated data from the end of the log, I've yet to + see a pure logging filesystem that does not have one of these two costs: + + 1. _O(n²)_ runtime + 2. _O(n)_ RAM + + SPIFFS is a very interesting case here, as it uses the fact that repeated + programs to NOR flash is both atomic and masking. This is a very neat + solution, however it limits the type of storage you can support. + +3. Perhaps the most common type of filesystem, a journaling filesystem is the + offspring that happens when you mate a block based filesystem with a logging + filesystem. [ext4] and [NTFS] are good examples. Here, we take a normal + block based filesystem and add a bounded log where we note every change + before it occurs. + + ``` + journal + .--------.--------. + .--------. | C'| D'| | E'| + | root |-->| | |-> | | + | | | | | | | + | | '--------'--------' + '--------' + .-' '-. + v v + .--------. .--------. + | A | | B | + | | | | + | | | | + '--------' '--------' + .-' .-' '-. + v v v + .--------. .--------. .--------. + | C | | D | | E | + | | | | | | + | | | | | | + '--------' '--------' '--------' + ``` + + + This sort of filesystem takes the best from both worlds. Performance can be + as fast as a block based filesystem (though updating the journal does have + a small cost), and atomic updates to the journal allow the filesystem to + recover in the event of a power loss. + + Unfortunately, journaling filesystems have a couple of problems. They are + fairly complex, since there are effectively two filesystems running in + parallel, which comes with a code size cost. They also offer no protection + against wear because of the strong relationship between storage location + and data. + +4. Last but not least we have copy-on-write (COW) filesystems, such as + [btrfs] and [ZFS]. These are very similar to other block based filesystems, + but instead of updating block inplace, all updates are performed by creating + a copy with the changes and replacing any references to the old block with + our new block. This recursively pushes all of our problems upwards until we + reach the root of our filesystem, which is often stored in a very small log. + + ``` + .--------. .--------. + | root | write |new root| + | | ==> | | + | | | | + '--------' '--------' + .-' '-. | '-. + | .-------|------------------' v + v v v .--------. + .--------. .--------. | new B | + | A | | B | | | + | | | | | | + | | | | '--------' + '--------' '--------' .-' | + .-' .-' '-. .------------|------' + | | | | v + v v v v .--------. + .--------. .--------. .--------. | new D | + | C | | D | | E | | | + | | | | | | | | + | | | | | | '--------' + '--------' '--------' '--------' + ``` + + COW filesystems are interesting. They offer very similar performance to + block based filesystems while managing to pull off atomic updates without + storing data changes directly in a log. They even disassociate the storage + location of data, which creates an opportunity for wear leveling. + + Well, almost. The unbounded upwards movement of updates causes some + problems. Because updates to a COW filesystem don't stop until they've + reached the root, an update can cascade into a larger set of writes than + would be needed for the original data. On top of this, the upward motion + focuses these writes into the block, which can wear out much earlier than + the rest of the filesystem. + +## littlefs + +So what does littlefs do? + +If we look at existing filesystems, there are two interesting design patterns +that stand out, but each have their own set of problems. Logging, which +provides independent atomicity, has poor runtime performance. And COW data +structures, which perform well, push the atomicity problem upwards. + +Can we work around these limitations? + +Consider logging. It has either a _O(n²)_ runtime or _O(n)_ RAM cost. We +can't avoid these costs, _but_ if we put an upper bound on the size we can at +least prevent the theoretical cost from becoming problem. This relies on the +super secret computer science hack where you can pretend any algorithmic +complexity is _O(1)_ by bounding the input. + +In the case of COW data structures, we can try twisting the definition a bit. +Let's say that our COW structure doesn't copy after a single write, but instead +copies after _n_ writes. This doesn't change most COW properties (assuming you +can write atomically!), but what it does do is prevent the upward motion of +wear. This sort of copy-on-bounded-writes (CObW) still focuses wear, but at +each level we divide the propagation of wear by _n_. With a sufficiently +large _n_ (> branching factor) wear propagation is no longer a problem. + +See where this is going? Separate, logging and COW are imperfect solutions and +have weaknesses that limit their usefulness. But if we merge the two they can +mutually solve each other's limitations. + +This is the idea behind littlefs. At the sub-block level, littlefs is built +out of small, two block logs that provide atomic updates to metadata anywhere +on the filesystem. At the super-block level, littlefs is a CObW tree of blocks +that can be evicted on demand. + +``` + root + .--------.--------. + | A'| B'| | + | | |-> | + | | | | + '--------'--------' + .----' '--------------. + A v B v + .--------.--------. .--------.--------. + | C'| D'| | | E'|new| | + | | |-> | | | E'|-> | + | | | | | | | | + '--------'--------' '--------'--------' + .-' '--. | '------------------. + v v .-' v +.--------. .--------. v .--------. +| C | | D | .--------. write | new E | +| | | | | E | ==> | | +| | | | | | | | +'--------' '--------' | | '--------' + '--------' .-' | + .-' '-. .-------------|------' + v v v v + .--------. .--------. .--------. + | F | | G | | new F | + | | | | | | + | | | | | | + '--------' '--------' '--------' +``` + +There are still some minor issues. Small logs can be expensive in terms of +storage, in the worst case a small log costs 4x the size of the original data. +CObW structures require an efficient block allocator since allocation occurs +every _n_ writes. And there is still the challenge of keeping the RAM usage +constant. + +## Metadata pairs + +Metadata pairs are the backbone of littlefs. These are small, two block logs +that allow atomic updates anywhere in the filesystem. + +Why two blocks? Well, logs work by appending entries to a circular buffer +stored on disk. But remember that flash has limited write granularity. We can +incrementally program new data onto erased blocks, but we need to erase a full +block at a time. This means that in order for our circular buffer to work, we +need more than one block. + +We could make our logs larger than two blocks, but the next challenge is how +do we store references to these logs? Because the blocks themselves are erased +during writes, using a data structure to track these blocks is complicated. +The simple solution here is to store a two block addresses for every metadata +pair. This has the added advantage that we can change out blocks in the +metadata pair independently, and we don't reduce our block granularity for +other operations. + +In order to determine which metadata block is the most recent, we store a +revision count that we compare using [sequence arithmetic][wikipedia-sna] +(very handy for avoiding problems with integer overflow). Conveniently, this +revision count also gives us a rough idea of how many erases have occurred on +the block. + +``` +metadata pair pointer: {block 0, block 1} + | '--------------------. + '-. | +disk v v +.--------.--------.--------.--------.--------.--------.--------.--------. +| | |metadata| |metadata| | +| | |block 0 | |block 1 | | +| | | | | | | +'--------'--------'--------'--------'--------'--------'--------'--------' + '--. .----' + v v + metadata pair .----------------.----------------. + | revision 11 | revision 12 | + block 1 is |----------------|----------------| + most recent | A | A'' | + |----------------|----------------| + | checksum | checksum | + |----------------|----------------| + | B | A''' | <- most recent A + |----------------|----------------| + | A'' | checksum | + |----------------|----------------| + | checksum | | | + |----------------| v | + '----------------'----------------' +``` + +So how do we atomically update our metadata pairs? Atomicity (a type of +power-loss resilience) requires two parts: redundancy and error detection. +Error detection can be provided with a checksum, and in littlefs's case we +use a 32-bit [CRC][wikipedia-crc]. Maintaining redundancy, on the other hand, +requires multiple stages. + +1. If our block is not full and the program size is small enough to let us + append more entries, we can simply append the entries to the log. Because + we don't overwrite the original entries (remember rewriting flash requires + an erase), we still have the original entries if we lose power during the + append. + + ``` + commit A + .----------------.----------------. .----------------.----------------. + | revision 1 | revision 0 | => | revision 1 | revision 0 | + |----------------|----------------| |----------------|----------------| + | | | | | A | | + | v | | |----------------| | + | | | | checksum | | + | | | |----------------| | + | | | | | | | + | | | | v | | + | | | | | | + | | | | | | + | | | | | | + | | | | | | + '----------------'----------------' '----------------'----------------' + ``` + + Note that littlefs doesn't maintain a checksum for each entry. Many logging + filesystems do this, but it limits what you can update in a single atomic + operation. What we can do instead is group multiple entries into a commit + that shares a single checksum. This lets us update multiple unrelated pieces + of metadata as long as they reside on the same metadata pair. + + ``` + commit B and A' + .----------------.----------------. .----------------.----------------. + | revision 1 | revision 0 | => | revision 1 | revision 0 | + |----------------|----------------| |----------------|----------------| + | A | | | A | | + |----------------| | |----------------| | + | checksum | | | checksum | | + |----------------| | |----------------| | + | | | | | B | | + | v | | |----------------| | + | | | | A' | | + | | | |----------------| | + | | | | checksum | | + | | | |----------------| | + '----------------'----------------' '----------------'----------------' + ``` + +2. If our block _is_ full of entries, we need to somehow remove outdated + entries to make space for new ones. This process is called garbage + collection, but because littlefs has multiple garbage collectors, we + also call this specific case compaction. + + Compared to other filesystems, littlefs's garbage collector is relatively + simple. We want to avoid RAM consumption, so we use a sort of brute force + solution where for each entry we check to see if a newer entry has been + written. If the entry is the most recent we append it to our new block. This + is where having two blocks becomes important, if we lose power we still have + everything in our original block. + + During this compaction step we also erase the metadata block and increment + the revision count. Because we can commit multiple entries at once, we can + write all of these changes to the second block without worrying about power + loss. It's only when the commit's checksum is written that the compacted + entries and revision count become committed and readable. + + ``` + commit B', need to compact + .----------------.----------------. .----------------.----------------. + | revision 1 | revision 0 | => | revision 1 | revision 2 | + |----------------|----------------| |----------------|----------------| + | A | | | A | A' | + |----------------| | |----------------|----------------| + | checksum | | | checksum | B' | + |----------------| | |----------------|----------------| + | B | | | B | checksum | + |----------------| | |----------------|----------------| + | A' | | | A' | | | + |----------------| | |----------------| v | + | checksum | | | checksum | | + |----------------| | |----------------| | + '----------------'----------------' '----------------'----------------' + ``` + +3. If our block is full of entries _and_ we can't find any garbage, then what? + At this point, most logging filesystems would return an error indicating no + more space is available, but because we have small logs, overflowing a log + isn't really an error condition. + + Instead, we split our original metadata pair into two metadata pairs, each + containing half of the entries, connected by a tail pointer. Instead of + increasing the size of the log and dealing with the scalability issues + associated with larger logs, we form a linked list of small bounded logs. + This is a tradeoff as this approach does use more storage space, but at the + benefit of improved scalability. + + Despite writing to two metadata pairs, we can still maintain power + resilience during this split step by first preparing the new metadata pair, + and then inserting the tail pointer during the commit to the original + metadata pair. + + ``` + commit C and D, need to split + .----------------.----------------. .----------------.----------------. + | revision 1 | revision 2 | => | revision 3 | revision 2 | + |----------------|----------------| |----------------|----------------| + | A | A' | | A' | A' | + |----------------|----------------| |----------------|----------------| + | checksum | B' | | B' | B' | + |----------------|----------------| |----------------|----------------| + | B | checksum | | tail ---------------------. + |----------------|----------------| |----------------|----------------| | + | A' | | | | checksum | | | + |----------------| v | |----------------| | | + | checksum | | | | | | | + |----------------| | | v | | | + '----------------'----------------' '----------------'----------------' | + .----------------.---------' + v v + .----------------.----------------. + | revision 1 | revision 0 | + |----------------|----------------| + | C | | + |----------------| | + | D | | + |----------------| | + | checksum | | + |----------------| | + | | | | + | v | | + | | | + | | | + '----------------'----------------' + ``` + +There is another complexity the crops up when dealing with small logs. The +amortized runtime cost of garbage collection is not only dependent on its +one time cost (_O(n²)_ for littlefs), but also depends on how often +garbage collection occurs. + +Consider two extremes: + +1. Log is empty, garbage collection occurs once every _n_ updates +2. Log is full, garbage collection occurs **every** update + +Clearly we need to be more aggressive than waiting for our metadata pair to +be full. As the metadata pair approaches fullness the frequency of compactions +grows very rapidly. + +Looking at the problem generically, consider a log with ![n] bytes for each +entry, ![d] dynamic entries (entries that are outdated during garbage +collection), and ![s] static entries (entries that need to be copied during +garbage collection). If we look at the amortized runtime complexity of updating +this log we get this formula: + +![cost = n + n (s / d+1)][metadata-formula1] + +If we let ![r] be the ratio of static space to the size of our log in bytes, we +find an alternative representation of the number of static and dynamic entries: + +![s = r (size/n)][metadata-formula2] + +![d = (1 - r) (size/n)][metadata-formula3] + +Substituting these in for ![d] and ![s] gives us a nice formula for the cost of +updating an entry given how full the log is: + +![cost = n + n (r (size/n) / ((1-r) (size/n) + 1))][metadata-formula4] + +Assuming 100 byte entries in a 4 KiB log, we can graph this using the entry +size to find a multiplicative cost: + +![Metadata pair update cost graph][metadata-cost-graph] + +So at 50% usage, we're seeing an average of 2x cost per update, and at 75% +usage, we're already at an average of 4x cost per update. + +To avoid this exponential growth, instead of waiting for our metadata pair +to be full, we split the metadata pair once we exceed 50% capacity. We do this +lazily, waiting until we need to compact before checking if we fit in our 50% +limit. This limits the overhead of garbage collection to 2x the runtime cost, +giving us an amortized runtime complexity of _O(1)_. + +--- + +If we look at metadata pairs and linked-lists of metadata pairs at a high +level, they have fairly nice runtime costs. Assuming _n_ metadata pairs, +each containing _m_ metadata entries, the _lookup_ cost for a specific +entry has a worst case runtime complexity of _O(nm)_. For _updating_ a specific +entry, the worst case complexity is _O(nm²)_, with an amortized complexity +of only _O(nm)_. + +However, splitting at 50% capacity does mean that in the best case our +metadata pairs will only be 1/2 full. If we include the overhead of the second +block in our metadata pair, each metadata entry has an effective storage cost +of 4x the original size. I imagine users would not be happy if they found +that they can only use a quarter of their original storage. Metadata pairs +provide a mechanism for performing atomic updates, but we need a separate +mechanism for storing the bulk of our data. + +## CTZ skip-lists + +Metadata pairs provide efficient atomic updates but unfortunately have a large +storage cost. But we can work around this storage cost by only using the +metadata pairs to store references to more dense, copy-on-write (COW) data +structures. + +[Copy-on-write data structures][wikipedia-cow], also called purely functional +data structures, are a category of data structures where the underlying +elements are immutable. Making changes to the data requires creating new +elements containing a copy of the updated data and replacing any references +with references to the new elements. Generally, the performance of a COW data +structure depends on how many old elements can be reused after replacing parts +of the data. + +littlefs has several requirements of its COW structures. They need to be +efficient to read and write, but most frustrating, they need to be traversable +with a constant amount of RAM. Notably this rules out +[B-trees][wikipedia-B-tree], which can not be traversed with constant RAM, and +[B+-trees][wikipedia-B+-tree], which are not possible to update with COW +operations. + +--- + +So, what can we do? First let's consider storing files in a simple COW +linked-list. Appending a block, which is the basis for writing files, means we +have to update the last block to point to our new block. This requires a COW +operation, which means we need to update the second-to-last block, and then the +third-to-last, and so on until we've copied out the entire file. + +``` +A linked-list +.--------. .--------. .--------. .--------. .--------. .--------. +| data 0 |->| data 1 |->| data 2 |->| data 4 |->| data 5 |->| data 6 | +| | | | | | | | | | | | +| | | | | | | | | | | | +'--------' '--------' '--------' '--------' '--------' '--------' +``` + +To avoid a full copy during appends, we can store the data backwards. Appending +blocks just requires adding the new block and no other blocks need to be +updated. If we update a block in the middle, we still need to copy the +following blocks, but can reuse any blocks before it. Since most file writes +are linear, this design gambles that appends are the most common type of data +update. + +``` +A backwards linked-list +.--------. .--------. .--------. .--------. .--------. .--------. +| data 0 |<-| data 1 |<-| data 2 |<-| data 4 |<-| data 5 |<-| data 6 | +| | | | | | | | | | | | +| | | | | | | | | | | | +'--------' '--------' '--------' '--------' '--------' '--------' +``` + +However, a backwards linked-list does have a rather glaring problem. Iterating +over a file _in order_ has a runtime cost of _O(n²)_. A quadratic runtime +just to read a file! That's awful. + +Fortunately we can do better. Instead of a singly linked list, littlefs +uses a multilayered linked-list often called a +[skip-list][wikipedia-skip-list]. However, unlike the most common type of +skip-list, littlefs's skip-lists are strictly deterministic built around some +interesting properties of the count-trailing-zeros (CTZ) instruction. + +The rules CTZ skip-lists follow are that for every _n_‍th block where _n_ +is divisible by 2‍_ˣ_, that block contains a pointer to block +_n_-2‍_ˣ_. This means that each block contains anywhere from 1 to +log₂_n_ pointers that skip to different preceding elements of the +skip-list. + +The name comes from heavy use of the [CTZ instruction][wikipedia-ctz], which +lets us calculate the power-of-two factors efficiently. For a give block _n_, +that block contains ctz(_n_)+1 pointers. + +``` +A backwards CTZ skip-list +.--------. .--------. .--------. .--------. .--------. .--------. +| data 0 |<-| data 1 |<-| data 2 |<-| data 3 |<-| data 4 |<-| data 5 | +| |<-| |--| |<-| |--| | | | +| |<-| |--| |--| |--| | | | +'--------' '--------' '--------' '--------' '--------' '--------' +``` + +The additional pointers let us navigate the data-structure on disk much more +efficiently than in a singly linked list. + +Consider a path from data block 5 to data block 1. You can see how data block 3 +was completely skipped: +``` +.--------. .--------. .--------. .--------. .--------. .--------. +| data 0 | | data 1 |<-| data 2 | | data 3 | | data 4 |<-| data 5 | +| | | | | |<-| |--| | | | +| | | | | | | | | | | | +'--------' '--------' '--------' '--------' '--------' '--------' +``` + +The path to data block 0 is even faster, requiring only two jumps: +``` +.--------. .--------. .--------. .--------. .--------. .--------. +| data 0 | | data 1 | | data 2 | | data 3 | | data 4 |<-| data 5 | +| | | | | | | | | | | | +| |<-| |--| |--| |--| | | | +'--------' '--------' '--------' '--------' '--------' '--------' +``` + +We can find the runtime complexity by looking at the path to any block from +the block containing the most pointers. Every step along the path divides +the search space for the block in half, giving us a runtime of _O(log n)_. +To get _to_ the block with the most pointers, we can perform the same steps +backwards, which puts the runtime at _O(2 log n)_ = _O(log n)_. An interesting +note is that this optimal path occurs naturally if we greedily choose the +pointer that covers the most distance without passing our target. + +So now we have a [COW] data structure that is cheap to append with a runtime +of _O(1)_, and can be read with a worst case runtime of _O(n log n)_. Given +that this runtime is also divided by the amount of data we can store in a +block, this cost is fairly reasonable. + +--- + +This is a new data structure, so we still have several questions. What is the +storage overhead? Can the number of pointers exceed the size of a block? How do +we store a CTZ skip-list in our metadata pairs? + +To find the storage overhead, we can look at the data structure as multiple +linked-lists. Each linked-list skips twice as many blocks as the previous, +or from another perspective, each linked-list uses half as much storage as +the previous. As we approach infinity, the storage overhead forms a geometric +series. Solving this tells us that on average our storage overhead is only +2 pointers per block. + +![lim,n->inf((1/n)sum,i,0->n(ctz(i)+1)) = sum,i,0->inf(1/2^i) = 2][ctz-formula1] + +Because our file size is limited the word width we use to store sizes, we can +also solve for the maximum number of pointers we would ever need to store in a +block. If we set the overhead of pointers equal to the block size, we get the +following equation. Note that both a smaller block size (![B][bigB]) and larger +word width (![w]) result in more storage overhead. + +![B = (w/8)ceil(log2(2^w / (B-2w/8)))][ctz-formula2] + +Solving the equation for ![B][bigB] gives us the minimum block size for some +common word widths: + +1. 32-bit CTZ skip-list => minimum block size of 104 bytes +2. 64-bit CTZ skip-list => minimum block size of 448 bytes + +littlefs uses a 32-bit word width, so our blocks can only overflow with +pointers if they are smaller than 104 bytes. This is an easy requirement, as +in practice, most block sizes start at 512 bytes. As long as our block size +is larger than 104 bytes, we can avoid the extra logic needed to handle +pointer overflow. + +This last question is how do we store CTZ skip-lists? We need a pointer to the +head block, the size of the skip-list, the index of the head block, and our +offset in the head block. But it's worth noting that each size maps to a unique +index + offset pair. So in theory we can store only a single pointer and size. + +However, calculating the index + offset pair from the size is a bit +complicated. We can start with a summation that loops through all of the blocks +up until our given size. Let ![B][bigB] be the block size in bytes, ![w] be the +word width in bits, ![n] be the index of the block in the skip-list, and +![N][bigN] be the file size in bytes: + +![N = sum,i,0->n(B-(w/8)(ctz(i)+1))][ctz-formula3] + +This works quite well, but requires _O(n)_ to compute, which brings the full +runtime of reading a file up to _O(n² log n)_. Fortunately, that summation +doesn't need to touch the disk, so the practical impact is minimal. + +However, despite the integration of a bitwise operation, we can actually reduce +this equation to a _O(1)_ form. While browsing the amazing resource that is +the [On-Line Encyclopedia of Integer Sequences (OEIS)][oeis], I managed to find +[A001511], which matches the iteration of the CTZ instruction, +and [A005187], which matches its partial summation. Much to my +surprise, these both result from simple equations, leading us to a rather +unintuitive property that ties together two seemingly unrelated bitwise +instructions: + +![sum,i,0->n(ctz(i)+1) = 2n-popcount(n)][ctz-formula4] + +where: + +1. ctz(![x]) = the number of trailing bits that are 0 in ![x] +2. popcount(![x]) = the number of bits that are 1 in ![x] + +Initial tests of this surprising property seem to hold. As ![n] approaches +infinity, we end up with an average overhead of 2 pointers, which matches our +assumption from earlier. During iteration, the popcount function seems to +handle deviations from this average. Of course, just to make sure I wrote a +quick script that verified this property for all 32-bit integers. + +Now we can substitute into our original equation to find a more efficient +equation for file size: + +![N = Bn - (w/8)(2n-popcount(n))][ctz-formula5] + +Unfortunately, the popcount function is non-injective, so we can't solve this +equation for our index. But what we can do is solve for an ![n'] index that +is greater than ![n] with error bounded by the range of the popcount function. +We can repeatedly substitute ![n'] into the original equation until the error +is smaller than our integer resolution. As it turns out, we only need to +perform this substitution once, which gives us this formula for our index: + +![n = floor((N-(w/8)popcount(N/(B-2w/8))) / (B-2w/8))][ctz-formula6] + +Now that we have our index ![n], we can just plug it back into the above +equation to find the offset. We run into a bit of a problem with integer +overflow, but we can avoid this by rearranging the equation a bit: + +![off = N - (B-2w/8)n - (w/8)popcount(n)][ctz-formula7] + +Our solution requires quite a bit of math, but computers are very good at math. +Now we can find both our block index and offset from a size in _O(1)_, letting +us store CTZ skip-lists with only a pointer and size. + +CTZ skip-lists give us a COW data structure that is easily traversable in +_O(n)_, can be appended in _O(1)_, and can be read in _O(n log n)_. All of +these operations work in a bounded amount of RAM and require only two words of +storage overhead per block. In combination with metadata pairs, CTZ skip-lists +provide power resilience and compact storage of data. + +``` + .--------. + .|metadata| + || | + || | + |'--------' + '----|---' + v +.--------. .--------. .--------. .--------. +| data 0 |<-| data 1 |<-| data 2 |<-| data 3 | +| |<-| |--| | | | +| | | | | | | | +'--------' '--------' '--------' '--------' + +write data to disk, create copies +=> + .--------. + .|metadata| + || | + || | + |'--------' + '----|---' + v +.--------. .--------. .--------. .--------. +| data 0 |<-| data 1 |<-| data 2 |<-| data 3 | +| |<-| |--| | | | +| | | | | | | | +'--------' '--------' '--------' '--------' + ^ ^ ^ + | | | .--------. .--------. .--------. .--------. + | | '----| new |<-| new |<-| new |<-| new | + | '----------------| data 2 |<-| data 3 |--| data 4 | | data 5 | + '------------------| |--| |--| | | | + '--------' '--------' '--------' '--------' + +commit to metadata pair +=> + .--------. + .|new | + ||metadata| + || | + |'--------' + '----|---' + | +.--------. .--------. .--------. .--------. | +| data 0 |<-| data 1 |<-| data 2 |<-| data 3 | | +| |<-| |--| | | | | +| | | | | | | | | +'--------' '--------' '--------' '--------' | + ^ ^ ^ v + | | | .--------. .--------. .--------. .--------. + | | '----| new |<-| new |<-| new |<-| new | + | '----------------| data 2 |<-| data 3 |--| data 4 | | data 5 | + '------------------| |--| |--| | | | + '--------' '--------' '--------' '--------' +``` + +## The block allocator + +So we now have the framework for an atomic, wear leveling filesystem. Small two +block metadata pairs provide atomic updates, while CTZ skip-lists provide +compact storage of data in COW blocks. + +But now we need to look at the [elephant] in the room. Where do all these +blocks come from? + +Deciding which block to use next is the responsibility of the block allocator. +In filesystem design, block allocation is often a second-class citizen, but in +a COW filesystem its role becomes much more important as it is needed for +nearly every write to the filesystem. + +Normally, block allocation involves some sort of free list or bitmap stored on +the filesystem that is updated with free blocks. However, with power +resilience, keeping these structures consistent becomes difficult. It doesn't +help that any mistake in updating these structures can result in lost blocks +that are impossible to recover. + +littlefs takes a cautious approach. Instead of trusting a free list on disk, +littlefs relies on the fact that the filesystem on disk is a mirror image of +the free blocks on the disk. The block allocator operates much like a garbage +collector in a scripting language, scanning for unused blocks on demand. + +``` + .----. + |root| + | | + '----' + v-------' '-------v +.----. . . .----. +| A | . . | B | +| | . . | | +'----' . . '----' +. . . . v--' '------------v---------v +. . . .----. . .----. .----. +. . . | C | . | D | | E | +. . . | | . | | | | +. . . '----' . '----' '----' +. . . . . . . . . . +.----.----.----.----.----.----.----.----.----.----.----.----. +| A | |root| C | B | | D | | E | | +| | | | | | | | | | | +'----'----'----'----'----'----'----'----'----'----'----'----' + ^ ^ ^ ^ ^ + '-------------------'----'-------------------'----'-- free blocks +``` + +While this approach may sound complicated, the decision to not maintain a free +list greatly simplifies the overall design of littlefs. Unlike programming +languages, there are only a handful of data structures we need to traverse. +And block deallocation, which occurs nearly as often as block allocation, +is simply a noop. This "drop it on the floor" strategy greatly reduces the +complexity of managing on disk data structures, especially when handling +high-risk error conditions. + +--- + +Our block allocator needs to find free blocks efficiently. You could traverse +through every block on storage and check each one against our filesystem tree; +however, the runtime would be abhorrent. We need to somehow collect multiple +blocks per traversal. + +Looking at existing designs, some larger filesystems that use a similar "drop +it on the floor" strategy store a bitmap of the entire storage in [RAM]. This +works well because bitmaps are surprisingly compact. We can't use the same +strategy here, as it violates our constant RAM requirement, but we may be able +to modify the idea into a workable solution. + +``` +.----.----.----.----.----.----.----.----.----.----.----.----. +| A | |root| C | B | | D | | E | | +| | | | | | | | | | | +'----'----'----'----'----'----'----'----'----'----'----'----' + 1 0 1 1 1 0 0 1 0 1 0 0 + \---------------------------+----------------------------/ + v + bitmap: 0xb94 (0b101110010100) +``` + +The block allocator in littlefs is a compromise between a disk-sized bitmap and +a brute force traversal. Instead of a bitmap the size of storage, we keep track +of a small, fixed-size bitmap called the lookahead buffer. During block +allocation, we take blocks from the lookahead buffer. If the lookahead buffer +is empty, we scan the filesystem for more free blocks, populating our lookahead +buffer. In each scan we use an increasing offset, circling the storage as +blocks are allocated. + +Here's what it might look like to allocate 4 blocks on a decently busy +filesystem with a 32 bit lookahead and a total of 128 blocks (512 KiB +of storage if blocks are 4 KiB): +``` +boot... lookahead: + fs blocks: fffff9fffffffffeffffffffffff0000 +scanning... lookahead: fffff9ff + fs blocks: fffff9fffffffffeffffffffffff0000 +alloc = 21 lookahead: fffffdff + fs blocks: fffffdfffffffffeffffffffffff0000 +alloc = 22 lookahead: ffffffff + fs blocks: fffffffffffffffeffffffffffff0000 +scanning... lookahead: fffffffe + fs blocks: fffffffffffffffeffffffffffff0000 +alloc = 63 lookahead: ffffffff + fs blocks: ffffffffffffffffffffffffffff0000 +scanning... lookahead: ffffffff + fs blocks: ffffffffffffffffffffffffffff0000 +scanning... lookahead: ffffffff + fs blocks: ffffffffffffffffffffffffffff0000 +scanning... lookahead: ffff0000 + fs blocks: ffffffffffffffffffffffffffff0000 +alloc = 112 lookahead: ffff8000 + fs blocks: ffffffffffffffffffffffffffff8000 +``` + +This lookahead approach has a runtime complexity of _O(n²)_ to completely +scan storage; however, bitmaps are surprisingly compact, and in practice only +one or two passes are usually needed to find free blocks. Additionally, the +performance of the allocator can be optimized by adjusting the block size or +size of the lookahead buffer, trading either write granularity or RAM for +allocator performance. + +## Wear leveling + +The block allocator has a secondary role: wear leveling. + +Wear leveling is the process of distributing wear across all blocks in the +storage to prevent the filesystem from experiencing an early death due to +wear on a single block in the storage. + +littlefs has two methods of protecting against wear: +1. Detection and recovery from bad blocks +2. Evenly distributing wear across dynamic blocks + +--- + +Recovery from bad blocks doesn't actually have anything to do with the block +allocator itself. Instead, it relies on the ability of the filesystem to detect +and evict bad blocks when they occur. + +In littlefs, it is fairly straightforward to detect bad blocks at write time. +All writes must be sourced by some form of data in RAM, so immediately after we +write to a block, we can read the data back and verify that it was written +correctly. If we find that the data on disk does not match the copy we have in +RAM, a write error has occurred and we most likely have a bad block. + +Once we detect a bad block, we need to recover from it. In the case of write +errors, we have a copy of the corrupted data in RAM, so all we need to do is +evict the bad block, allocate a new, hopefully good block, and repeat the write +that previously failed. + +The actual act of evicting the bad block and replacing it with a new block is +left up to the filesystem's copy-on-bounded-writes (CObW) data structures. One +property of CObW data structures is that any block can be replaced during a +COW operation. The bounded-writes part is normally triggered by a counter, but +nothing prevents us from triggering a COW operation as soon as we find a bad +block. + +``` + .----. + |root| + | | + '----' + v--' '----------------------v +.----. .----. +| A | | B | +| | | | +'----' '----' +. . v---' . +. . .----. . +. . | C | . +. . | | . +. . '----' . +. . . . . +.----.----.----.----.----.----.----.----.----.----. +| A |root| | C | B | | +| | | | | | | +'----'----'----'----'----'----'----'----'----'----' + +update C +=> + .----. + |root| + | | + '----' + v--' '----------------------v +.----. .----. +| A | | B | +| | | | +'----' '----' +. . v---' . +. . .----. . +. . |bad | . +. . |blck| . +. . '----' . +. . . . . +.----.----.----.----.----.----.----.----.----.----. +| A |root| |bad | B | | +| | | |blck| | | +'----'----'----'----'----'----'----'----'----'----' + +oh no! bad block! relocate C +=> + .----. + |root| + | | + '----' + v--' '----------------------v +.----. .----. +| A | | B | +| | | | +'----' '----' +. . v---' . +. . .----. . +. . |bad | . +. . |blck| . +. . '----' . +. . . . . +.----.----.----.----.----.----.----.----.----.----. +| A |root| |bad | B |bad | | +| | | |blck| |blck| | +'----'----'----'----'----'----'----'----'----'----' + ---------> +oh no! bad block! relocate C +=> + .----. + |root| + | | + '----' + v--' '----------------------v +.----. .----. +| A | | B | +| | | | +'----' '----' +. . v---' . +. . .----. . .----. +. . |bad | . | C' | +. . |blck| . | | +. . '----' . '----' +. . . . . . . +.----.----.----.----.----.----.----.----.----.----. +| A |root| |bad | B |bad | C' | | +| | | |blck| |blck| | | +'----'----'----'----'----'----'----'----'----'----' + --------------> +successfully relocated C, update B +=> + .----. + |root| + | | + '----' + v--' '----------------------v +.----. .----. +| A | |bad | +| | |blck| +'----' '----' +. . v---' . +. . .----. . .----. +. . |bad | . | C' | +. . |blck| . | | +. . '----' . '----' +. . . . . . . +.----.----.----.----.----.----.----.----.----.----. +| A |root| |bad |bad |bad | C' | | +| | | |blck|blck|blck| | | +'----'----'----'----'----'----'----'----'----'----' + +oh no! bad block! relocate B +=> + .----. + |root| + | | + '----' + v--' '----------------------v +.----. .----. .----. +| A | |bad | |bad | +| | |blck| |blck| +'----' '----' '----' +. . v---' . . . +. . .----. . .----. . +. . |bad | . | C' | . +. . |blck| . | | . +. . '----' . '----' . +. . . . . . . . +.----.----.----.----.----.----.----.----.----.----. +| A |root| |bad |bad |bad | C' |bad | +| | | |blck|blck|blck| |blck| +'----'----'----'----'----'----'----'----'----'----' + --------------> +oh no! bad block! relocate B +=> + .----. + |root| + | | + '----' + v--' '----------------------v +.----. .----. .----. +| A | | B' | |bad | +| | | | |blck| +'----' '----' '----' +. . . | . .---' . +. . . '--------------v-------------v +. . . . .----. . .----. +. . . . |bad | . | C' | +. . . . |blck| . | | +. . . . '----' . '----' +. . . . . . . . . +.----.----.----.----.----.----.----.----.----.----. +| A |root| B' | |bad |bad |bad | C' |bad | +| | | | |blck|blck|blck| |blck| +'----'----'----'----'----'----'----'----'----'----' +------------> ------------------ +successfully relocated B, update root +=> + .----. + |root| + | | + '----' + v--' '--v +.----. .----. +| A | | B' | +| | | | +'----' '----' +. . . '---------------------------v +. . . . .----. +. . . . | C' | +. . . . | | +. . . . '----' +. . . . . . +.----.----.----.----.----.----.----.----.----.----. +| A |root| B' | |bad |bad |bad | C' |bad | +| | | | |blck|blck|blck| |blck| +'----'----'----'----'----'----'----'----'----'----' +``` + +We may find that the new block is also bad, but hopefully after repeating this +cycle we'll eventually find a new block where a write succeeds. If we don't, +that means that all blocks in our storage are bad, and we've reached the end of +our device's usable life. At this point, littlefs will return an "out of space" +error. This is technically true, as there are no more good blocks, but as an +added benefit it also matches the error condition expected by users of +dynamically sized data. + +--- + +Read errors, on the other hand, are quite a bit more complicated. We don't have +a copy of the data lingering around in RAM, so we need a way to reconstruct the +original data even after it has been corrupted. One such mechanism for this is +[error-correction-codes (ECC)][wikipedia-ecc]. + +ECC is an extension to the idea of a checksum. Where a checksum such as CRC can +detect that an error has occurred in the data, ECC can detect and actually +correct some amount of errors. However, there is a limit to how many errors ECC +can detect: the [Hamming bound][wikipedia-hamming-bound]. As the number of +errors approaches the Hamming bound, we may still be able to detect errors, but +can no longer fix the data. If we've reached this point the block is +unrecoverable. + +littlefs by itself does **not** provide ECC. The block nature and relatively +large footprint of ECC does not work well with the dynamically sized data of +filesystems, correcting errors without RAM is complicated, and ECC fits better +with the geometry of block devices. In fact, several NOR flash chips have extra +storage intended for ECC, and many NAND chips can even calculate ECC on the +chip itself. + +In littlefs, ECC is entirely optional. Read errors can instead be prevented +proactively by wear leveling. But it's important to note that ECC can be used +at the block device level to modestly extend the life of a device. littlefs +respects any errors reported by the block device, allowing a block device to +provide additional aggressive error detection. + +--- + +To avoid read errors, we need to be proactive, as opposed to reactive as we +were with write errors. + +One way to do this is to detect when the number of errors in a block exceeds +some threshold, but is still recoverable. With ECC we can do this at write +time, and treat the error as a write error, evicting the block before fatal +read errors have a chance to develop. + +A different, more generic strategy, is to proactively distribute wear across +all blocks in the storage, with the hope that no single block fails before the +rest of storage is approaching the end of its usable life. This is called +wear leveling. + +Generally, wear leveling algorithms fall into one of two categories: + +1. [Dynamic wear leveling][wikipedia-dynamic-wear-leveling], where we + distribute wear over "dynamic" blocks. The can be accomplished by + only considering unused blocks. + +2. [Static wear leveling][wikipedia-static-wear-leveling], where we + distribute wear over both "dynamic" and "static" blocks. To make this work, + we need to consider all blocks, including blocks that already contain data. + +As a tradeoff for code size and complexity, littlefs (currently) only provides +dynamic wear leveling. This is a best effort solution. Wear is not distributed +perfectly, but it is distributed among the free blocks and greatly extends the +life of a device. + +On top of this, littlefs uses a statistical wear leveling algorithm. What this +means is that we don’t actively track wear, instead we rely on a uniform +distribution of wear across storage to approximate a dynamic wear leveling +algorithm. Despite the long name, this is actually a simplification of dynamic +wear leveling. + +The uniform distribution of wear is left up to the block allocator, which +creates a uniform distribution in two parts. The easy part is when the device +is powered, in which case we allocate the blocks linearly, circling the device. +The harder part is what to do when the device loses power. We can't just +restart the allocator at the beginning of storage, as this would bias the wear. +Instead, we start the allocator as a random offset every time we mount the +filesystem. As long as this random offset is uniform, the combined allocation +pattern is also a uniform distribution. + +![Cumulative wear distribution graph][wear-distribution-graph] + +Initially, this approach to wear leveling looks like it creates a difficult +dependency on a power-independent random number generator, which must return +different random numbers on each boot. However, the filesystem is in a +relatively unique situation in that it is sitting on top of a large of amount +of entropy that persists across power loss. + +We can actually use the data on disk to directly drive our random number +generator. In practice, this is implemented by xoring the checksums of each +metadata pair, which is already calculated to fetch and mount the filesystem. + +``` + .--------. \ probably random + .|metadata| | ^ + || | +-> crc ----------------------> xor + || | | ^ + |'--------' / | + '---|--|-' | + .-' '-------------------------. | + | | | + | .--------------> xor ------------> xor + | | ^ | ^ + v crc crc v crc + .--------. \ ^ .--------. \ ^ .--------. \ ^ + .|metadata|-|--|-->|metadata| | | .|metadata| | | + || | +--' || | +--' || | +--' + || | | || | | || | | + |'--------' / |'--------' / |'--------' / + '---|--|-' '----|---' '---|--|-' + .-' '-. | .-' '-. + v v v v v +.--------. .--------. .--------. .--------. .--------. +| data | | data | | data | | data | | data | +| | | | | | | | | | +| | | | | | | | | | +'--------' '--------' '--------' '--------' '--------' +``` + +Note that this random number generator is not perfect. It only returns unique +random numbers when the filesystem is modified. This is exactly what we want +for distributing wear in the allocator, but means this random number generator +is not useful for general use. + +--- + +Together, bad block detection and dynamic wear leveling provide a best effort +solution for avoiding the early death of a filesystem due to wear. Importantly, +littlefs's wear leveling algorithm provides a key feature: You can increase the +life of a device simply by increasing the size of storage. And if more +aggressive wear leveling is desired, you can always combine littlefs with a +[flash translation layer (FTL)][wikipedia-ftl] to get a small power resilient +filesystem with static wear leveling. + +## Files + +Now that we have our building blocks out of the way, we can start looking at +our filesystem as a whole. + +The first step: How do we actually store our files? + +We've determined that CTZ skip-lists are pretty good at storing data compactly, +so following the precedent found in other filesystems we could give each file +a skip-list stored in a metadata pair that acts as an inode for the file. + + +``` + .--------. + .|metadata| + || | + || | + |'--------' + '----|---' + v +.--------. .--------. .--------. .--------. +| data 0 |<-| data 1 |<-| data 2 |<-| data 3 | +| |<-| |--| | | | +| | | | | | | | +'--------' '--------' '--------' '--------' +``` + +However, this doesn't work well when files are small, which is common for +embedded systems. Compared to PCs, _all_ data in an embedded system is small. + +Consider a small 4-byte file. With a two block metadata-pair and one block for +the CTZ skip-list, we find ourselves using a full 3 blocks. On most NOR flash +with 4 KiB blocks, this is 12 KiB of overhead. A ridiculous 3072x increase. + +``` +file stored as inode, 4 bytes costs ~12 KiB + + .----------------. \ +.| revision | | +||----------------| \ | +|| skiplist ---. +- metadata | +||----------------| | / 4x8 bytes | +|| checksum | | 32 bytes | +||----------------| | | +|| | | | +- metadata pair +|| v | | | 2x4 KiB +|| | | | 8 KiB +|| | | | +|| | | | +|| | | | +|'----------------' | | +'----------------' | / + .--------' + v + .----------------. \ \ + | data | +- data | + |----------------| / 4 bytes | + | | | + | | | + | | | + | | +- data block + | | | 4 KiB + | | | + | | | + | | | + | | | + | | | + '----------------' / +``` + +We can make several improvements. First, instead of giving each file its own +metadata pair, we can store multiple files in a single metadata pair. One way +to do this is to directly associate a directory with a metadata pair (or a +linked list of metadata pairs). This makes it easy for multiple files to share +the directory's metadata pair for logging and reduces the collective storage +overhead. + +The strict binding of metadata pairs and directories also gives users +direct control over storage utilization depending on how they organize their +directories. + +``` +multiple files stored in metadata pair, 4 bytes costs ~4 KiB + + .----------------. + .| revision | + ||----------------| + || A name | + || A skiplist -----. + ||----------------| | \ + || B name | | +- metadata + || B skiplist ---. | | 4x8 bytes + ||----------------| | | / 32 bytes + || checksum | | | + ||----------------| | | + || | | | | + || v | | | + |'----------------' | | + '----------------' | | + .----------------' | + v v +.----------------. .----------------. \ \ +| A data | | B data | +- data | +| | |----------------| / 4 bytes | +| | | | | +| | | | | +| | | | | +| | | | + data block +| | | | | 4 KiB +| | | | | +|----------------| | | | +| | | | | +| | | | | +| | | | | +'----------------' '----------------' / +``` + +The second improvement we can make is noticing that for very small files, our +attempts to use CTZ skip-lists for compact storage backfires. Metadata pairs +have a ~4x storage cost, so if our file is smaller than 1/4 the block size, +there's actually no benefit in storing our file outside of our metadata pair. + +In this case, we can store the file directly in our directory's metadata pair. +We call this an inline file, and it allows a directory to store many small +files quite efficiently. Our previous 4 byte file now only takes up a +theoretical 16 bytes on disk. + +``` +inline files stored in metadata pair, 4 bytes costs ~16 bytes + + .----------------. +.| revision | +||----------------| +|| A name | +|| A skiplist ---. +||----------------| | \ +|| B name | | +- data +|| B data | | | 4x4 bytes +||----------------| | / 16 bytes +|| checksum | | +||----------------| | +|| | | | +|| v | | +|'----------------' | +'----------------' | + .---------' + v + .----------------. + | A data | + | | + | | + | | + | | + | | + | | + | | + |----------------| + | | + | | + | | + '----------------' +``` + +Once the file exceeds 1/4 the block size, we switch to a CTZ skip-list. This +means that our files never use more than 4x storage overhead, decreasing as +the file grows in size. + +![File storage cost graph][file-cost-graph] + +## Directories + +Now we just need directories to store our files. As mentioned above we want +a strict binding of directories and metadata pairs, but there are a few +complications we need to sort out. + +On their own, each directory is a linked-list of metadata pairs. This lets us +store an unlimited number of files in each directory, and we don't need to +worry about the runtime complexity of unbounded logs. We can store other +directory pointers in our metadata pairs, which gives us a directory tree, much +like what you find on other filesystems. + +``` + .--------. + .| root | + || | + || | + |'--------' + '---|--|-' + .-' '-------------------------. + v v + .--------. .--------. .--------. + .| dir A |------->| dir A | .| dir B | + || | || | || | + || | || | || | + |'--------' |'--------' |'--------' + '---|--|-' '----|---' '---|--|-' + .-' '-. | .-' '-. + v v v v v +.--------. .--------. .--------. .--------. .--------. +| file C | | file D | | file E | | file F | | file G | +| | | | | | | | | | +| | | | | | | | | | +'--------' '--------' '--------' '--------' '--------' +``` + +The main complication is, once again, traversal with a constant amount of +[RAM]. The directory tree is a tree, and the unfortunate fact is you can't +traverse a tree with constant RAM. + +Fortunately, the elements of our tree are metadata pairs, so unlike CTZ +skip-lists, we're not limited to strict COW operations. One thing we can do is +thread a linked-list through our tree, explicitly enabling cheap traversal +over the entire filesystem. + +``` + .--------. + .| root |-. + || | | + .-------|| |-' + | |'--------' + | '---|--|-' + | .-' '-------------------------. + | v v + | .--------. .--------. .--------. + '->| dir A |------->| dir A |------->| dir B | + || | || | || | + || | || | || | + |'--------' |'--------' |'--------' + '---|--|-' '----|---' '---|--|-' + .-' '-. | .-' '-. + v v v v v +.--------. .--------. .--------. .--------. .--------. +| file C | | file D | | file E | | file F | | file G | +| | | | | | | | | | +| | | | | | | | | | +'--------' '--------' '--------' '--------' '--------' +``` + +Unfortunately, not sticking to pure COW operations creates some problems. Now, +whenever we want to manipulate the directory tree, multiple pointers need to be +updated. If you're familiar with designing atomic data structures this should +set off a bunch of red flags. + +To work around this, our threaded linked-list has a bit of leeway. Instead of +only containing metadata pairs found in our filesystem, it is allowed to +contain metadata pairs that have no parent because of a power loss. These are +called orphaned metadata pairs. + +With the possibility of orphans, we can build power loss resilient operations +that maintain a filesystem tree threaded with a linked-list for traversal. + +Adding a directory to our tree: + +``` + .--------. + .| root |-. + || | | +.-------|| |-' +| |'--------' +| '---|--|-' +| .-' '-. +| v v +| .--------. .--------. +'->| dir A |->| dir C | + || | || | + || | || | + |'--------' |'--------' + '--------' '--------' + +allocate dir B +=> + .--------. + .| root |-. + || | | +.-------|| |-' +| |'--------' +| '---|--|-' +| .-' '-. +| v v +| .--------. .--------. +'->| dir A |--->| dir C | + || | .->| | + || | | || | + |'--------' | |'--------' + '--------' | '--------' + | + .--------. | + .| dir B |-' + || | + || | + |'--------' + '--------' + +insert dir B into threaded linked-list, creating an orphan +=> + .--------. + .| root |-. + || | | +.-------|| |-' +| |'--------' +| '---|--|-' +| .-' '-------------. +| v v +| .--------. .--------. .--------. +'->| dir A |->| dir B |->| dir C | + || | || orphan!| || | + || | || | || | + |'--------' |'--------' |'--------' + '--------' '--------' '--------' + +add dir B to parent directory +=> + .--------. + .| root |-. + || | | +.-------------|| |-' +| |'--------' +| '--|-|-|-' +| .------' | '-------. +| v v v +| .--------. .--------. .--------. +'->| dir A |->| dir B |->| dir C | + || | || | || | + || | || | || | + |'--------' |'--------' |'--------' + '--------' '--------' '--------' +``` + +Removing a directory: + +``` + .--------. + .| root |-. + || | | +.-------------|| |-' +| |'--------' +| '--|-|-|-' +| .------' | '-------. +| v v v +| .--------. .--------. .--------. +'->| dir A |->| dir B |->| dir C | + || | || | || | + || | || | || | + |'--------' |'--------' |'--------' + '--------' '--------' '--------' + +remove dir B from parent directory, creating an orphan +=> + .--------. + .| root |-. + || | | +.-------|| |-' +| |'--------' +| '---|--|-' +| .-' '-------------. +| v v +| .--------. .--------. .--------. +'->| dir A |->| dir B |->| dir C | + || | || orphan!| || | + || | || | || | + |'--------' |'--------' |'--------' + '--------' '--------' '--------' + +remove dir B from threaded linked-list, returning dir B to free blocks +=> + .--------. + .| root |-. + || | | +.-------|| |-' +| |'--------' +| '---|--|-' +| .-' '-. +| v v +| .--------. .--------. +'->| dir A |->| dir C | + || | || | + || | || | + |'--------' |'--------' + '--------' '--------' +``` + +In addition to normal directory tree operations, we can use orphans to evict +blocks in a metadata pair when the block goes bad or exceeds its allocated +erases. If we lose power while evicting a metadata block we may end up with +a situation where the filesystem references the replacement block while the +threaded linked-list still contains the evicted block. We call this a +half-orphan. + +``` + .--------. + .| root |-. + || | | +.-------------|| |-' +| |'--------' +| '--|-|-|-' +| .------' | '-------. +| v v v +| .--------. .--------. .--------. +'->| dir A |->| dir B |->| dir C | + || | || | || | + || | || | || | + |'--------' |'--------' |'--------' + '--------' '--------' '--------' + +try to write to dir B +=> + .--------. + .| root |-. + || | | +.----------------|| |-' +| |'--------' +| '-|-||-|-' +| .--------' || '-----. +| v |v v +| .--------. .--------. .--------. +'->| dir A |---->| dir B |->| dir C | + || |-. | | || | + || | | | | || | + |'--------' | '--------' |'--------' + '--------' | v '--------' + | .--------. + '->| dir B | + | bad | + | block! | + '--------' + +oh no! bad block detected, allocate replacement +=> + .--------. + .| root |-. + || | | +.----------------|| |-' +| |'--------' +| '-|-||-|-' +| .--------' || '-------. +| v |v v +| .--------. .--------. .--------. +'->| dir A |---->| dir B |--->| dir C | + || |-. | | .->| | + || | | | | | || | + |'--------' | '--------' | |'--------' + '--------' | v | '--------' + | .--------. | + '->| dir B | | + | bad | | + | block! | | + '--------' | + | + .--------. | + | dir B |--' + | | + | | + '--------' + +insert replacement in threaded linked-list, creating a half-orphan +=> + .--------. + .| root |-. + || | | +.----------------|| |-' +| |'--------' +| '-|-||-|-' +| .--------' || '-------. +| v |v v +| .--------. .--------. .--------. +'->| dir A |---->| dir B |--->| dir C | + || |-. | | .->| | + || | | | | | || | + |'--------' | '--------' | |'--------' + '--------' | v | '--------' + | .--------. | + | | dir B | | + | | bad | | + | | block! | | + | '--------' | + | | + | .--------. | + '->| dir B |--' + | half | + | orphan!| + '--------' + +fix reference in parent directory +=> + .--------. + .| root |-. + || | | +.-------------|| |-' +| |'--------' +| '--|-|-|-' +| .------' | '-------. +| v v v +| .--------. .--------. .--------. +'->| dir A |->| dir B |->| dir C | + || | || | || | + || | || | || | + |'--------' |'--------' |'--------' + '--------' '--------' '--------' +``` + +Finding orphans and half-orphans is expensive, requiring a _O(n²)_ +comparison of every metadata pair with every directory entry. But the tradeoff +is a power resilient filesystem that works with only a bounded amount of RAM. +Fortunately, we only need to check for orphans on the first allocation after +boot, and a read-only littlefs can ignore the threaded linked-list entirely. + +If we only had some sort of global state, then we could also store a flag and +avoid searching for orphans unless we knew we were specifically interrupted +while manipulating the directory tree (foreshadowing!). + +## The move problem + +We have one last challenge: the move problem. Phrasing the problem is simple: + +How do you atomically move a file between two directories? + +In littlefs we can atomically commit to directories, but we can't create +an atomic commit that spans multiple directories. The filesystem must go +through a minimum of two distinct states to complete a move. + +To make matters worse, file moves are a common form of synchronization for +filesystems. As a filesystem designed for power-loss, it's important we get +atomic moves right. + +So what can we do? + +- We definitely can't just let power-loss result in duplicated or lost files. + This could easily break users' code and would only reveal itself in extreme + cases. We were only able to be lazy about the threaded linked-list because + it isn't user facing and we can handle the corner cases internally. + +- Some filesystems propagate COW operations up the tree until a common parent + is found. Unfortunately this interacts poorly with our threaded tree and + brings back the issue of upward propagation of wear. + +- In a previous version of littlefs we tried to solve this problem by going + back and forth between the source and destination, marking and unmarking the + file as moving in order to make the move atomic from the user perspective. + This worked, but not well. Finding failed moves was expensive and required + a unique identifier for each file. + +In the end, solving the move problem required creating a new mechanism for +sharing knowledge between multiple metadata pairs. In littlefs this led to the +introduction of a mechanism called "global state". + +--- + +Global state is a small set of state that can be updated from _any_ metadata +pair. Combining global state with metadata pairs' ability to update multiple +entries in one commit gives us a powerful tool for crafting complex atomic +operations. + +How does global state work? + +Global state exists as a set of deltas that are distributed across the metadata +pairs in the filesystem. The actual global state can be built out of these +deltas by xoring together all of the deltas in the filesystem. + +``` + .--------. .--------. .--------. .--------. .--------. +.| |->| gdelta |->| |->| gdelta |->| gdelta | +|| | || 0x23 | || | || 0xff | || 0xce | +|| | || | || | || | || | +|'--------' |'--------' |'--------' |'--------' |'--------' +'--------' '----|---' '--------' '----|---' '----|---' + v v v + 0x00 --> xor ------------------> xor ------> xor --> gstate 0x12 +``` + +To update the global state from a metadata pair, we take the global state we +know and xor it with both our changes and any existing delta in the metadata +pair. Committing this new delta to the metadata pair commits the changes to +the filesystem's global state. + +``` + .--------. .--------. .--------. .--------. .--------. +.| |->| gdelta |->| |->| gdelta |->| gdelta | +|| | || 0x23 | || | || 0xff | || 0xce | +|| | || | || | || | || | +|'--------' |'--------' |'--------' |'--------' |'--------' +'--------' '----|---' '--------' '--|---|-' '----|---' + v v | v + 0x00 --> xor ----------------> xor -|------> xor --> gstate = 0x12 + | | + | | +change gstate to 0xab --> xor <------------|--------------------------' +=> | v + '------------> xor + | + v + .--------. .--------. .--------. .--------. .--------. +.| |->| gdelta |->| |->| gdelta |->| gdelta | +|| | || 0x23 | || | || 0x46 | || 0xce | +|| | || | || | || | || | +|'--------' |'--------' |'--------' |'--------' |'--------' +'--------' '----|---' '--------' '----|---' '----|---' + v v v + 0x00 --> xor ------------------> xor ------> xor --> gstate = 0xab +``` + +To make this efficient, we always keep a copy of the global state in RAM. We +only need to iterate over our metadata pairs and build the global state when +the filesystem is mounted. + +You may have noticed that global state is very expensive. We keep a copy in +RAM and a delta in an unbounded number of metadata pairs. Even if we reset +the global state to its initial value, we can't easily clean up the deltas on +disk. For this reason, it's very important that we keep the size of global +state bounded and extremely small. But, even with a strict budget, global +state is incredibly valuable. + +--- + +Now we can solve the move problem. We can create global state describing our +move atomically with the creation of the new file, and we can clear this move +state atomically with the removal of the old file. + +``` + .--------. gstate = no move + .| root |-. + || | | +.-------------|| |-' +| |'--------' +| '--|-|-|-' +| .------' | '-------. +| v v v +| .--------. .--------. .--------. +'->| dir A |->| dir B |->| dir C | + || | || | || | + || | || | || | + |'--------' |'--------' |'--------' + '----|---' '--------' '--------' + v + .--------. + | file D | + | | + | | + '--------' + +begin move, add reference in dir C, change gstate to have move +=> + .--------. gstate = moving file D in dir A (m1) + .| root |-. + || | | +.-------------|| |-' +| |'--------' +| '--|-|-|-' +| .------' | '-------. +| v v v +| .--------. .--------. .--------. +'->| dir A |->| dir B |->| dir C | + || | || | || gdelta | + || | || | || =m1 | + |'--------' |'--------' |'--------' + '----|---' '--------' '----|---' + | .----------------' + v v + .--------. + | file D | + | | + | | + '--------' + +complete move, remove reference in dir A, change gstate to no move +=> + .--------. gstate = no move (m1^~m1) + .| root |-. + || | | +.-------------|| |-' +| |'--------' +| '--|-|-|-' +| .------' | '-------. +| v v v +| .--------. .--------. .--------. +'->| dir A |->| dir B |->| dir C | + || gdelta | || | || gdelta | + || =~m1 | || | || =m1 | + |'--------' |'--------' |'--------' + '--------' '--------' '----|---' + v + .--------. + | file D | + | | + | | + '--------' +``` + + +If, after building our global state during mount, we find information +describing an ongoing move, we know we lost power during a move and the file +is duplicated in both the source and destination directories. If this happens, +we can resolve the move using the information in the global state to remove +one of the files. + +``` + .--------. gstate = moving file D in dir A (m1) + .| root |-. ^ + || |------------> xor +.---------------|| |-' ^ +| |'--------' | +| '--|-|-|-' | +| .--------' | '---------. | +| | | | | +| | .----------> xor --------> xor +| v | v ^ v ^ +| .--------. | .--------. | .--------. | +'->| dir A |-|->| dir B |-|->| dir C | | + || |-' || |-' || gdelta |-' + || | || | || =m1 | + |'--------' |'--------' |'--------' + '----|---' '--------' '----|---' + | .---------------------' + v v + .--------. + | file D | + | | + | | + '--------' +``` + +We can also move directories the same way we move files. There is the threaded +linked-list to consider, but leaving the threaded linked-list unchanged works +fine as the order doesn't really matter. + +``` + .--------. gstate = no move (m1^~m1) + .| root |-. + || | | +.-------------|| |-' +| |'--------' +| '--|-|-|-' +| .------' | '-------. +| v v v +| .--------. .--------. .--------. +'->| dir A |->| dir B |->| dir C | + || gdelta | || | || gdelta | + || =~m1 | || | || =m1 | + |'--------' |'--------' |'--------' + '--------' '--------' '----|---' + v + .--------. + | file D | + | | + | | + '--------' + +begin move, add reference in dir C, change gstate to have move +=> + .--------. gstate = moving dir B in root (m1^~m1^m2) + .| root |-. + || | | +.--------------|| |-' +| |'--------' +| '--|-|-|-' +| .-------' | '----------. +| v | v +| .--------. | .--------. +'->| dir A |-. | .->| dir C | + || gdelta | | | | || gdelta | + || =~m1 | | | | || =m1^m2 | + |'--------' | | | |'--------' + '--------' | | | '---|--|-' + | | .-------' | + | v v | v + | .--------. | .--------. + '->| dir B |-' | file D | + || | | | + || | | | + |'--------' '--------' + '--------' + +complete move, remove reference in root, change gstate to no move +=> + .--------. gstate = no move (m1^~m1^m2^~m2) + .| root |-. + || gdelta | | +.-----------|| =~m2 |-' +| |'--------' +| '---|--|-' +| .-----' '-----. +| v v +| .--------. .--------. +'->| dir A |-. .->| dir C | + || gdelta | | | || gdelta | + || =~m1 | | '-|| =m1^m2 |-------. + |'--------' | |'--------' | + '--------' | '---|--|-' | + | .-' '-. | + | v v | + | .--------. .--------. | + '->| dir B |--| file D |-' + || | | | + || | | | + |'--------' '--------' + '--------' +``` + +Global state gives us a powerful tool we can use to solve the move problem. +And the result is surprisingly performant, only needing the minimum number +of states and using the same number of commits as a naive move. Additionally, +global state gives us a bit of persistent state we can use for some other +small improvements. + +## Conclusion + +And that's littlefs, thanks for reading! + + +[wikipedia-flash]: https://en.wikipedia.org/wiki/Flash_memory +[wikipedia-sna]: https://en.wikipedia.org/wiki/Serial_number_arithmetic +[wikipedia-crc]: https://en.wikipedia.org/wiki/Cyclic_redundancy_check +[wikipedia-cow]: https://en.wikipedia.org/wiki/Copy-on-write +[wikipedia-B-tree]: https://en.wikipedia.org/wiki/B-tree +[wikipedia-B+-tree]: https://en.wikipedia.org/wiki/B%2B_tree +[wikipedia-skip-list]: https://en.wikipedia.org/wiki/Skip_list +[wikipedia-ctz]: https://en.wikipedia.org/wiki/Count_trailing_zeros +[wikipedia-ecc]: https://en.wikipedia.org/wiki/Error_correction_code +[wikipedia-hamming-bound]: https://en.wikipedia.org/wiki/Hamming_bound +[wikipedia-dynamic-wear-leveling]: https://en.wikipedia.org/wiki/Wear_leveling#Dynamic_wear_leveling +[wikipedia-static-wear-leveling]: https://en.wikipedia.org/wiki/Wear_leveling#Static_wear_leveling +[wikipedia-ftl]: https://en.wikipedia.org/wiki/Flash_translation_layer + +[oeis]: https://oeis.org +[A001511]: https://oeis.org/A001511 +[A005187]: https://oeis.org/A005187 + +[fat]: https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system +[ext2]: http://e2fsprogs.sourceforge.net/ext2intro.html +[jffs]: https://www.sourceware.org/jffs2/jffs2-html +[yaffs]: https://yaffs.net/documents/how-yaffs-works +[spiffs]: https://github.com/pellepl/spiffs/blob/master/docs/TECH_SPEC +[ext4]: https://ext4.wiki.kernel.org/index.php/Ext4_Design +[ntfs]: https://en.wikipedia.org/wiki/NTFS +[btrfs]: https://btrfs.wiki.kernel.org/index.php/Btrfs_design +[zfs]: https://en.wikipedia.org/wiki/ZFS + +[cow]: https://upload.wikimedia.org/wikipedia/commons/0/0c/Cow_female_black_white.jpg +[elephant]: https://upload.wikimedia.org/wikipedia/commons/3/37/African_Bush_Elephant.jpg +[ram]: https://upload.wikimedia.org/wikipedia/commons/9/97/New_Mexico_Bighorn_Sheep.JPG + +[metadata-formula1]: https://latex.codecogs.com/svg.latex?cost%20%3D%20n%20+%20n%20%5Cfrac%7Bs%7D%7Bd+1%7D +[metadata-formula2]: https://latex.codecogs.com/svg.latex?s%20%3D%20r%20%5Cfrac%7Bsize%7D%7Bn%7D +[metadata-formula3]: https://latex.codecogs.com/svg.latex?d%20%3D%20%281-r%29%20%5Cfrac%7Bsize%7D%7Bn%7D +[metadata-formula4]: https://latex.codecogs.com/svg.latex?cost%20%3D%20n%20+%20n%20%5Cfrac%7Br%5Cfrac%7Bsize%7D%7Bn%7D%7D%7B%281-r%29%5Cfrac%7Bsize%7D%7Bn%7D+1%7D + +[ctz-formula1]: https://latex.codecogs.com/svg.latex?%5Clim_%7Bn%5Cto%5Cinfty%7D%5Cfrac%7B1%7D%7Bn%7D%5Csum_%7Bi%3D0%7D%5E%7Bn%7D%5Cleft%28%5Ctext%7Bctz%7D%28i%29+1%5Cright%29%20%3D%20%5Csum_%7Bi%3D0%7D%5Cfrac%7B1%7D%7B2%5Ei%7D%20%3D%202 +[ctz-formula2]: https://latex.codecogs.com/svg.latex?B%20%3D%20%5Cfrac%7Bw%7D%7B8%7D%5Cleft%5Clceil%5Clog_2%5Cleft%28%5Cfrac%7B2%5Ew%7D%7BB-2%5Cfrac%7Bw%7D%7B8%7D%7D%5Cright%29%5Cright%5Crceil +[ctz-formula3]: https://latex.codecogs.com/svg.latex?N%20%3D%20%5Csum_i%5En%5Cleft%5BB-%5Cfrac%7Bw%7D%7B8%7D%5Cleft%28%5Ctext%7Bctz%7D%28i%29+1%5Cright%29%5Cright%5D +[ctz-formula4]: https://latex.codecogs.com/svg.latex?%5Csum_i%5En%5Cleft%28%5Ctext%7Bctz%7D%28i%29+1%5Cright%29%20%3D%202n-%5Ctext%7Bpopcount%7D%28n%29 +[ctz-formula5]: https://latex.codecogs.com/svg.latex?N%20%3D%20Bn%20-%20%5Cfrac%7Bw%7D%7B8%7D%5Cleft%282n-%5Ctext%7Bpopcount%7D%28n%29%5Cright%29 +[ctz-formula6]: https://latex.codecogs.com/svg.latex?n%20%3D%20%5Cleft%5Clfloor%5Cfrac%7BN-%5Cfrac%7Bw%7D%7B8%7D%5Cleft%28%5Ctext%7Bpopcount%7D%5Cleft%28%5Cfrac%7BN%7D%7BB-2%5Cfrac%7Bw%7D%7B8%7D%7D-1%5Cright%29+2%5Cright%29%7D%7BB-2%5Cfrac%7Bw%7D%7B8%7D%7D%5Cright%5Crfloor +[ctz-formula7]: https://latex.codecogs.com/svg.latex?%5Cmathit%7Boff%7D%20%3D%20N%20-%20%5Cleft%28B-2%5Cfrac%7Bw%7D%7B8%7D%5Cright%29n%20-%20%5Cfrac%7Bw%7D%7B8%7D%5Ctext%7Bpopcount%7D%28n%29 + +[bigB]: https://latex.codecogs.com/svg.latex?B +[d]: https://latex.codecogs.com/svg.latex?d +[m]: https://latex.codecogs.com/svg.latex?m +[bigN]: https://latex.codecogs.com/svg.latex?N +[n]: https://latex.codecogs.com/svg.latex?n +[n']: https://latex.codecogs.com/svg.latex?n%27 +[r]: https://latex.codecogs.com/svg.latex?r +[s]: https://latex.codecogs.com/svg.latex?s +[w]: https://latex.codecogs.com/svg.latex?w +[x]: https://latex.codecogs.com/svg.latex?x + +[metadata-cost-graph]: https://raw.githubusercontent.com/geky/littlefs/gh-images/metadata-cost.svg?sanitize=true +[wear-distribution-graph]: https://raw.githubusercontent.com/geky/littlefs/gh-images/wear-distribution.svg?sanitize=true +[file-cost-graph]: https://raw.githubusercontent.com/geky/littlefs/gh-images/file-cost.svg?sanitize=true diff --git a/Firmware_V3/lib/LittleFS/littlefs/LICENSE.md b/Firmware_V3/lib/LittleFS/littlefs/LICENSE.md new file mode 100644 index 0000000..ed69bea --- /dev/null +++ b/Firmware_V3/lib/LittleFS/littlefs/LICENSE.md @@ -0,0 +1,24 @@ +Copyright (c) 2017, Arm Limited. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. +- Neither the name of ARM nor the names of its contributors may be used to + endorse or promote products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Firmware_V3/lib/LittleFS/littlefs/README.md b/Firmware_V3/lib/LittleFS/littlefs/README.md new file mode 100644 index 0000000..f900674 --- /dev/null +++ b/Firmware_V3/lib/LittleFS/littlefs/README.md @@ -0,0 +1,252 @@ +## littlefs + +A little fail-safe filesystem designed for microcontrollers. + +``` + | | | .---._____ + .-----. | | +--|o |---| littlefs | +--| |---| | + '-----' '----------' + | | | +``` + +**Power-loss resilience** - littlefs is designed to handle random power +failures. All file operations have strong copy-on-write guarantees and if +power is lost the filesystem will fall back to the last known good state. + +**Dynamic wear leveling** - littlefs is designed with flash in mind, and +provides wear leveling over dynamic blocks. Additionally, littlefs can +detect bad blocks and work around them. + +**Bounded RAM/ROM** - littlefs is designed to work with a small amount of +memory. RAM usage is strictly bounded, which means RAM consumption does not +change as the filesystem grows. The filesystem contains no unbounded +recursion and dynamic memory is limited to configurable buffers that can be +provided statically. + +## Example + +Here's a simple example that updates a file named `boot_count` every time +main runs. The program can be interrupted at any time without losing track +of how many times it has been booted and without corrupting the filesystem: + +``` c +#include "lfs.h" + +// variables used by the filesystem +lfs_t lfs; +lfs_file_t file; + +// configuration of the filesystem is provided by this struct +const struct lfs_config cfg = { + // block device operations + .read = user_provided_block_device_read, + .prog = user_provided_block_device_prog, + .erase = user_provided_block_device_erase, + .sync = user_provided_block_device_sync, + + // block device configuration + .read_size = 16, + .prog_size = 16, + .block_size = 4096, + .block_count = 128, + .cache_size = 16, + .lookahead_size = 16, + .block_cycles = 500, +}; + +// entry point +int main(void) { + // mount the filesystem + int err = lfs_mount(&lfs, &cfg); + + // reformat if we can't mount the filesystem + // this should only happen on the first boot + if (err) { + lfs_format(&lfs, &cfg); + lfs_mount(&lfs, &cfg); + } + + // read current count + uint32_t boot_count = 0; + lfs_file_open(&lfs, &file, "boot_count", LFS_O_RDWR | LFS_O_CREAT); + lfs_file_read(&lfs, &file, &boot_count, sizeof(boot_count)); + + // update boot count + boot_count += 1; + lfs_file_rewind(&lfs, &file); + lfs_file_write(&lfs, &file, &boot_count, sizeof(boot_count)); + + // remember the storage is not updated until the file is closed successfully + lfs_file_close(&lfs, &file); + + // release any resources we were using + lfs_unmount(&lfs); + + // print the boot count + printf("boot_count: %d\n", boot_count); +} +``` + +## Usage + +Detailed documentation (or at least as much detail as is currently available) +can be found in the comments in [lfs.h](lfs.h). + +littlefs takes in a configuration structure that defines how the filesystem +operates. The configuration struct provides the filesystem with the block +device operations and dimensions, tweakable parameters that tradeoff memory +usage for performance, and optional static buffers if the user wants to avoid +dynamic memory. + +The state of the littlefs is stored in the `lfs_t` type which is left up +to the user to allocate, allowing multiple filesystems to be in use +simultaneously. With the `lfs_t` and configuration struct, a user can +format a block device or mount the filesystem. + +Once mounted, the littlefs provides a full set of POSIX-like file and +directory functions, with the deviation that the allocation of filesystem +structures must be provided by the user. + +All POSIX operations, such as remove and rename, are atomic, even in event +of power-loss. Additionally, file updates are not actually committed to +the filesystem until sync or close is called on the file. + +## Other notes + +Littlefs is written in C, and specifically should compile with any compiler +that conforms to the `C99` standard. + +All littlefs calls have the potential to return a negative error code. The +errors can be either one of those found in the `enum lfs_error` in +[lfs.h](lfs.h), or an error returned by the user's block device operations. + +In the configuration struct, the `prog` and `erase` function provided by the +user may return a `LFS_ERR_CORRUPT` error if the implementation already can +detect corrupt blocks. However, the wear leveling does not depend on the return +code of these functions, instead all data is read back and checked for +integrity. + +If your storage caches writes, make sure that the provided `sync` function +flushes all the data to memory and ensures that the next read fetches the data +from memory, otherwise data integrity can not be guaranteed. If the `write` +function does not perform caching, and therefore each `read` or `write` call +hits the memory, the `sync` function can simply return 0. + +## Design + +At a high level, littlefs is a block based filesystem that uses small logs to +store metadata and larger copy-on-write (COW) structures to store file data. + +In littlefs, these ingredients form a sort of two-layered cake, with the small +logs (called metadata pairs) providing fast updates to metadata anywhere on +storage, while the COW structures store file data compactly and without any +wear amplification cost. + +Both of these data structures are built out of blocks, which are fed by a +common block allocator. By limiting the number of erases allowed on a block +per allocation, the allocator provides dynamic wear leveling over the entire +filesystem. + +``` + root + .--------.--------. + | A'| B'| | + | | |-> | + | | | | + '--------'--------' + .----' '--------------. + A v B v + .--------.--------. .--------.--------. + | C'| D'| | | E'|new| | + | | |-> | | | E'|-> | + | | | | | | | | + '--------'--------' '--------'--------' + .-' '--. | '------------------. + v v .-' v +.--------. .--------. v .--------. +| C | | D | .--------. write | new E | +| | | | | E | ==> | | +| | | | | | | | +'--------' '--------' | | '--------' + '--------' .-' | + .-' '-. .-------------|------' + v v v v + .--------. .--------. .--------. + | F | | G | | new F | + | | | | | | + | | | | | | + '--------' '--------' '--------' +``` + +More details on how littlefs works can be found in [DESIGN.md](DESIGN.md) and +[SPEC.md](SPEC.md). + +- [DESIGN.md](DESIGN.md) - A fully detailed dive into how littlefs works. + I would suggest reading it as the tradeoffs at work are quite interesting. + +- [SPEC.md](SPEC.md) - The on-disk specification of littlefs with all the + nitty-gritty details. May be useful for tooling development. + +## Testing + +The littlefs comes with a test suite designed to run on a PC using the +[emulated block device](emubd/lfs_emubd.h) found in the emubd directory. +The tests assume a Linux environment and can be started with make: + +``` bash +make test +``` + +## License + +The littlefs is provided under the [BSD-3-Clause] license. See +[LICENSE.md](LICENSE.md) for more information. Contributions to this project +are accepted under the same license. + +Individual files contain the following tag instead of the full license text. + + SPDX-License-Identifier: BSD-3-Clause + +This enables machine processing of license information based on the SPDX +License Identifiers that are here available: http://spdx.org/licenses/ + +## Related projects + +- [littlefs-fuse] - A [FUSE] wrapper for littlefs. The project allows you to + mount littlefs directly on a Linux machine. Can be useful for debugging + littlefs if you have an SD card handy. + +- [littlefs-js] - A javascript wrapper for littlefs. I'm not sure why you would + want this, but it is handy for demos. You can see it in action + [here][littlefs-js-demo]. + +- [mklfs] - A command line tool built by the [Lua RTOS] guys for making + littlefs images from a host PC. Supports Windows, Mac OS, and Linux. + +- [Mbed OS] - The easiest way to get started with littlefs is to jump into Mbed + which already has block device drivers for most forms of embedded storage. + littlefs is available in Mbed OS as the [LittleFileSystem] class. + +- [SPIFFS] - Another excellent embedded filesystem for NOR flash. As a more + traditional logging filesystem with full static wear-leveling, SPIFFS will + likely outperform littlefs on small memories such as the internal flash on + microcontrollers. + +- [Dhara] - An interesting NAND flash translation layer designed for small + MCUs. It offers static wear-leveling and power-resilience with only a fixed + _O(|address|)_ pointer structure stored on each block and in RAM. + + +[BSD-3-Clause]: https://spdx.org/licenses/BSD-3-Clause.html +[littlefs-fuse]: https://github.com/geky/littlefs-fuse +[FUSE]: https://github.com/libfuse/libfuse +[littlefs-js]: https://github.com/geky/littlefs-js +[littlefs-js-demo]:http://littlefs.geky.net/demo.html +[mklfs]: https://github.com/whitecatboard/Lua-RTOS-ESP32/tree/master/components/mklfs/src +[Lua RTOS]: https://github.com/whitecatboard/Lua-RTOS-ESP32 +[Mbed OS]: https://github.com/armmbed/mbed-os +[LittleFileSystem]: https://os.mbed.com/docs/mbed-os/v5.12/apis/littlefilesystem.html +[SPIFFS]: https://github.com/pellepl/spiffs +[Dhara]: https://github.com/dlbeer/dhara diff --git a/Firmware_V3/lib/LittleFS/littlefs/SPEC.md b/Firmware_V3/lib/LittleFS/littlefs/SPEC.md new file mode 100644 index 0000000..cc602c1 --- /dev/null +++ b/Firmware_V3/lib/LittleFS/littlefs/SPEC.md @@ -0,0 +1,787 @@ +## littlefs technical specification + +This is the technical specification of the little filesystem. This document +covers the technical details of how the littlefs is stored on disk for +introspection and tooling. This document assumes you are familiar with the +design of the littlefs, for more info on how littlefs works check +out [DESIGN.md](DESIGN.md). + +``` + | | | .---._____ + .-----. | | +--|o |---| littlefs | +--| |---| | + '-----' '----------' + | | | +``` + +## Some quick notes + +- littlefs is a block-based filesystem. The disk is divided into an array of + evenly sized blocks that are used as the logical unit of storage. + +- Block pointers are stored in 32 bits, with the special value `0xffffffff` + representing a null block address. + +- In addition to the logical block size (which usually matches the erase + block size), littlefs also uses a program block size and read block size. + These determine the alignment of block device operations, but don't need + to be consistent for portability. + +- By default, all values in littlefs are stored in little-endian byte order. + +## Directories / Metadata pairs + +Metadata pairs form the backbone of littlefs and provide a system for +distributed atomic updates. Even the superblock is stored in a metadata pair. + +As their name suggests, a metadata pair is stored in two blocks, with one block +providing a backup during erase cycles in case power is lost. These two blocks +are not necessarily sequential and may be anywhere on disk, so a "pointer" to a +metadata pair is stored as two block pointers. + +On top of this, each metadata block behaves as an appendable log, containing a +variable number of commits. Commits can be appended to the metadata log in +order to update the metadata without requiring an erase cycles. Note that +successive commits may supersede the metadata in previous commits. Only the +most recent metadata should be considered valid. + +The high-level layout of a metadata block is fairly simple: + +``` + .---------------------------------------. +.-| revision count | entries | \ +| |-------------------+ | | +| | | | +| | | +-- 1st commit +| | | | +| | +-------------------| | +| | | CRC | / +| |-------------------+-------------------| +| | entries | \ +| | | | +| | | +-- 2nd commit +| | +-------------------+--------------| | +| | | CRC | padding | / +| |----+-------------------+--------------| +| | entries | \ +| | | | +| | | +-- 3rd commit +| | +-------------------+---------| | +| | | CRC | | / +| |---------+-------------------+ | +| | unwritten storage | more commits +| | | | +| | | v +| | | +| | | +| '---------------------------------------' +'---------------------------------------' +``` + +Each metadata block contains a 32-bit revision count followed by a number of +commits. Each commit contains a variable number of metadata entries followed +by a 32-bit CRC. + +Note also that entries aren't necessarily word-aligned. This allows us to +store metadata more compactly, however we can only write to addresses that are +aligned to our program block size. This means each commit may have padding for +alignment. + +Metadata block fields: + +1. **Revision count (32-bits)** - Incremented every erase cycle. If both blocks + contain valid commits, only the block with the most recent revision count + should be used. Sequence comparison must be used to avoid issues with + integer overflow. + +2. **CRC (32-bits)** - Detects corruption from power-loss or other write + issues. Uses a CRC-32 with a polynomial of `0x04c11db7` initialized + with `0xffffffff`. + +Entries themselves are stored as a 32-bit tag followed by a variable length +blob of data. But exactly how these tags are stored is a little bit tricky. + +Metadata blocks support both forward and backward iteration. In order to do +this without duplicating the space for each tag, neighboring entries have their +tags XORed together, starting with `0xffffffff`. + +``` + Forward iteration Backward iteration + +.-------------------. 0xffffffff .-------------------. +| revision count | | | revision count | +|-------------------| v |-------------------| +| tag ~A |---> xor -> tag A | tag ~A |---> xor -> 0xffffffff +|-------------------| | |-------------------| ^ +| data A | | | data A | | +| | | | | | +| | | | | | +|-------------------| v |-------------------| | +| tag AxB |---> xor -> tag B | tag AxB |---> xor -> tag A +|-------------------| | |-------------------| ^ +| data B | | | data B | | +| | | | | | +| | | | | | +|-------------------| v |-------------------| | +| tag BxC |---> xor -> tag C | tag BxC |---> xor -> tag B +|-------------------| |-------------------| ^ +| data C | | data C | | +| | | | tag C +| | | | +| | | | +'-------------------' '-------------------' +``` + +One last thing to note before we get into the details around tag encoding. Each +tag contains a valid bit used to indicate if the tag and containing commit is +valid. This valid bit is the first bit found in the tag and the commit and can +be used to tell if we've attempted to write to the remaining space in the +block. + +Here's a more complete example of metadata block containing 4 entries: + +``` + .---------------------------------------. +.-| revision count | tag ~A | \ +| |-------------------+-------------------| | +| | data A | | +| | | | +| |-------------------+-------------------| | +| | tag AxB | data B | <--. | +| |-------------------+ | | | +| | | | +-- 1st commit +| | +-------------------+---------| | | +| | | tag BxC | | <-.| | +| |---------+-------------------+ | || | +| | data C | || | +| | | || | +| |-------------------+-------------------| || | +| | tag CxCRC | CRC | || / +| |-------------------+-------------------| || +| | tag CRCxA' | data A' | || \ +| |-------------------+ | || | +| | | || | +| | +-------------------+----| || +-- 2nd commit +| | | tag CRCxA' | | || | +| |--------------+-------------------+----| || | +| | CRC | padding | || / +| |--------------+----+-------------------| || +| | tag CRCxA'' | data A'' | <---. \ +| |-------------------+ | ||| | +| | | ||| | +| | +-------------------+---------| ||| | +| | | tag A''xD | | < ||| | +| |---------+-------------------+ | |||| +-- 3rd commit +| | data D | |||| | +| | +---------| |||| | +| | | tag Dx| |||| | +| |---------+-------------------+---------| |||| | +| |CRC | CRC | | |||| / +| |---------+-------------------+ | |||| +| | unwritten storage | |||| more commits +| | | |||| | +| | | |||| v +| | | |||| +| | | |||| +| '---------------------------------------' |||| +'---------------------------------------' |||'- most recent A + ||'-- most recent B + |'--- most recent C + '---- most recent D +``` + +## Metadata tags + +So in littlefs, 32-bit tags describe every type of metadata. And this means +_every_ type of metadata, including file entries, directory fields, and +global state. Even the CRCs used to mark the end of commits get their own tag. + +Because of this, the tag format contains some densely packed information. Note +that there are multiple levels of types which break down into more info: + +``` +[---- 32 ----] +[1|-- 11 --|-- 10 --|-- 10 --] + ^. ^ . ^ ^- length + |. | . '------------ id + |. '-----.------------------ type (type3) + '.-----------.------------------ valid bit + [-3-|-- 8 --] + ^ ^- chunk + '------- type (type1) +``` + + +Before we go further, there's one important thing to note. These tags are +**not** stored in little-endian. Tags stored in commits are actually stored +in big-endian (and is the only thing in littlefs stored in big-endian). This +little bit of craziness comes from the fact that the valid bit must be the +first bit in a commit, and when converted to little-endian, the valid bit finds +itself in byte 4. We could restructure the tag to store the valid bit lower, +but, because none of the fields are byte-aligned, this would be more +complicated than just storing the tag in big-endian. + +Another thing to note is that both the tags `0x00000000` and `0xffffffff` are +invalid and can be used for null values. + +Metadata tag fields: + +1. **Valid bit (1-bit)** - Indicates if the tag is valid. + +2. **Type3 (11-bits)** - Type of the tag. This field is broken down further + into a 3-bit abstract type and an 8-bit chunk field. Note that the value + `0x000` is invalid and not assigned a type. + +3. **Type1 (3-bits)** - Abstract type of the tag. Groups the tags into + 8 categories that facilitate bitmasked lookups. + +4. **Chunk (8-bits)** - Chunk field used for various purposes by the different + abstract types. type1+chunk+id form a unique identifier for each tag in the + metadata block. + +5. **Id (10-bits)** - File id associated with the tag. Each file in a metadata + block gets a unique id which is used to associate tags with that file. The + special value `0x3ff` is used for any tags that are not associated with a + file, such as directory and global metadata. + +6. **Length (10-bits)** - Length of the data in bytes. The special value + `0x3ff` indicates that this tag has been deleted. + +## Metadata types + +What follows is an exhaustive list of metadata in littlefs. + +--- +#### `0x401` LFS_TYPE_CREATE + +Creates a new file with this id. Note that files in a metadata block +don't necessarily need a create tag. All a create does is move over any +files using this id. In this sense a create is similar to insertion into +an imaginary array of files. + +The create and delete tags allow littlefs to keep files in a directory +ordered alphabetically by filename. + +--- +#### `0x4ff` LFS_TYPE_DELETE + +Deletes the file with this id. An inverse to create, this tag moves over +any files neighboring this id similar to a deletion from an imaginary +array of files. + +--- +#### `0x0xx` LFS_TYPE_NAME + +Associates the id with a file name and file type. + +The data contains the file name stored as an ASCII string (may be expanded to +UTF8 in the future). + +The chunk field in this tag indicates an 8-bit file type which can be one of +the following. + +Currently, the name tag must precede any other tags associated with the id and +can not be reassigned without deleting the file. + +Layout of the name tag: + +``` + tag data +[-- 32 --][--- variable length ---] +[1| 3| 8 | 10 | 10 ][--- (size * 8) ---] + ^ ^ ^ ^ ^- size ^- file name + | | | '------ id + | | '----------- file type + | '-------------- type1 (0x0) + '----------------- valid bit +``` + +Name fields: + +1. **file type (8-bits)** - Type of the file. + +2. **file name** - File name stored as an ASCII string. + +--- +#### `0x001` LFS_TYPE_REG + +Initializes the id + name as a regular file. + +How each file is stored depends on its struct tag, which is described below. + +--- +#### `0x002` LFS_TYPE_DIR + +Initializes the id + name as a directory. + +Directories in littlefs are stored on disk as a linked-list of metadata pairs, +each pair containing any number of files in alphabetical order. A pointer to +the directory is stored in the struct tag, which is described below. + +--- +#### `0x0ff` LFS_TYPE_SUPERBLOCK + +Initializes the id as a superblock entry. + +The superblock entry is a special entry used to store format-time configuration +and identify the filesystem. + +The name is a bit of a misnomer. While the superblock entry serves the same +purpose as a superblock found in other filesystems, in littlefs the superblock +does not get a dedicated block. Instead, the superblock entry is duplicated +across a linked-list of metadata pairs rooted on the blocks 0 and 1. The last +metadata pair doubles as the root directory of the filesystem. + +``` + .--------. .--------. .--------. .--------. .--------. +.| super |->| super |->| super |->| super |->| file B | +|| block | || block | || block | || block | || file C | +|| | || | || | || file A | || file D | +|'--------' |'--------' |'--------' |'--------' |'--------' +'--------' '--------' '--------' '--------' '--------' + +\----------------+----------------/ \----------+----------/ + superblock pairs root directory +``` + +The filesystem starts with only the root directory. The superblock metadata +pairs grow every time the root pair is compacted in order to prolong the +life of the device exponentially. + +The contents of the superblock entry are stored in a name tag with the +superblock type and an inline-struct tag. The name tag contains the magic +string "littlefs", while the inline-struct tag contains version and +configuration information. + +Layout of the superblock name tag and inline-struct tag: + +``` + tag data +[-- 32 --][-- 32 --|-- 32 --] +[1|- 11 -| 10 | 10 ][--- 64 ---] + ^ ^ ^ ^- size (8) ^- magic string ("littlefs") + | | '------ id (0) + | '------------ type (0x0ff) + '----------------- valid bit + + tag data +[-- 32 --][-- 32 --|-- 32 --|-- 32 --] +[1|- 11 -| 10 | 10 ][-- 32 --|-- 32 --|-- 32 --] + ^ ^ ^ ^ ^- version ^- block size ^- block count + | | | | [-- 32 --|-- 32 --|-- 32 --] + | | | | [-- 32 --|-- 32 --|-- 32 --] + | | | | ^- name max ^- file max ^- attr max + | | | '- size (24) + | | '------ id (0) + | '------------ type (0x201) + '----------------- valid bit +``` + +Superblock fields: + +1. **Magic string (8-bytes)** - Magic string indicating the presence of + littlefs on the device. Must be the string "littlefs". + +2. **Version (32-bits)** - The version of littlefs at format time. The version + is encoded in a 32-bit value with the upper 16-bits containing the major + version, and the lower 16-bits containing the minor version. + + This specification describes version 2.0 (`0x00020000`). + +3. **Block size (32-bits)** - Size of the logical block size used by the + filesystem in bytes. + +4. **Block count (32-bits)** - Number of blocks in the filesystem. + +5. **Name max (32-bits)** - Maximum size of file names in bytes. + +6. **File max (32-bits)** - Maximum size of files in bytes. + +7. **Attr max (32-bits)** - Maximum size of file attributes in bytes. + +The superblock must always be the first entry (id 0) in a metadata pair as well +as be the first entry written to the block. This means that the superblock +entry can be read from a device using offsets alone. + +--- +#### `0x2xx` LFS_TYPE_STRUCT + +Associates the id with an on-disk data structure. + +The exact layout of the data depends on the data structure type stored in the +chunk field and can be one of the following. + +Any type of struct supersedes all other structs associated with the id. For +example, appending a ctz-struct replaces an inline-struct on the same file. + +--- +#### `0x200` LFS_TYPE_DIRSTRUCT + +Gives the id a directory data structure. + +Directories in littlefs are stored on disk as a linked-list of metadata pairs, +each pair containing any number of files in alphabetical order. + +``` + | + v + .--------. .--------. .--------. .--------. .--------. .--------. +.| file A |->| file D |->| file G |->| file I |->| file J |->| file M | +|| file B | || file E | || file H | || | || file K | || file N | +|| file C | || file F | || | || | || file L | || | +|'--------' |'--------' |'--------' |'--------' |'--------' |'--------' +'--------' '--------' '--------' '--------' '--------' '--------' +``` + +The dir-struct tag contains only the pointer to the first metadata-pair in the +directory. The directory size is not known without traversing the directory. + +The pointer to the next metadata-pair in the directory is stored in a tail tag, +which is described below. + +Layout of the dir-struct tag: + +``` + tag data +[-- 32 --][-- 32 --|-- 32 --] +[1|- 11 -| 10 | 10 ][--- 64 ---] + ^ ^ ^ ^- size (8) ^- metadata pair + | | '------ id + | '------------ type (0x200) + '----------------- valid bit +``` + +Dir-struct fields: + +1. **Metadata pair (8-bytes)** - Pointer to the first metadata-pair + in the directory. + +--- +#### `0x201` LFS_TYPE_INLINESTRUCT + +Gives the id an inline data structure. + +Inline structs store small files that can fit in the metadata pair. In this +case, the file data is stored directly in the tag's data area. + +Layout of the inline-struct tag: + +``` + tag data +[-- 32 --][--- variable length ---] +[1|- 11 -| 10 | 10 ][--- (size * 8) ---] + ^ ^ ^ ^- size ^- inline data + | | '------ id + | '------------ type (0x201) + '----------------- valid bit +``` + +Inline-struct fields: + +1. **Inline data** - File data stored directly in the metadata-pair. + +--- +#### `0x202` LFS_TYPE_CTZSTRUCT + +Gives the id a CTZ skip-list data structure. + +CTZ skip-lists store files that can not fit in the metadata pair. These files +are stored in a skip-list in reverse, with a pointer to the head of the +skip-list. Note that the head of the skip-list and the file size is enough +information to read the file. + +How exactly CTZ skip-lists work is a bit complicated. A full explanation can be +found in the [DESIGN.md](DESIGN.md#ctz-skip-lists). + +A quick summary: For every _n_‍th block where _n_ is divisible by +2‍_ˣ_, that block contains a pointer to block _n_-2‍_ˣ_. +These pointers are stored in increasing order of _x_ in each block of the file +before the actual data. + +``` + | + v +.--------. .--------. .--------. .--------. .--------. .--------. +| A |<-| D |<-| G |<-| J |<-| M |<-| P | +| B |<-| E |--| H |<-| K |--| N | | Q | +| C |<-| F |--| I |--| L |--| O | | | +'--------' '--------' '--------' '--------' '--------' '--------' + block 0 block 1 block 2 block 3 block 4 block 5 + 1 skip 2 skips 1 skip 3 skips 1 skip +``` + +Note that the maximum number of pointers in a block is bounded by the maximum +file size divided by the block size. With 32 bits for file size, this results +in a minimum block size of 104 bytes. + +Layout of the CTZ-struct tag: + +``` + tag data +[-- 32 --][-- 32 --|-- 32 --] +[1|- 11 -| 10 | 10 ][-- 32 --|-- 32 --] + ^ ^ ^ ^ ^ ^- file size + | | | | '-------------------- file head + | | | '- size (8) + | | '------ id + | '------------ type (0x202) + '----------------- valid bit +``` + +CTZ-struct fields: + +1. **File head (32-bits)** - Pointer to the block that is the head of the + file's CTZ skip-list. + +2. **File size (32-bits)** - Size of the file in bytes. + +--- +#### `0x3xx` LFS_TYPE_USERATTR + +Attaches a user attribute to an id. + +littlefs has a concept of "user attributes". These are small user-provided +attributes that can be used to store things like timestamps, hashes, +permissions, etc. + +Each user attribute is uniquely identified by an 8-bit type which is stored in +the chunk field, and the user attribute itself can be found in the tag's data. + +There are currently no standard user attributes and a portable littlefs +implementation should work with any user attributes missing. + +Layout of the user-attr tag: + +``` + tag data +[-- 32 --][--- variable length ---] +[1| 3| 8 | 10 | 10 ][--- (size * 8) ---] + ^ ^ ^ ^ ^- size ^- attr data + | | | '------ id + | | '----------- attr type + | '-------------- type1 (0x3) + '----------------- valid bit +``` + +User-attr fields: + +1. **Attr type (8-bits)** - Type of the user attributes. + +2. **Attr data** - The data associated with the user attribute. + +--- +#### `0x6xx` LFS_TYPE_TAIL + +Provides the tail pointer for the metadata pair itself. + +The metadata pair's tail pointer is used in littlefs for a linked-list +containing all metadata pairs. The chunk field contains the type of the tail, +which indicates if the following metadata pair is a part of the directory +(hard-tail) or only used to traverse the filesystem (soft-tail). + +``` + .--------. + .| dir A |-. + ||softtail| | +.--------| |-' +| |'--------' +| '---|--|-' +| .-' '-------------. +| v v +| .--------. .--------. .--------. +'->| dir B |->| dir B |->| dir C | + ||hardtail| ||softtail| || | + || | || | || | + |'--------' |'--------' |'--------' + '--------' '--------' '--------' +``` + +Currently any type supersedes any other preceding tails in the metadata pair, +but this may change if additional metadata pair state is added. + +A note about the metadata pair linked-list: Normally, this linked-list contains +every metadata pair in the filesystem. However, there are some operations that +can cause this linked-list to become out of sync if a power-loss were to occur. +When this happens, littlefs sets the "sync" flag in the global state. How +exactly this flag is stored is described below. + +When the sync flag is set: + +1. The linked-list may contain an orphaned directory that has been removed in + the filesystem. +2. The linked-list may contain a metadata pair with a bad block that has been + replaced in the filesystem. + +If the sync flag is set, the threaded linked-list must be checked for these +errors before it can be used reliably. Note that the threaded linked-list can +be ignored if littlefs is mounted read-only. + +Layout of the tail tag: + +``` + tag data +[-- 32 --][-- 32 --|-- 32 --] +[1| 3| 8 | 10 | 10 ][--- 64 ---] + ^ ^ ^ ^ ^- size (8) ^- metadata pair + | | | '------ id + | | '---------- tail type + | '------------- type1 (0x6) + '---------------- valid bit +``` + +Tail fields: + +1. **Tail type (8-bits)** - Type of the tail pointer. + +2. **Metadata pair (8-bytes)** - Pointer to the next metadata-pair. + +--- +#### `0x600` LFS_TYPE_SOFTTAIL + +Provides a tail pointer that points to the next metadata pair in the +filesystem. + +In this case, the next metadata pair is not a part of our current directory +and should only be followed when traversing the entire filesystem. + +--- +#### `0x601` LFS_TYPE_HARDTAIL + +Provides a tail pointer that points to the next metadata pair in the +directory. + +In this case, the next metadata pair belongs to the current directory. Note +that because directories in littlefs are sorted alphabetically, the next +metadata pair should only contain filenames greater than any filename in the +current pair. + +--- +#### `0x7xx` LFS_TYPE_GSTATE + +Provides delta bits for global state entries. + +littlefs has a concept of "global state". This is a small set of state that +can be updated by a commit to _any_ metadata pair in the filesystem. + +The way this works is that the global state is stored as a set of deltas +distributed across the filesystem such that the global state can be found by +the xor-sum of these deltas. + +``` + .--------. .--------. .--------. .--------. .--------. +.| |->| gdelta |->| |->| gdelta |->| gdelta | +|| | || 0x23 | || | || 0xff | || 0xce | +|| | || | || | || | || | +|'--------' |'--------' |'--------' |'--------' |'--------' +'--------' '----|---' '--------' '----|---' '----|---' + v v v + 0x00 --> xor ------------------> xor ------> xor --> gstate = 0x12 +``` + +Note that storing globals this way is very expensive in terms of storage usage, +so any global state should be kept very small. + +The size and format of each piece of global state depends on the type, which +is stored in the chunk field. Currently, the only global state is move state, +which is outlined below. + +--- +#### `0x7ff` LFS_TYPE_MOVESTATE + +Provides delta bits for the global move state. + +The move state in littlefs is used to store info about operations that could +cause to filesystem to go out of sync if the power is lost. The operations +where this could occur is moves of files between metadata pairs and any +operation that changes metadata pairs on the threaded linked-list. + +In the case of moves, the move state contains a tag + metadata pair describing +the source of the ongoing move. If this tag is non-zero, that means that power +was lost during a move, and the file exists in two different locations. If this +happens, the source of the move should be considered deleted, and the move +should be completed (the source should be deleted) before any other write +operations to the filesystem. + +In the case of operations to the threaded linked-list, a single "sync" bit is +used to indicate that a modification is ongoing. If this sync flag is set, the +threaded linked-list will need to be checked for errors before it can be used +reliably. The exact cases to check for are described above in the tail tag. + +Layout of the move state: + +``` + tag data +[-- 32 --][-- 32 --|-- 32 --|-- 32 --] +[1|- 11 -| 10 | 10 ][1|- 11 -| 10 | 10 |--- 64 ---] + ^ ^ ^ ^ ^ ^ ^ ^- padding (0) ^- metadata pair + | | | | | | '------ move id + | | | | | '------------ move type + | | | | '----------------- sync bit + | | | | + | | | '- size (12) + | | '------ id (0x3ff) + | '------------ type (0x7ff) + '----------------- valid bit +``` + +Move state fields: + +1. **Sync bit (1-bit)** - Indicates if the metadata pair threaded linked-list + is in-sync. If set, the threaded linked-list should be checked for errors. + +2. **Move type (11-bits)** - Type of move being performed. Must be either + `0x000`, indicating no move, or `0x4ff` indicating the source file should + be deleted. + +3. **Move id (10-bits)** - The file id being moved. + +4. **Metadata pair (8-bytes)** - Pointer to the metadata-pair containing + the move. + +--- +#### `0x5xx` LFS_TYPE_CRC + +Last but not least, the CRC tag marks the end of a commit and provides a +checksum for any commits to the metadata block. + +The first 32-bits of the data contain a CRC-32 with a polynomial of +`0x04c11db7` initialized with `0xffffffff`. This CRC provides a checksum for +all metadata since the previous CRC tag, including the CRC tag itself. For +the first commit, this includes the revision count for the metadata block. + +However, the size of the data is not limited to 32-bits. The data field may +larger to pad the commit to the next program-aligned boundary. + +In addition, the CRC tag's chunk field contains a set of flags which can +change the behaviour of commits. Currently the only flag in use is the lowest +bit, which determines the expected state of the valid bit for any following +tags. This is used to guarantee that unwritten storage in a metadata block +will be detected as invalid. + +Layout of the CRC tag: + +``` + tag data +[-- 32 --][-- 32 --|--- variable length ---] +[1| 3| 8 | 10 | 10 ][-- 32 --|--- (size * 8 - 32) ---] + ^ ^ ^ ^ ^ ^- crc ^- padding + | | | | '- size + | | | '------ id (0x3ff) + | | '----------- valid state + | '-------------- type1 (0x5) + '----------------- valid bit +``` + +CRC fields: + +1. **Valid state (1-bit)** - Indicates the expected value of the valid bit for + any tags in the next commit. + +2. **CRC (32-bits)** - CRC-32 with a polynomial of `0x04c11db7` initialized + with `0xffffffff`. + +3. **Padding** - Padding to the next program-aligned boundary. No guarantees + are made about the contents. + +--- diff --git a/Firmware_V3/lib/LittleFS/littlefs/lfs.c b/Firmware_V3/lib/LittleFS/littlefs/lfs.c new file mode 100644 index 0000000..b1f445e --- /dev/null +++ b/Firmware_V3/lib/LittleFS/littlefs/lfs.c @@ -0,0 +1,4914 @@ +/* + * The little filesystem + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "lfs.h" +#include "lfs_util.h" +#include "wiring.h" + +#define LFS_BLOCK_NULL ((lfs_block_t)-1) +#define LFS_BLOCK_INLINE ((lfs_block_t)-2) + +/// Caching block device operations /// +static inline void lfs_cache_drop(lfs_t *lfs, lfs_cache_t *rcache) { + // do not zero, cheaper if cache is readonly or only going to be + // written with identical data (during relocates) + (void)lfs; + rcache->block = LFS_BLOCK_NULL; +} + +static inline void lfs_cache_zero(lfs_t *lfs, lfs_cache_t *pcache) { + // zero to avoid information leak + memset(pcache->buffer, 0xff, lfs->cfg->cache_size); + pcache->block = LFS_BLOCK_NULL; +} + +static int lfs_bd_read(lfs_t *lfs, + const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint, + lfs_block_t block, lfs_off_t off, + void *buffer, lfs_size_t size) { + uint8_t *data = buffer; + if (block >= lfs->cfg->block_count || + off+size > lfs->cfg->block_size) { + return LFS_ERR_CORRUPT; + } + + while (size > 0) { + lfs_size_t diff = size; + + if (pcache && block == pcache->block && + off < pcache->off + pcache->size) { + if (off >= pcache->off) { + // is already in pcache? + diff = lfs_min(diff, pcache->size - (off-pcache->off)); + memcpy(data, &pcache->buffer[off-pcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + // pcache takes priority + diff = lfs_min(diff, pcache->off-off); + } + + if (block == rcache->block && + off < rcache->off + rcache->size) { + if (off >= rcache->off) { + // is already in rcache? + diff = lfs_min(diff, rcache->size - (off-rcache->off)); + memcpy(data, &rcache->buffer[off-rcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + // rcache takes priority + diff = lfs_min(diff, rcache->off-off); + } + + if (size >= hint && off % lfs->cfg->read_size == 0 && + size >= lfs->cfg->read_size) { + // bypass cache? + diff = lfs_aligndown(diff, lfs->cfg->read_size); + int err = lfs->cfg->read(lfs->cfg, block, off, data, diff); + if (err) { + return err; + } + + data += diff; + off += diff; + size -= diff; + continue; + } + + // load to cache, first condition can no longer fail + LFS_ASSERT(block < lfs->cfg->block_count); + rcache->block = block; + rcache->off = lfs_aligndown(off, lfs->cfg->read_size); + rcache->size = lfs_min( + lfs_min( + lfs_alignup(off+hint, lfs->cfg->read_size), + lfs->cfg->block_size) + - rcache->off, + lfs->cfg->cache_size); + int err = lfs->cfg->read(lfs->cfg, rcache->block, + rcache->off, rcache->buffer, rcache->size); + LFS_ASSERT(err <= 0); + if (err) { + return err; + } + } + + return 0; +} + +enum { + LFS_CMP_EQ = 0, + LFS_CMP_LT = 1, + LFS_CMP_GT = 2, +}; + +static int lfs_bd_cmp(lfs_t *lfs, + const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint, + lfs_block_t block, lfs_off_t off, + const void *buffer, lfs_size_t size) { + const uint8_t *data = buffer; + + for (lfs_off_t i = 0; i < size; i++) { + uint8_t dat; + int err = lfs_bd_read(lfs, + pcache, rcache, hint-i, + block, off+i, &dat, 1); + if (err) { + return err; + } + + if (dat != data[i]) { + return (dat < data[i]) ? LFS_CMP_LT : LFS_CMP_GT; + } + } + + return LFS_CMP_EQ; +} + +static int lfs_bd_flush(lfs_t *lfs, + lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) { + if (pcache->block != LFS_BLOCK_NULL && pcache->block != LFS_BLOCK_INLINE) { + LFS_ASSERT(pcache->block < lfs->cfg->block_count); + lfs_size_t diff = lfs_alignup(pcache->size, lfs->cfg->prog_size); + int err = lfs->cfg->prog(lfs->cfg, pcache->block, + pcache->off, pcache->buffer, diff); + LFS_ASSERT(err <= 0); + if (err) { + return err; + } + + if (validate) { + // check data on disk + lfs_cache_drop(lfs, rcache); + int res = lfs_bd_cmp(lfs, + NULL, rcache, diff, + pcache->block, pcache->off, pcache->buffer, diff); + if (res < 0) { + return res; + } + + if (res != LFS_CMP_EQ) { + return LFS_ERR_CORRUPT; + } + } + + lfs_cache_zero(lfs, pcache); + } + + return 0; +} + +static int lfs_bd_sync(lfs_t *lfs, + lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) { + lfs_cache_drop(lfs, rcache); + + int err = lfs_bd_flush(lfs, pcache, rcache, validate); + if (err) { + return err; + } + + err = lfs->cfg->sync(lfs->cfg); + LFS_ASSERT(err <= 0); + return err; +} + +static int lfs_bd_prog(lfs_t *lfs, + lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate, + lfs_block_t block, lfs_off_t off, + const void *buffer, lfs_size_t size) { + const uint8_t *data = buffer; + LFS_ASSERT(block == LFS_BLOCK_INLINE || block < lfs->cfg->block_count); + LFS_ASSERT(off + size <= lfs->cfg->block_size); + + while (size > 0) { + if (block == pcache->block && + off >= pcache->off && + off < pcache->off + lfs->cfg->cache_size) { + // already fits in pcache? + lfs_size_t diff = lfs_min(size, + lfs->cfg->cache_size - (off-pcache->off)); + memcpy(&pcache->buffer[off-pcache->off], data, diff); + + data += diff; + off += diff; + size -= diff; + + pcache->size = lfs_max(pcache->size, off - pcache->off); + if (pcache->size == lfs->cfg->cache_size) { + // eagerly flush out pcache if we fill up + int err = lfs_bd_flush(lfs, pcache, rcache, validate); + if (err) { + return err; + } + } + + continue; + } + + // pcache must have been flushed, either by programming and + // entire block or manually flushing the pcache + LFS_ASSERT(pcache->block == LFS_BLOCK_NULL); + + // prepare pcache, first condition can no longer fail + pcache->block = block; + pcache->off = lfs_aligndown(off, lfs->cfg->prog_size); + pcache->size = 0; + } + + return 0; +} + +static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) { + LFS_ASSERT(block < lfs->cfg->block_count); + int err = lfs->cfg->erase(lfs->cfg, block); + LFS_ASSERT(err <= 0); + return err; +} + + +/// Small type-level utilities /// +// operations on block pairs +static inline void lfs_pair_swap(lfs_block_t pair[2]) { + lfs_block_t t = pair[0]; + pair[0] = pair[1]; + pair[1] = t; +} + +static inline bool lfs_pair_isnull(const lfs_block_t pair[2]) { + return pair[0] == LFS_BLOCK_NULL || pair[1] == LFS_BLOCK_NULL; +} + +static inline int lfs_pair_cmp( + const lfs_block_t paira[2], + const lfs_block_t pairb[2]) { + return !(paira[0] == pairb[0] || paira[1] == pairb[1] || + paira[0] == pairb[1] || paira[1] == pairb[0]); +} + +static inline bool lfs_pair_sync( + const lfs_block_t paira[2], + const lfs_block_t pairb[2]) { + return (paira[0] == pairb[0] && paira[1] == pairb[1]) || + (paira[0] == pairb[1] && paira[1] == pairb[0]); +} + +static inline void lfs_pair_fromle32(lfs_block_t pair[2]) { + pair[0] = lfs_fromle32(pair[0]); + pair[1] = lfs_fromle32(pair[1]); +} + +static inline void lfs_pair_tole32(lfs_block_t pair[2]) { + pair[0] = lfs_tole32(pair[0]); + pair[1] = lfs_tole32(pair[1]); +} + +// operations on 32-bit entry tags +typedef uint32_t lfs_tag_t; +typedef int32_t lfs_stag_t; + +#define LFS_MKTAG(type, id, size) \ + (((lfs_tag_t)(type) << 20) | ((lfs_tag_t)(id) << 10) | (lfs_tag_t)(size)) + +#define LFS_MKTAG_IF(cond, type, id, size) \ + ((cond) ? LFS_MKTAG(type, id, size) : LFS_MKTAG(LFS_FROM_NOOP, 0, 0)) + +#define LFS_MKTAG_IF_ELSE(cond, type1, id1, size1, type2, id2, size2) \ + ((cond) ? LFS_MKTAG(type1, id1, size1) : LFS_MKTAG(type2, id2, size2)) + +static inline bool lfs_tag_isvalid(lfs_tag_t tag) { + return !(tag & 0x80000000); +} + +static inline bool lfs_tag_isdelete(lfs_tag_t tag) { + return ((int32_t)(tag << 22) >> 22) == -1; +} + +static inline uint16_t lfs_tag_type1(lfs_tag_t tag) { + return (tag & 0x70000000) >> 20; +} + +static inline uint16_t lfs_tag_type3(lfs_tag_t tag) { + return (tag & 0x7ff00000) >> 20; +} + +static inline uint8_t lfs_tag_chunk(lfs_tag_t tag) { + return (tag & 0x0ff00000) >> 20; +} + +static inline int8_t lfs_tag_splice(lfs_tag_t tag) { + return (int8_t)lfs_tag_chunk(tag); +} + +static inline uint16_t lfs_tag_id(lfs_tag_t tag) { + return (tag & 0x000ffc00) >> 10; +} + +static inline lfs_size_t lfs_tag_size(lfs_tag_t tag) { + return tag & 0x000003ff; +} + +static inline lfs_size_t lfs_tag_dsize(lfs_tag_t tag) { + return sizeof(tag) + lfs_tag_size(tag + lfs_tag_isdelete(tag)); +} + +// operations on attributes in attribute lists +struct lfs_mattr { + lfs_tag_t tag; + const void *buffer; +}; + +struct lfs_diskoff { + lfs_block_t block; + lfs_off_t off; +}; + +#define LFS_MKATTRS(...) \ + (struct lfs_mattr[]){__VA_ARGS__}, \ + sizeof((struct lfs_mattr[]){__VA_ARGS__}) / sizeof(struct lfs_mattr) + +// operations on global state +static inline void lfs_gstate_xor(lfs_gstate_t *a, const lfs_gstate_t *b) { + for (int i = 0; i < 3; i++) { + ((uint32_t*)a)[i] ^= ((const uint32_t*)b)[i]; + } +} + +static inline bool lfs_gstate_iszero(const lfs_gstate_t *a) { + for (int i = 0; i < 3; i++) { + if (((uint32_t*)a)[i] != 0) { + return false; + } + } + return true; +} + +static inline bool lfs_gstate_hasorphans(const lfs_gstate_t *a) { + return lfs_tag_size(a->tag); +} + +static inline uint8_t lfs_gstate_getorphans(const lfs_gstate_t *a) { + return lfs_tag_size(a->tag); +} + +static inline bool lfs_gstate_hasmove(const lfs_gstate_t *a) { + return lfs_tag_type1(a->tag); +} + +static inline bool lfs_gstate_hasmovehere(const lfs_gstate_t *a, + const lfs_block_t *pair) { + return lfs_tag_type1(a->tag) && lfs_pair_cmp(a->pair, pair) == 0; +} + +static inline void lfs_gstate_fromle32(lfs_gstate_t *a) { + a->tag = lfs_fromle32(a->tag); + a->pair[0] = lfs_fromle32(a->pair[0]); + a->pair[1] = lfs_fromle32(a->pair[1]); +} + +static inline void lfs_gstate_tole32(lfs_gstate_t *a) { + a->tag = lfs_tole32(a->tag); + a->pair[0] = lfs_tole32(a->pair[0]); + a->pair[1] = lfs_tole32(a->pair[1]); +} + +// other endianness operations +static void lfs_ctz_fromle32(struct lfs_ctz *ctz) { + ctz->head = lfs_fromle32(ctz->head); + ctz->size = lfs_fromle32(ctz->size); +} + +static void lfs_ctz_tole32(struct lfs_ctz *ctz) { + ctz->head = lfs_tole32(ctz->head); + ctz->size = lfs_tole32(ctz->size); +} + +static inline void lfs_superblock_fromle32(lfs_superblock_t *superblock) { + superblock->version = lfs_fromle32(superblock->version); + superblock->block_size = lfs_fromle32(superblock->block_size); + superblock->block_count = lfs_fromle32(superblock->block_count); + superblock->name_max = lfs_fromle32(superblock->name_max); + superblock->file_max = lfs_fromle32(superblock->file_max); + superblock->attr_max = lfs_fromle32(superblock->attr_max); +} + +static inline void lfs_superblock_tole32(lfs_superblock_t *superblock) { + superblock->version = lfs_tole32(superblock->version); + superblock->block_size = lfs_tole32(superblock->block_size); + superblock->block_count = lfs_tole32(superblock->block_count); + superblock->name_max = lfs_tole32(superblock->name_max); + superblock->file_max = lfs_tole32(superblock->file_max); + superblock->attr_max = lfs_tole32(superblock->attr_max); +} + + +/// Internal operations predeclared here /// +static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, + const struct lfs_mattr *attrs, int attrcount); +static int lfs_dir_compact(lfs_t *lfs, + lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount, + lfs_mdir_t *source, uint16_t begin, uint16_t end); +static int lfs_file_outline(lfs_t *lfs, lfs_file_t *file); +static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file); +static void lfs_fs_preporphans(lfs_t *lfs, int8_t orphans); +static void lfs_fs_prepmove(lfs_t *lfs, + uint16_t id, const lfs_block_t pair[2]); +static int lfs_fs_pred(lfs_t *lfs, const lfs_block_t dir[2], + lfs_mdir_t *pdir); +static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t dir[2], + lfs_mdir_t *parent); +static int lfs_fs_relocate(lfs_t *lfs, + const lfs_block_t oldpair[2], lfs_block_t newpair[2]); +int lfs_fs_traverseraw(lfs_t *lfs, + int (*cb)(void *data, lfs_block_t block), void *data, + bool includeorphans); +static int lfs_fs_forceconsistency(lfs_t *lfs); +static int lfs_deinit(lfs_t *lfs); +#ifdef LFS_MIGRATE +static int lfs1_traverse(lfs_t *lfs, + int (*cb)(void*, lfs_block_t), void *data); +#endif + +/// Block allocator /// +static int lfs_alloc_lookahead(void *p, lfs_block_t block) { + lfs_t *lfs = (lfs_t*)p; + lfs_block_t off = ((block - lfs->free.off) + + lfs->cfg->block_count) % lfs->cfg->block_count; + + if (off < lfs->free.size) { + lfs->free.buffer[off / 32] |= 1U << (off % 32); + } + + return 0; +} + +static void lfs_alloc_ack(lfs_t *lfs) { + lfs->free.ack = lfs->cfg->block_count; +} + +// Invalidate the lookahead buffer. This is done during mounting and +// failed traversals +static void lfs_alloc_reset(lfs_t *lfs) { + lfs->free.off = lfs->seed % lfs->cfg->block_size; + lfs->free.size = 0; + lfs->free.i = 0; + lfs_alloc_ack(lfs); +} + +static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { + while (true) { + while (lfs->free.i != lfs->free.size) { + lfs_block_t off = lfs->free.i; + lfs->free.i += 1; + lfs->free.ack -= 1; + + if (!(lfs->free.buffer[off / 32] & (1U << (off % 32)))) { + // found a free block + *block = (lfs->free.off + off) % lfs->cfg->block_count; + + // eagerly find next off so an alloc ack can + // discredit old lookahead blocks + while (lfs->free.i != lfs->free.size && + (lfs->free.buffer[lfs->free.i / 32] + & (1U << (lfs->free.i % 32)))) { + lfs->free.i += 1; + lfs->free.ack -= 1; + } + + return 0; + } + } + + // check if we have looked at all blocks since last ack + if (lfs->free.ack == 0) { + LFS_ERROR("No more free space %"PRIu32, + lfs->free.i + lfs->free.off); + return LFS_ERR_NOSPC; + } + + lfs->free.off = (lfs->free.off + lfs->free.size) + % lfs->cfg->block_count; + lfs->free.size = lfs_min(8*lfs->cfg->lookahead_size, lfs->free.ack); + lfs->free.i = 0; + + // find mask of free blocks from tree + memset(lfs->free.buffer, 0, lfs->cfg->lookahead_size); + int err = lfs_fs_traverseraw(lfs, lfs_alloc_lookahead, lfs, true); + if (err) { + lfs_alloc_reset(lfs); + return err; + } + } +} + +/// Metadata pair and directory operations /// +static lfs_stag_t lfs_dir_getslice(lfs_t *lfs, const lfs_mdir_t *dir, + lfs_tag_t gmask, lfs_tag_t gtag, + lfs_off_t goff, void *gbuffer, lfs_size_t gsize) { + lfs_off_t off = dir->off; + lfs_tag_t ntag = dir->etag; + lfs_stag_t gdiff = 0; + + if (lfs_gstate_hasmovehere(&lfs->gdisk, dir->pair) && + lfs_tag_id(gmask) != 0 && + lfs_tag_id(lfs->gdisk.tag) <= lfs_tag_id(gtag)) { + // synthetic moves + gdiff -= LFS_MKTAG(0, 1, 0); + } + + // iterate over dir block backwards (for faster lookups) + while (off >= sizeof(lfs_tag_t) + lfs_tag_dsize(ntag)) { + off -= lfs_tag_dsize(ntag); + lfs_tag_t tag = ntag; + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, sizeof(ntag), + dir->pair[0], off, &ntag, sizeof(ntag)); + if (err) { + return err; + } + + ntag = (lfs_frombe32(ntag) ^ tag) & 0x7fffffff; + + if (lfs_tag_id(gmask) != 0 && + lfs_tag_type1(tag) == LFS_TYPE_SPLICE && + lfs_tag_id(tag) <= lfs_tag_id(gtag - gdiff)) { + if (tag == (LFS_MKTAG(LFS_TYPE_CREATE, 0, 0) | + (LFS_MKTAG(0, 0x3ff, 0) & (gtag - gdiff)))) { + // found where we were created + return LFS_ERR_NOENT; + } + + // move around splices + gdiff += LFS_MKTAG(0, lfs_tag_splice(tag), 0); + } + + if ((gmask & tag) == (gmask & (gtag - gdiff))) { + if (lfs_tag_isdelete(tag)) { + return LFS_ERR_NOENT; + } + + lfs_size_t diff = lfs_min(lfs_tag_size(tag), gsize); + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, diff, + dir->pair[0], off+sizeof(tag)+goff, gbuffer, diff); + if (err) { + return err; + } + + memset((uint8_t*)gbuffer + diff, 0, gsize - diff); + + return tag + gdiff; + } + } + + return LFS_ERR_NOENT; +} + +static lfs_stag_t lfs_dir_get(lfs_t *lfs, const lfs_mdir_t *dir, + lfs_tag_t gmask, lfs_tag_t gtag, void *buffer) { + return lfs_dir_getslice(lfs, dir, + gmask, gtag, + 0, buffer, lfs_tag_size(gtag)); +} + +static int lfs_dir_getread(lfs_t *lfs, const lfs_mdir_t *dir, + const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint, + lfs_tag_t gmask, lfs_tag_t gtag, + lfs_off_t off, void *buffer, lfs_size_t size) { + uint8_t *data = buffer; + if (off+size > lfs->cfg->block_size) { + return LFS_ERR_CORRUPT; + } + + while (size > 0) { + lfs_size_t diff = size; + + if (pcache && pcache->block == LFS_BLOCK_INLINE && + off < pcache->off + pcache->size) { + if (off >= pcache->off) { + // is already in pcache? + diff = lfs_min(diff, pcache->size - (off-pcache->off)); + memcpy(data, &pcache->buffer[off-pcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + // pcache takes priority + diff = lfs_min(diff, pcache->off-off); + } + + if (rcache->block == LFS_BLOCK_INLINE && + off < rcache->off + rcache->size) { + if (off >= rcache->off) { + // is already in rcache? + diff = lfs_min(diff, rcache->size - (off-rcache->off)); + memcpy(data, &rcache->buffer[off-rcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + // rcache takes priority + diff = lfs_min(diff, rcache->off-off); + } + + // load to cache, first condition can no longer fail + rcache->block = LFS_BLOCK_INLINE; + rcache->off = lfs_aligndown(off, lfs->cfg->read_size); + rcache->size = lfs_min(lfs_alignup(off+hint, lfs->cfg->read_size), + lfs->cfg->cache_size); + int err = lfs_dir_getslice(lfs, dir, gmask, gtag, + rcache->off, rcache->buffer, rcache->size); + if (err < 0) { + return err; + } + } + + return 0; +} + +static int lfs_dir_traverse_filter(void *p, + lfs_tag_t tag, const void *buffer) { + lfs_tag_t *filtertag = p; + (void)buffer; + + // which mask depends on unique bit in tag structure + uint32_t mask = (tag & LFS_MKTAG(0x100, 0, 0)) + ? LFS_MKTAG(0x7ff, 0x3ff, 0) + : LFS_MKTAG(0x700, 0x3ff, 0); + + // check for redundancy + if ((mask & tag) == (mask & *filtertag) || + lfs_tag_isdelete(*filtertag) || + (LFS_MKTAG(0x7ff, 0x3ff, 0) & tag) == ( + LFS_MKTAG(LFS_TYPE_DELETE, 0, 0) | + (LFS_MKTAG(0, 0x3ff, 0) & *filtertag))) { + return true; + } + + // check if we need to adjust for created/deleted tags + if (lfs_tag_type1(tag) == LFS_TYPE_SPLICE && + lfs_tag_id(tag) <= lfs_tag_id(*filtertag)) { + *filtertag += LFS_MKTAG(0, lfs_tag_splice(tag), 0); + } + + return false; +} + +static int lfs_dir_traverse(lfs_t *lfs, + const lfs_mdir_t *dir, lfs_off_t off, lfs_tag_t ptag, + const struct lfs_mattr *attrs, int attrcount, + lfs_tag_t tmask, lfs_tag_t ttag, + uint16_t begin, uint16_t end, int16_t diff, + int (*cb)(void *data, lfs_tag_t tag, const void *buffer), void *data) { + // iterate over directory and attrs + while (true) { + lfs_tag_t tag; + const void *buffer; + struct lfs_diskoff disk; + if (off+lfs_tag_dsize(ptag) < dir->off) { + off += lfs_tag_dsize(ptag); + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, sizeof(tag), + dir->pair[0], off, &tag, sizeof(tag)); + if (err) { + return err; + } + + tag = (lfs_frombe32(tag) ^ ptag) | 0x80000000; + disk.block = dir->pair[0]; + disk.off = off+sizeof(lfs_tag_t); + buffer = &disk; + ptag = tag; + } else if (attrcount > 0) { + tag = attrs[0].tag; + buffer = attrs[0].buffer; + attrs += 1; + attrcount -= 1; + } else { + return 0; + } + + lfs_tag_t mask = LFS_MKTAG(0x7ff, 0, 0); + if ((mask & tmask & tag) != (mask & tmask & ttag)) { + continue; + } + + // do we need to filter? inlining the filtering logic here allows + // for some minor optimizations + if (lfs_tag_id(tmask) != 0) { + // scan for duplicates and update tag based on creates/deletes + int filter = lfs_dir_traverse(lfs, + dir, off, ptag, attrs, attrcount, + 0, 0, 0, 0, 0, + lfs_dir_traverse_filter, &tag); + if (filter < 0) { + return filter; + } + + if (filter) { + continue; + } + + // in filter range? + if (!(lfs_tag_id(tag) >= begin && lfs_tag_id(tag) < end)) { + continue; + } + } + + // handle special cases for mcu-side operations + if (lfs_tag_type3(tag) == LFS_FROM_NOOP) { + // do nothing + } else if (lfs_tag_type3(tag) == LFS_FROM_MOVE) { + uint16_t fromid = lfs_tag_size(tag); + uint16_t toid = lfs_tag_id(tag); + int err = lfs_dir_traverse(lfs, + buffer, 0, 0xffffffff, NULL, 0, + LFS_MKTAG(0x600, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, 0, 0), + fromid, fromid+1, toid-fromid+diff, + cb, data); + if (err) { + return err; + } + } else if (lfs_tag_type3(tag) == LFS_FROM_USERATTRS) { + for (unsigned i = 0; i < lfs_tag_size(tag); i++) { + const struct lfs_attr *a = buffer; + int err = cb(data, LFS_MKTAG(LFS_TYPE_USERATTR + a[i].type, + lfs_tag_id(tag) + diff, a[i].size), a[i].buffer); + if (err) { + return err; + } + } + } else { + int err = cb(data, tag + LFS_MKTAG(0, diff, 0), buffer); + if (err) { + return err; + } + } + } +} + +static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, + lfs_mdir_t *dir, const lfs_block_t pair[2], + lfs_tag_t fmask, lfs_tag_t ftag, uint16_t *id, + int (*cb)(void *data, lfs_tag_t tag, const void *buffer), void *data) { + // we can find tag very efficiently during a fetch, since we're already + // scanning the entire directory + lfs_stag_t besttag = -1; + + // if either block address is invalid we return LFS_ERR_CORRUPT here, + // otherwise later writes to the pair could fail + if (pair[0] >= lfs->cfg->block_count || pair[1] >= lfs->cfg->block_count) { + return LFS_ERR_CORRUPT; + } + + // find the block with the most recent revision + uint32_t revs[2] = {0, 0}; + int r = 0; + for (int i = 0; i < 2; i++) { + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, sizeof(revs[i]), + pair[i], 0, &revs[i], sizeof(revs[i])); + revs[i] = lfs_fromle32(revs[i]); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + + if (err != LFS_ERR_CORRUPT && + lfs_scmp(revs[i], revs[(i+1)%2]) > 0) { + r = i; + } + } + + dir->pair[0] = pair[(r+0)%2]; + dir->pair[1] = pair[(r+1)%2]; + dir->rev = revs[(r+0)%2]; + dir->off = 0; // nonzero = found some commits + + // now scan tags to fetch the actual dir and find possible match + for (int i = 0; i < 2; i++) { + lfs_off_t off = 0; + lfs_tag_t ptag = 0xffffffff; + + uint16_t tempcount = 0; + lfs_block_t temptail[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}; + bool tempsplit = false; + lfs_stag_t tempbesttag = besttag; + + dir->rev = lfs_tole32(dir->rev); + uint32_t crc = lfs_crc(0xffffffff, &dir->rev, sizeof(dir->rev)); + dir->rev = lfs_fromle32(dir->rev); + + while (true) { + // extract next tag + lfs_tag_t tag; + off += lfs_tag_dsize(ptag); + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], off, &tag, sizeof(tag)); + if (err) { + if (err == LFS_ERR_CORRUPT) { + // can't continue? + dir->erased = false; + break; + } + return err; + } + + crc = lfs_crc(crc, &tag, sizeof(tag)); + tag = lfs_frombe32(tag) ^ ptag; + + // next commit not yet programmed or we're not in valid range + if (!lfs_tag_isvalid(tag)) { + dir->erased = (lfs_tag_type1(ptag) == LFS_TYPE_CRC && + dir->off % lfs->cfg->prog_size == 0); + break; + } else if (off + lfs_tag_dsize(tag) > lfs->cfg->block_size) { + dir->erased = false; + break; + } + + ptag = tag; + + if (lfs_tag_type1(tag) == LFS_TYPE_CRC) { + // check the crc attr + uint32_t dcrc; + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], off+sizeof(tag), &dcrc, sizeof(dcrc)); + if (err) { + if (err == LFS_ERR_CORRUPT) { + dir->erased = false; + break; + } + return err; + } + dcrc = lfs_fromle32(dcrc); + + if (crc != dcrc) { + dir->erased = false; + break; + } + + // reset the next bit if we need to + ptag ^= (lfs_tag_t)(lfs_tag_chunk(tag) & 1U) << 31; + + // toss our crc into the filesystem seed for + // pseudorandom numbers + lfs->seed ^= crc; + + // update with what's found so far + besttag = tempbesttag; + dir->off = off + lfs_tag_dsize(tag); + dir->etag = ptag; + dir->count = tempcount; + dir->tail[0] = temptail[0]; + dir->tail[1] = temptail[1]; + dir->split = tempsplit; + + // reset crc + crc = 0xffffffff; + continue; + } + + // crc the entry first, hopefully leaving it in the cache + for (lfs_off_t j = sizeof(tag); j < lfs_tag_dsize(tag); j++) { + uint8_t dat; + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], off+j, &dat, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + dir->erased = false; + break; + } + return err; + } + + crc = lfs_crc(crc, &dat, 1); + } + + // directory modification tags? + if (lfs_tag_type1(tag) == LFS_TYPE_NAME) { + // increase count of files if necessary + if (lfs_tag_id(tag) >= tempcount) { + tempcount = lfs_tag_id(tag) + 1; + } + } else if (lfs_tag_type1(tag) == LFS_TYPE_SPLICE) { + tempcount += lfs_tag_splice(tag); + + if (tag == (LFS_MKTAG(LFS_TYPE_DELETE, 0, 0) | + (LFS_MKTAG(0, 0x3ff, 0) & tempbesttag))) { + tempbesttag |= 0x80000000; + } else if (tempbesttag != -1 && + lfs_tag_id(tag) <= lfs_tag_id(tempbesttag)) { + tempbesttag += LFS_MKTAG(0, lfs_tag_splice(tag), 0); + } + } else if (lfs_tag_type1(tag) == LFS_TYPE_TAIL) { + tempsplit = (lfs_tag_chunk(tag) & 1); + + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], off+sizeof(tag), &temptail, 8); + if (err) { + if (err == LFS_ERR_CORRUPT) { + dir->erased = false; + break; + } + } + lfs_pair_fromle32(temptail); + } + + // found a match for our fetcher? + if ((fmask & tag) == (fmask & ftag)) { + int res = cb(data, tag, &(struct lfs_diskoff){ + dir->pair[0], off+sizeof(tag)}); + if (res < 0) { + if (res == LFS_ERR_CORRUPT) { + dir->erased = false; + break; + } + return res; + } + + if (res == LFS_CMP_EQ) { + // found a match + tempbesttag = tag; + } else if ((LFS_MKTAG(0x7ff, 0x3ff, 0) & tag) == + (LFS_MKTAG(0x7ff, 0x3ff, 0) & tempbesttag)) { + // found an identical tag, but contents didn't match + // this must mean that our besttag has been overwritten + tempbesttag = -1; + } else if (res == LFS_CMP_GT && + lfs_tag_id(tag) <= lfs_tag_id(tempbesttag)) { + // found a greater match, keep track to keep things sorted + tempbesttag = tag | 0x80000000; + } + } + } + + // consider what we have good enough + if (dir->off > 0) { + // synthetic move + if (lfs_gstate_hasmovehere(&lfs->gdisk, dir->pair)) { + if (lfs_tag_id(lfs->gdisk.tag) == lfs_tag_id(besttag)) { + besttag |= 0x80000000; + } else if (besttag != -1 && + lfs_tag_id(lfs->gdisk.tag) < lfs_tag_id(besttag)) { + besttag -= LFS_MKTAG(0, 1, 0); + } + } + + // found tag? or found best id? + if (id) { + *id = lfs_min(lfs_tag_id(besttag), dir->count); + } + + if (lfs_tag_isvalid(besttag)) { + return besttag; + } else if (lfs_tag_id(besttag) < dir->count) { + return LFS_ERR_NOENT; + } else { + return 0; + } + } + + // failed, try the other block? + lfs_pair_swap(dir->pair); + dir->rev = revs[(r+1)%2]; + } + + LFS_ERROR("Corrupted dir pair at {0x%"PRIx32", 0x%"PRIx32"}", + dir->pair[0], dir->pair[1]); + return LFS_ERR_CORRUPT; +} + +static int lfs_dir_fetch(lfs_t *lfs, + lfs_mdir_t *dir, const lfs_block_t pair[2]) { + // note, mask=-1, tag=-1 can never match a tag since this + // pattern has the invalid bit set + return (int)lfs_dir_fetchmatch(lfs, dir, pair, + (lfs_tag_t)-1, (lfs_tag_t)-1, NULL, NULL, NULL); +} + +static int lfs_dir_getgstate(lfs_t *lfs, const lfs_mdir_t *dir, + lfs_gstate_t *gstate) { + lfs_gstate_t temp; + lfs_stag_t res = lfs_dir_get(lfs, dir, LFS_MKTAG(0x7ff, 0, 0), + LFS_MKTAG(LFS_TYPE_MOVESTATE, 0, sizeof(temp)), &temp); + if (res < 0 && res != LFS_ERR_NOENT) { + return res; + } + + if (res != LFS_ERR_NOENT) { + // xor together to find resulting gstate + lfs_gstate_fromle32(&temp); + lfs_gstate_xor(gstate, &temp); + } + + return 0; +} + +static int lfs_dir_getinfo(lfs_t *lfs, lfs_mdir_t *dir, + uint16_t id, struct lfs_info *info) { + if (id == 0x3ff) { + // special case for root + strcpy(info->name, "/"); + info->type = LFS_TYPE_DIR; + return 0; + } + + lfs_stag_t tag = lfs_dir_get(lfs, dir, LFS_MKTAG(0x780, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_NAME, id, lfs->name_max+1), info->name); + if (tag < 0) { + return (int)tag; + } + + info->type = lfs_tag_type3(tag); + + struct lfs_ctz ctz; + tag = lfs_dir_get(lfs, dir, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, id, sizeof(ctz)), &ctz); + if (tag < 0) { + return (int)tag; + } + lfs_ctz_fromle32(&ctz); + + if (lfs_tag_type3(tag) == LFS_TYPE_CTZSTRUCT) { + info->size = ctz.size; + } else if (lfs_tag_type3(tag) == LFS_TYPE_INLINESTRUCT) { + info->size = lfs_tag_size(tag); + } + + return 0; +} + +struct lfs_dir_find_match { + lfs_t *lfs; + const void *name; + lfs_size_t size; +}; + +static int lfs_dir_find_match(void *data, + lfs_tag_t tag, const void *buffer) { + struct lfs_dir_find_match *name = data; + lfs_t *lfs = name->lfs; + const struct lfs_diskoff *disk = buffer; + + // compare with disk + lfs_size_t diff = lfs_min(name->size, lfs_tag_size(tag)); + int res = lfs_bd_cmp(lfs, + NULL, &lfs->rcache, diff, + disk->block, disk->off, name->name, diff); + if (res != LFS_CMP_EQ) { + return res; + } + + // only equal if our size is still the same + if (name->size != lfs_tag_size(tag)) { + return (name->size < lfs_tag_size(tag)) ? LFS_CMP_LT : LFS_CMP_GT; + } + + // found a match! + return LFS_CMP_EQ; +} + +static lfs_stag_t lfs_dir_find(lfs_t *lfs, lfs_mdir_t *dir, + const char **path, uint16_t *id) { + // we reduce path to a single name if we can find it + const char *name = *path; + if (id) { + *id = 0x3ff; + } + + // default to root dir + lfs_stag_t tag = LFS_MKTAG(LFS_TYPE_DIR, 0x3ff, 0); + dir->tail[0] = lfs->root[0]; + dir->tail[1] = lfs->root[1]; + + while (true) { +nextname: + // skip slashes + name += strspn(name, "/"); + lfs_size_t namelen = strcspn(name, "/"); + + // skip '.' and root '..' + if ((namelen == 1 && memcmp(name, ".", 1) == 0) || + (namelen == 2 && memcmp(name, "..", 2) == 0)) { + name += namelen; + goto nextname; + } + + // skip if matched by '..' in name + const char *suffix = name + namelen; + lfs_size_t sufflen; + int depth = 1; + while (true) { + suffix += strspn(suffix, "/"); + sufflen = strcspn(suffix, "/"); + if (sufflen == 0) { + break; + } + + if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) { + depth -= 1; + if (depth == 0) { + name = suffix + sufflen; + goto nextname; + } + } else { + depth += 1; + } + + suffix += sufflen; + } + + // found path + if (name[0] == '\0') { + return tag; + } + + // update what we've found so far + *path = name; + + // only continue if we hit a directory + if (lfs_tag_type3(tag) != LFS_TYPE_DIR) { + return LFS_ERR_NOTDIR; + } + + // grab the entry data + if (lfs_tag_id(tag) != 0x3ff) { + lfs_stag_t res = lfs_dir_get(lfs, dir, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), dir->tail); + if (res < 0) { + return res; + } + lfs_pair_fromle32(dir->tail); + } + + // find entry matching name + while (true) { + tag = lfs_dir_fetchmatch(lfs, dir, dir->tail, + LFS_MKTAG(0x780, 0, 0), + LFS_MKTAG(LFS_TYPE_NAME, 0, namelen), + // are we last name? + (strchr(name, '/') == NULL) ? id : NULL, + lfs_dir_find_match, &(struct lfs_dir_find_match){ + lfs, name, namelen}); + if (tag < 0) { + return tag; + } + + if (tag) { + break; + } + + if (!dir->split) { + return LFS_ERR_NOENT; + } + } + + // to next name + name += namelen; + } +} + +// commit logic +struct lfs_commit { + lfs_block_t block; + lfs_off_t off; + lfs_tag_t ptag; + uint32_t crc; + + lfs_off_t begin; + lfs_off_t end; +}; + +static int lfs_dir_commitprog(lfs_t *lfs, struct lfs_commit *commit, + const void *buffer, lfs_size_t size) { + int err = lfs_bd_prog(lfs, + &lfs->pcache, &lfs->rcache, false, + commit->block, commit->off , + (const uint8_t*)buffer, size); + if (err) { + return err; + } + + commit->crc = lfs_crc(commit->crc, buffer, size); + commit->off += size; + return 0; +} + +static int lfs_dir_commitattr(lfs_t *lfs, struct lfs_commit *commit, + lfs_tag_t tag, const void *buffer) { + // check if we fit + lfs_size_t dsize = lfs_tag_dsize(tag); + if (commit->off + dsize > commit->end) { + return LFS_ERR_NOSPC; + } + + // write out tag + lfs_tag_t ntag = lfs_tobe32((tag & 0x7fffffff) ^ commit->ptag); + int err = lfs_dir_commitprog(lfs, commit, &ntag, sizeof(ntag)); + if (err) { + return err; + } + + if (!(tag & 0x80000000)) { + // from memory + err = lfs_dir_commitprog(lfs, commit, buffer, dsize-sizeof(tag)); + if (err) { + return err; + } + } else { + // from disk + const struct lfs_diskoff *disk = buffer; + for (lfs_off_t i = 0; i < dsize-sizeof(tag); i++) { + // rely on caching to make this efficient + uint8_t dat; + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, dsize-sizeof(tag)-i, + disk->block, disk->off+i, &dat, 1); + if (err) { + return err; + } + + err = lfs_dir_commitprog(lfs, commit, &dat, 1); + if (err) { + return err; + } + } + } + + commit->ptag = tag & 0x7fffffff; + return 0; +} + +static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) { + const lfs_off_t off1 = commit->off; + const uint32_t crc1 = commit->crc; + // align to program units + const lfs_off_t end = lfs_alignup(off1 + 2*sizeof(uint32_t), + lfs->cfg->prog_size); + + // create crc tags to fill up remainder of commit, note that + // padding is not crced, which lets fetches skip padding but + // makes committing a bit more complicated + while (commit->off < end) { + lfs_off_t off = commit->off + sizeof(lfs_tag_t); + lfs_off_t noff = lfs_min(end - off, 0x3fe) + off; + if (noff < end) { + noff = lfs_min(noff, end - 2*sizeof(uint32_t)); + } + + // read erased state from next program unit + lfs_tag_t tag = 0xffffffff; + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, sizeof(tag), + commit->block, noff, &tag, sizeof(tag)); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + + // build crc tag + bool reset = ~lfs_frombe32(tag) >> 31; + tag = LFS_MKTAG(LFS_TYPE_CRC + reset, 0x3ff, noff - off); + + // write out crc + uint32_t footer[2]; + footer[0] = lfs_tobe32(tag ^ commit->ptag); + commit->crc = lfs_crc(commit->crc, &footer[0], sizeof(footer[0])); + footer[1] = lfs_tole32(commit->crc); + err = lfs_bd_prog(lfs, + &lfs->pcache, &lfs->rcache, false, + commit->block, commit->off, &footer, sizeof(footer)); + if (err) { + return err; + } + + commit->off += sizeof(tag)+lfs_tag_size(tag); + commit->ptag = tag ^ ((lfs_tag_t)reset << 31); + commit->crc = 0xffffffff; // reset crc for next "commit" + } + + // flush buffers + int err = lfs_bd_sync(lfs, &lfs->pcache, &lfs->rcache, false); + if (err) { + return err; + } + + // successful commit, check checksums to make sure + lfs_off_t off = commit->begin; + lfs_off_t noff = off1 + sizeof(uint32_t); + while (off < end) { + uint32_t crc = 0xffffffff; + for (lfs_off_t i = off; i < noff+sizeof(uint32_t); i++) { + // check against written crc, may catch blocks that + // become readonly and match our commit size exactly + if (i == off1 && crc != crc1) { + return LFS_ERR_CORRUPT; + } + + // leave it up to caching to make this efficient + uint8_t dat; + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, noff+sizeof(uint32_t)-i, + commit->block, i, &dat, 1); + if (err) { + return err; + } + + crc = lfs_crc(crc, &dat, 1); + } + + // detected write error? + if (crc != 0) { + return LFS_ERR_CORRUPT; + } + + // skip padding + off = lfs_min(end - noff, 0x3fe) + noff; + if (off < end) { + off = lfs_min(off, end - 2*sizeof(uint32_t)); + } + noff = off + sizeof(uint32_t); + } + + return 0; +} + +static int lfs_dir_alloc(lfs_t *lfs, lfs_mdir_t *dir) { + // allocate pair of dir blocks (backwards, so we write block 1 first) + for (int i = 0; i < 2; i++) { + int err = lfs_alloc(lfs, &dir->pair[(i+1)%2]); + if (err) { + return err; + } + } + + // zero for reproducability in case initial block is unreadable + dir->rev = 0; + + // rather than clobbering one of the blocks we just pretend + // the revision may be valid + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, sizeof(dir->rev), + dir->pair[0], 0, &dir->rev, sizeof(dir->rev)); + dir->rev = lfs_fromle32(dir->rev); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + + // make sure we don't immediately evict + dir->rev += dir->rev & 1; + + // set defaults + dir->off = sizeof(dir->rev); + dir->etag = 0xffffffff; + dir->count = 0; + dir->tail[0] = LFS_BLOCK_NULL; + dir->tail[1] = LFS_BLOCK_NULL; + dir->erased = false; + dir->split = false; + + // don't write out yet, let caller take care of that + return 0; +} + +static int lfs_dir_drop(lfs_t *lfs, lfs_mdir_t *dir, lfs_mdir_t *tail) { + // steal state + int err = lfs_dir_getgstate(lfs, tail, &lfs->gdelta); + if (err) { + return err; + } + + // steal tail + lfs_pair_tole32(tail->tail); + err = lfs_dir_commit(lfs, dir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_TAIL + tail->split, 0x3ff, 8), tail->tail})); + lfs_pair_fromle32(tail->tail); + if (err) { + return err; + } + + return 0; +} + +static int lfs_dir_split(lfs_t *lfs, + lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount, + lfs_mdir_t *source, uint16_t split, uint16_t end) { + // create tail directory + lfs_alloc_ack(lfs); + lfs_mdir_t tail; + int err = lfs_dir_alloc(lfs, &tail); + if (err) { + return err; + } + + tail.split = dir->split; + tail.tail[0] = dir->tail[0]; + tail.tail[1] = dir->tail[1]; + + err = lfs_dir_compact(lfs, &tail, attrs, attrcount, source, split, end); + if (err) { + return err; + } + + dir->tail[0] = tail.pair[0]; + dir->tail[1] = tail.pair[1]; + dir->split = true; + + // update root if needed + if (lfs_pair_cmp(dir->pair, lfs->root) == 0 && split == 0) { + lfs->root[0] = tail.pair[0]; + lfs->root[1] = tail.pair[1]; + } + + return 0; +} + +static int lfs_dir_commit_size(void *p, lfs_tag_t tag, const void *buffer) { + lfs_size_t *size = p; + (void)buffer; + + *size += lfs_tag_dsize(tag); + return 0; +} + +struct lfs_dir_commit_commit { + lfs_t *lfs; + struct lfs_commit *commit; +}; + +static int lfs_dir_commit_commit(void *p, lfs_tag_t tag, const void *buffer) { + struct lfs_dir_commit_commit *commit = p; + return lfs_dir_commitattr(commit->lfs, commit->commit, tag, buffer); +} + +static int lfs_dir_compact(lfs_t *lfs, + lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount, + lfs_mdir_t *source, uint16_t begin, uint16_t end) { + // save some state in case block is bad + const lfs_block_t oldpair[2] = {dir->pair[0], dir->pair[1]}; + bool relocated = false; + bool tired = false; + + // should we split? + while (end - begin > 1) { + // find size + lfs_size_t size = 0; + int err = lfs_dir_traverse(lfs, + source, 0, 0xffffffff, attrs, attrcount, + LFS_MKTAG(0x400, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_NAME, 0, 0), + begin, end, -begin, + lfs_dir_commit_size, &size); + if (err) { + return err; + } + + // space is complicated, we need room for tail, crc, gstate, + // cleanup delete, and we cap at half a block to give room + // for metadata updates. + if (end - begin < 0xff && + size <= lfs_min(lfs->cfg->block_size - 36, + lfs_alignup(lfs->cfg->block_size/2, + lfs->cfg->prog_size))) { + break; + } + + // can't fit, need to split, we should really be finding the + // largest size that fits with a small binary search, but right now + // it's not worth the code size + uint16_t split = (end - begin) / 2; + err = lfs_dir_split(lfs, dir, attrs, attrcount, + source, begin+split, end); + if (err) { + // if we fail to split, we may be able to overcompact, unless + // we're too big for even the full block, in which case our + // only option is to error + if (err == LFS_ERR_NOSPC && size <= lfs->cfg->block_size - 36) { + break; + } + return err; + } + + end = begin + split; + } + + // increment revision count + dir->rev += 1; + // If our revision count == n * block_cycles, we should force a relocation, + // this is how littlefs wear-levels at the metadata-pair level. Note that we + // actually use (block_cycles+1)|1, this is to avoid two corner cases: + // 1. block_cycles = 1, which would prevent relocations from terminating + // 2. block_cycles = 2n, which, due to aliasing, would only ever relocate + // one metadata block in the pair, effectively making this useless + if (lfs->cfg->block_cycles > 0 && + (dir->rev % ((lfs->cfg->block_cycles+1)|1) == 0)) { + if (lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) { + // oh no! we're writing too much to the superblock, + // should we expand? + lfs_ssize_t res = lfs_fs_size(lfs); + if (res < 0) { + return res; + } + + // do we have extra space? littlefs can't reclaim this space + // by itself, so expand cautiously + if ((lfs_size_t)res < lfs->cfg->block_count/2) { + LFS_DEBUG("Expanding superblock at rev %"PRIu32, dir->rev); + int err = lfs_dir_split(lfs, dir, attrs, attrcount, + source, begin, end); + if (err && err != LFS_ERR_NOSPC) { + return err; + } + + // welp, we tried, if we ran out of space there's not much + // we can do, we'll error later if we've become frozen + if (!err) { + end = begin; + } + } +#ifdef LFS_MIGRATE + } else if (lfs->lfs1) { + // do not proactively relocate blocks during migrations, this + // can cause a number of failure states such: clobbering the + // v1 superblock if we relocate root, and invalidating directory + // pointers if we relocate the head of a directory. On top of + // this, relocations increase the overall complexity of + // lfs_migration, which is already a delicate operation. +#endif + } else { + // we're writing too much, time to relocate + tired = true; + goto relocate; + } + } + + // begin loop to commit compaction to blocks until a compact sticks + while (true) { + { + // setup commit state + struct lfs_commit commit = { + .block = dir->pair[1], + .off = 0, + .ptag = 0xffffffff, + .crc = 0xffffffff, + + .begin = 0, + .end = lfs->cfg->block_size - 8, + }; + + // erase block to write to + int err = lfs_bd_erase(lfs, dir->pair[1]); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // write out header + dir->rev = lfs_tole32(dir->rev); + err = lfs_dir_commitprog(lfs, &commit, + &dir->rev, sizeof(dir->rev)); + dir->rev = lfs_fromle32(dir->rev); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // traverse the directory, this time writing out all unique tags + err = lfs_dir_traverse(lfs, + source, 0, 0xffffffff, attrs, attrcount, + LFS_MKTAG(0x400, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_NAME, 0, 0), + begin, end, -begin, + lfs_dir_commit_commit, &(struct lfs_dir_commit_commit){ + lfs, &commit}); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // commit tail, which may be new after last size check + if (!lfs_pair_isnull(dir->tail)) { + lfs_pair_tole32(dir->tail); + err = lfs_dir_commitattr(lfs, &commit, + LFS_MKTAG(LFS_TYPE_TAIL + dir->split, 0x3ff, 8), + dir->tail); + lfs_pair_fromle32(dir->tail); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + // bring over gstate? + lfs_gstate_t delta = {0}; + if (!relocated) { + lfs_gstate_xor(&delta, &lfs->gdisk); + lfs_gstate_xor(&delta, &lfs->gstate); + } + lfs_gstate_xor(&delta, &lfs->gdelta); + delta.tag &= ~LFS_MKTAG(0, 0, 0x3ff); + + err = lfs_dir_getgstate(lfs, dir, &delta); + if (err) { + return err; + } + + if (!lfs_gstate_iszero(&delta)) { + lfs_gstate_tole32(&delta); + err = lfs_dir_commitattr(lfs, &commit, + LFS_MKTAG(LFS_TYPE_MOVESTATE, 0x3ff, + sizeof(delta)), &delta); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + // complete commit with crc + err = lfs_dir_commitcrc(lfs, &commit); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // successful compaction, swap dir pair to indicate most recent + LFS_ASSERT(commit.off % lfs->cfg->prog_size == 0); + lfs_pair_swap(dir->pair); + dir->count = end - begin; + dir->off = commit.off; + dir->etag = commit.ptag; + // update gstate + lfs->gdelta = (lfs_gstate_t){0}; + if (!relocated) { + lfs->gdisk = lfs->gstate; + } + } + break; + +relocate: + // commit was corrupted, drop caches and prepare to relocate block + relocated = true; + lfs_cache_drop(lfs, &lfs->pcache); + if (!tired) { + LFS_DEBUG("Bad block at 0x%"PRIx32, dir->pair[1]); + } + + // can't relocate superblock, filesystem is now frozen + if (lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) { + LFS_WARN("Superblock 0x%"PRIx32" has become unwritable", + dir->pair[1]); + return LFS_ERR_NOSPC; + } + + // relocate half of pair + int err = lfs_alloc(lfs, &dir->pair[1]); + if (err && (err != LFS_ERR_NOSPC || !tired)) { + return err; + } + + tired = false; + continue; + } + + if (relocated) { + // update references if we relocated + LFS_DEBUG("Relocating {0x%"PRIx32", 0x%"PRIx32"} " + "-> {0x%"PRIx32", 0x%"PRIx32"}", + oldpair[0], oldpair[1], dir->pair[0], dir->pair[1]); + int err = lfs_fs_relocate(lfs, oldpair, dir->pair); + if (err) { + return err; + } + } + + return 0; +} + +static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, + const struct lfs_mattr *attrs, int attrcount) { + // check for any inline files that aren't RAM backed and + // forcefully evict them, needed for filesystem consistency + for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) { + if (dir != &f->m && lfs_pair_cmp(f->m.pair, dir->pair) == 0 && + f->type == LFS_TYPE_REG && (f->flags & LFS_F_INLINE) && + f->ctz.size > lfs->cfg->cache_size) { + int err = lfs_file_outline(lfs, f); + if (err) { + return err; + } + + err = lfs_file_flush(lfs, f); + if (err) { + return err; + } + } + } + + // calculate changes to the directory + lfs_mdir_t olddir = *dir; + bool hasdelete = false; + for (int i = 0; i < attrcount; i++) { + if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_CREATE) { + dir->count += 1; + } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE) { + LFS_ASSERT(dir->count > 0); + dir->count -= 1; + hasdelete = true; + } else if (lfs_tag_type1(attrs[i].tag) == LFS_TYPE_TAIL) { + dir->tail[0] = ((lfs_block_t*)attrs[i].buffer)[0]; + dir->tail[1] = ((lfs_block_t*)attrs[i].buffer)[1]; + dir->split = (lfs_tag_chunk(attrs[i].tag) & 1); + lfs_pair_fromle32(dir->tail); + } + } + + // should we actually drop the directory block? + if (hasdelete && dir->count == 0) { + lfs_mdir_t pdir; + int err = lfs_fs_pred(lfs, dir->pair, &pdir); + if (err && err != LFS_ERR_NOENT) { + *dir = olddir; + return err; + } + + if (err != LFS_ERR_NOENT && pdir.split) { + err = lfs_dir_drop(lfs, &pdir, dir); + if (err) { + *dir = olddir; + return err; + } + } + } + + if (dir->erased || dir->count >= 0xff) { + // try to commit + struct lfs_commit commit = { + .block = dir->pair[0], + .off = dir->off, + .ptag = dir->etag, + .crc = 0xffffffff, + + .begin = dir->off, + .end = lfs->cfg->block_size - 8, + }; + + // traverse attrs that need to be written out + lfs_pair_tole32(dir->tail); + int err = lfs_dir_traverse(lfs, + dir, dir->off, dir->etag, attrs, attrcount, + 0, 0, 0, 0, 0, + lfs_dir_commit_commit, &(struct lfs_dir_commit_commit){ + lfs, &commit}); + lfs_pair_fromle32(dir->tail); + if (err) { + if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { + goto compact; + } + *dir = olddir; + return err; + } + + // commit any global diffs if we have any + lfs_gstate_t delta = {0}; + lfs_gstate_xor(&delta, &lfs->gstate); + lfs_gstate_xor(&delta, &lfs->gdisk); + lfs_gstate_xor(&delta, &lfs->gdelta); + delta.tag &= ~LFS_MKTAG(0, 0, 0x3ff); + if (!lfs_gstate_iszero(&delta)) { + err = lfs_dir_getgstate(lfs, dir, &delta); + if (err) { + *dir = olddir; + return err; + } + + lfs_gstate_tole32(&delta); + err = lfs_dir_commitattr(lfs, &commit, + LFS_MKTAG(LFS_TYPE_MOVESTATE, 0x3ff, + sizeof(delta)), &delta); + if (err) { + if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { + goto compact; + } + *dir = olddir; + return err; + } + } + + // finalize commit with the crc + err = lfs_dir_commitcrc(lfs, &commit); + if (err) { + if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { + goto compact; + } + *dir = olddir; + return err; + } + + // successful commit, update dir + LFS_ASSERT(commit.off % lfs->cfg->prog_size == 0); + dir->off = commit.off; + dir->etag = commit.ptag; + // and update gstate + lfs->gdisk = lfs->gstate; + lfs->gdelta = (lfs_gstate_t){0}; + } else { +compact: + // fall back to compaction + lfs_cache_drop(lfs, &lfs->pcache); + + int err = lfs_dir_compact(lfs, dir, attrs, attrcount, + dir, 0, dir->count); + if (err) { + *dir = olddir; + return err; + } + } + + // this complicated bit of logic is for fixing up any active + // metadata-pairs that we may have affected + // + // note we have to make two passes since the mdir passed to + // lfs_dir_commit could also be in this list, and even then + // we need to copy the pair so they don't get clobbered if we refetch + // our mdir. + for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) { + if (&d->m != dir && lfs_pair_cmp(d->m.pair, olddir.pair) == 0) { + d->m = *dir; + for (int i = 0; i < attrcount; i++) { + if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE && + d->id == lfs_tag_id(attrs[i].tag)) { + d->m.pair[0] = LFS_BLOCK_NULL; + d->m.pair[1] = LFS_BLOCK_NULL; + } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE && + d->id > lfs_tag_id(attrs[i].tag)) { + d->id -= 1; + if (d->type == LFS_TYPE_DIR) { + ((lfs_dir_t*)d)->pos -= 1; + } + } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_CREATE && + d->id >= lfs_tag_id(attrs[i].tag)) { + d->id += 1; + if (d->type == LFS_TYPE_DIR) { + ((lfs_dir_t*)d)->pos += 1; + } + } + } + } + } + + for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) { + if (lfs_pair_cmp(d->m.pair, olddir.pair) == 0) { + while (d->id >= d->m.count && d->m.split) { + // we split and id is on tail now + d->id -= d->m.count; + int err = lfs_dir_fetch(lfs, &d->m, d->m.tail); + if (err) { + return err; + } + } + } + } + + return 0; +} + + +/// Top level directory operations /// +int lfs_mkdir(lfs_t *lfs, const char *path) { + LFS_TRACE("lfs_mkdir(%p, \"%s\")", (void*)lfs, path); + // deorphan if we haven't yet, needed at most once after poweron + int err = lfs_fs_forceconsistency(lfs); + if (err) { + LFS_TRACE("lfs_mkdir -> %d", err); + return err; + } + + struct lfs_mlist cwd; + cwd.next = lfs->mlist; + uint16_t id; + err = lfs_dir_find(lfs, &cwd.m, &path, &id); + if (!(err == LFS_ERR_NOENT && id != 0x3ff)) { + LFS_TRACE("lfs_mkdir -> %d", (err < 0) ? err : LFS_ERR_EXIST); + return (err < 0) ? err : LFS_ERR_EXIST; + } + + // check that name fits + lfs_size_t nlen = strlen(path); + if (nlen > lfs->name_max) { + LFS_TRACE("lfs_mkdir -> %d", LFS_ERR_NAMETOOLONG); + return LFS_ERR_NAMETOOLONG; + } + + // build up new directory + lfs_alloc_ack(lfs); + lfs_mdir_t dir; + err = lfs_dir_alloc(lfs, &dir); + if (err) { + LFS_TRACE("lfs_mkdir -> %d", err); + return err; + } + + // find end of list + lfs_mdir_t pred = cwd.m; + while (pred.split) { + err = lfs_dir_fetch(lfs, &pred, pred.tail); + if (err) { + LFS_TRACE("lfs_mkdir -> %d", err); + return err; + } + } + + // setup dir + lfs_pair_tole32(pred.tail); + err = lfs_dir_commit(lfs, &dir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), pred.tail})); + lfs_pair_fromle32(pred.tail); + if (err) { + LFS_TRACE("lfs_mkdir -> %d", err); + return err; + } + + // current block end of list? + if (cwd.m.split) { + // update tails, this creates a desync + lfs_fs_preporphans(lfs, +1); + + // it's possible our predecessor has to be relocated, and if + // our parent is our predecessor's predecessor, this could have + // caused our parent to go out of date, fortunately we can hook + // ourselves into littlefs to catch this + cwd.type = 0; + cwd.id = 0; + lfs->mlist = &cwd; + + lfs_pair_tole32(dir.pair); + err = lfs_dir_commit(lfs, &pred, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair})); + lfs_pair_fromle32(dir.pair); + if (err) { + lfs->mlist = cwd.next; + LFS_TRACE("lfs_mkdir -> %d", err); + return err; + } + + lfs->mlist = cwd.next; + lfs_fs_preporphans(lfs, -1); + } + + // now insert into our parent block + lfs_pair_tole32(dir.pair); + err = lfs_dir_commit(lfs, &cwd.m, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_CREATE, id, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_DIR, id, nlen), path}, + {LFS_MKTAG(LFS_TYPE_DIRSTRUCT, id, 8), dir.pair}, + {LFS_MKTAG_IF(!cwd.m.split, + LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair})); + lfs_pair_fromle32(dir.pair); + if (err) { + LFS_TRACE("lfs_mkdir -> %d", err); + return err; + } + + LFS_TRACE("lfs_mkdir -> %d", 0); + return 0; +} + +int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) { + LFS_TRACE("lfs_dir_open(%p, %p, \"%s\")", (void*)lfs, (void*)dir, path); + lfs_stag_t tag = lfs_dir_find(lfs, &dir->m, &path, NULL); + if (tag < 0) { + LFS_TRACE("lfs_dir_open -> %"PRId32, tag); + return tag; + } + + if (lfs_tag_type3(tag) != LFS_TYPE_DIR) { + LFS_TRACE("lfs_dir_open -> %d", LFS_ERR_NOTDIR); + return LFS_ERR_NOTDIR; + } + + lfs_block_t pair[2]; + if (lfs_tag_id(tag) == 0x3ff) { + // handle root dir separately + pair[0] = lfs->root[0]; + pair[1] = lfs->root[1]; + } else { + // get dir pair from parent + lfs_stag_t res = lfs_dir_get(lfs, &dir->m, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), pair); + if (res < 0) { + LFS_TRACE("lfs_dir_open -> %"PRId32, res); + return res; + } + lfs_pair_fromle32(pair); + } + + // fetch first pair + int err = lfs_dir_fetch(lfs, &dir->m, pair); + if (err) { + LFS_TRACE("lfs_dir_open -> %d", err); + return err; + } + + // setup entry + dir->head[0] = dir->m.pair[0]; + dir->head[1] = dir->m.pair[1]; + dir->id = 0; + dir->pos = 0; + + // add to list of mdirs + dir->type = LFS_TYPE_DIR; + dir->next = (lfs_dir_t*)lfs->mlist; + lfs->mlist = (struct lfs_mlist*)dir; + + LFS_TRACE("lfs_dir_open -> %d", 0); + return 0; +} + +int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir) { + LFS_TRACE("lfs_dir_close(%p, %p)", (void*)lfs, (void*)dir); + // remove from list of mdirs + for (struct lfs_mlist **p = &lfs->mlist; *p; p = &(*p)->next) { + if (*p == (struct lfs_mlist*)dir) { + *p = (*p)->next; + break; + } + } + + LFS_TRACE("lfs_dir_close -> %d", 0); + return 0; +} + +int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { + LFS_TRACE("lfs_dir_read(%p, %p, %p)", + (void*)lfs, (void*)dir, (void*)info); + memset(info, 0, sizeof(*info)); + + // special offset for '.' and '..' + if (dir->pos == 0) { + info->type = LFS_TYPE_DIR; + strcpy(info->name, "."); + dir->pos += 1; + LFS_TRACE("lfs_dir_read -> %d", true); + return true; + } else if (dir->pos == 1) { + info->type = LFS_TYPE_DIR; + strcpy(info->name, ".."); + dir->pos += 1; + LFS_TRACE("lfs_dir_read -> %d", true); + return true; + } + + while (true) { + if (dir->id == dir->m.count) { + if (!dir->m.split) { + LFS_TRACE("lfs_dir_read -> %d", false); + return false; + } + + int err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail); + if (err) { + LFS_TRACE("lfs_dir_read -> %d", err); + return err; + } + + dir->id = 0; + } + + int err = lfs_dir_getinfo(lfs, &dir->m, dir->id, info); + if (err && err != LFS_ERR_NOENT) { + LFS_TRACE("lfs_dir_read -> %d", err); + return err; + } + + dir->id += 1; + if (err != LFS_ERR_NOENT) { + break; + } + } + + dir->pos += 1; + LFS_TRACE("lfs_dir_read -> %d", true); + return true; +} + +int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) { + LFS_TRACE("lfs_dir_seek(%p, %p, %"PRIu32")", + (void*)lfs, (void*)dir, off); + // simply walk from head dir + int err = lfs_dir_rewind(lfs, dir); + if (err) { + LFS_TRACE("lfs_dir_seek -> %d", err); + return err; + } + + // first two for ./.. + dir->pos = lfs_min(2, off); + off -= dir->pos; + + // skip superblock entry + dir->id = (off > 0 && lfs_pair_cmp(dir->head, lfs->root) == 0); + + while (off > 0) { + int diff = lfs_min(dir->m.count - dir->id, off); + dir->id += diff; + dir->pos += diff; + off -= diff; + + if (dir->id == dir->m.count) { + if (!dir->m.split) { + LFS_TRACE("lfs_dir_seek -> %d", LFS_ERR_INVAL); + return LFS_ERR_INVAL; + } + + err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail); + if (err) { + LFS_TRACE("lfs_dir_seek -> %d", err); + return err; + } + + dir->id = 0; + } + } + + LFS_TRACE("lfs_dir_seek -> %d", 0); + return 0; +} + +lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir) { + LFS_TRACE("lfs_dir_tell(%p, %p)", (void*)lfs, (void*)dir); + (void)lfs; + LFS_TRACE("lfs_dir_tell -> %"PRId32, dir->pos); + return dir->pos; +} + +int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) { + LFS_TRACE("lfs_dir_rewind(%p, %p)", (void*)lfs, (void*)dir); + // reload the head dir + int err = lfs_dir_fetch(lfs, &dir->m, dir->head); + if (err) { + LFS_TRACE("lfs_dir_rewind -> %d", err); + return err; + } + + dir->id = 0; + dir->pos = 0; + LFS_TRACE("lfs_dir_rewind -> %d", 0); + return 0; +} + + +/// File index list operations /// +static int lfs_ctz_index(lfs_t *lfs, lfs_off_t *off) { + lfs_off_t size = *off; + lfs_off_t b = lfs->cfg->block_size - 2*4; + lfs_off_t i = size / b; + if (i == 0) { + return 0; + } + + i = (size - 4*(lfs_popc(i-1)+2)) / b; + *off = size - b*i - 4*lfs_popc(i); + return i; +} + +static int lfs_ctz_find(lfs_t *lfs, + const lfs_cache_t *pcache, lfs_cache_t *rcache, + lfs_block_t head, lfs_size_t size, + lfs_size_t pos, lfs_block_t *block, lfs_off_t *off) { + if (size == 0) { + *block = LFS_BLOCK_NULL; + *off = 0; + return 0; + } + + lfs_off_t current = lfs_ctz_index(lfs, &(lfs_off_t){size-1}); + lfs_off_t target = lfs_ctz_index(lfs, &pos); + + while (current > target) { + lfs_size_t skip = lfs_min( + lfs_npw2(current-target+1) - 1, + lfs_ctz(current)); + + int err = lfs_bd_read(lfs, + pcache, rcache, sizeof(head), + head, 4*skip, &head, sizeof(head)); + head = lfs_fromle32(head); + if (err) { + return err; + } + + current -= 1 << skip; + } + + *block = head; + *off = pos; + return 0; +} + +static int lfs_ctz_extend(lfs_t *lfs, + lfs_cache_t *pcache, lfs_cache_t *rcache, + lfs_block_t head, lfs_size_t size, + lfs_block_t *block, lfs_off_t *off) { + while (true) { + // go ahead and grab a block + lfs_block_t nblock; + int err = lfs_alloc(lfs, &nblock); + if (err) { + return err; + } + + { + err = lfs_bd_erase(lfs, nblock); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + if (size == 0) { + *block = nblock; + *off = 0; + return 0; + } + + lfs_size_t noff = size - 1; + lfs_off_t index = lfs_ctz_index(lfs, &noff); + noff = noff + 1; + + // just copy out the last block if it is incomplete + if (noff != lfs->cfg->block_size) { + for (lfs_off_t i = 0; i < noff; i++) { + uint8_t data; + err = lfs_bd_read(lfs, + NULL, rcache, noff-i, + head, i, &data, 1); + if (err) { + return err; + } + + err = lfs_bd_prog(lfs, + pcache, rcache, true, + nblock, i, &data, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + *block = nblock; + *off = noff; + return 0; + } + + // append block + index += 1; + lfs_size_t skips = lfs_ctz(index) + 1; + lfs_block_t nhead = head; + for (lfs_off_t i = 0; i < skips; i++) { + nhead = lfs_tole32(nhead); + err = lfs_bd_prog(lfs, pcache, rcache, true, + nblock, 4*i, &nhead, 4); + nhead = lfs_fromle32(nhead); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + if (i != skips-1) { + err = lfs_bd_read(lfs, + NULL, rcache, sizeof(nhead), + nhead, 4*i, &nhead, sizeof(nhead)); + nhead = lfs_fromle32(nhead); + if (err) { + return err; + } + } + } + + *block = nblock; + *off = 4*skips; + return 0; + } + +relocate: + LFS_DEBUG("Bad block at 0x%"PRIx32, nblock); + + // just clear cache and try a new block + lfs_cache_drop(lfs, pcache); + } +} + +static int lfs_ctz_traverse(lfs_t *lfs, + const lfs_cache_t *pcache, lfs_cache_t *rcache, + lfs_block_t head, lfs_size_t size, + int (*cb)(void*, lfs_block_t), void *data) { + if (size == 0) { + return 0; + } + + lfs_off_t index = lfs_ctz_index(lfs, &(lfs_off_t){size-1}); + + while (true) { + int err = cb(data, head); + if (err) { + return err; + } + + if (index == 0) { + return 0; + } + + lfs_block_t heads[2]; + int count = 2 - (index & 1); + err = lfs_bd_read(lfs, + pcache, rcache, count*sizeof(head), + head, 0, &heads, count*sizeof(head)); + heads[0] = lfs_fromle32(heads[0]); + heads[1] = lfs_fromle32(heads[1]); + if (err) { + return err; + } + + for (int i = 0; i < count-1; i++) { + err = cb(data, heads[i]); + if (err) { + return err; + } + } + + head = heads[count-1]; + index -= count; + } +} + + +/// Top level file operations /// +int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags, + const struct lfs_file_config *cfg) { + LFS_TRACE("lfs_file_opencfg(%p, %p, \"%s\", %x, %p {" + ".buffer=%p, .attrs=%p, .attr_count=%"PRIu32"})", + (void*)lfs, (void*)file, path, flags, + (void*)cfg, cfg->buffer, (void*)cfg->attrs, cfg->attr_count); + + // deorphan if we haven't yet, needed at most once after poweron + if ((flags & 3) != LFS_O_RDONLY) { + int err = lfs_fs_forceconsistency(lfs); + if (err) { + LFS_TRACE("lfs_file_opencfg -> %d", err); + return err; + } + } + + // setup simple file details + int err; + file->cfg = cfg; + file->flags = flags | LFS_F_OPENED; + file->pos = 0; + file->off = 0; + file->cache.buffer = NULL; + + // allocate entry for file if it doesn't exist + lfs_stag_t tag = lfs_dir_find(lfs, &file->m, &path, &file->id); + if (tag < 0 && !(tag == LFS_ERR_NOENT && file->id != 0x3ff)) { + err = tag; + goto cleanup; + } + + // get id, add to list of mdirs to catch update changes + file->type = LFS_TYPE_REG; + file->next = (lfs_file_t*)lfs->mlist; + lfs->mlist = (struct lfs_mlist*)file; + + if (tag == LFS_ERR_NOENT) { + if (!(flags & LFS_O_CREAT)) { + err = LFS_ERR_NOENT; + goto cleanup; + } + + // check that name fits + lfs_size_t nlen = strlen(path); + if (nlen > lfs->name_max) { + err = LFS_ERR_NAMETOOLONG; + goto cleanup; + } + + // get next slot and create entry to remember name + err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_CREATE, file->id, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_REG, file->id, nlen), path}, + {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0), NULL})); + if (err) { + err = LFS_ERR_NAMETOOLONG; + goto cleanup; + } + + tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, 0); + } else if (flags & LFS_O_EXCL) { + err = LFS_ERR_EXIST; + goto cleanup; + } else if (lfs_tag_type3(tag) != LFS_TYPE_REG) { + err = LFS_ERR_ISDIR; + goto cleanup; + } else if (flags & LFS_O_TRUNC) { + // truncate if requested + tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0); + file->flags |= LFS_F_DIRTY; + } else { + // try to load what's on disk, if it's inlined we'll fix it later + tag = lfs_dir_get(lfs, &file->m, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, file->id, 8), &file->ctz); + if (tag < 0) { + err = tag; + goto cleanup; + } + lfs_ctz_fromle32(&file->ctz); + } + + // fetch attrs + for (unsigned i = 0; i < file->cfg->attr_count; i++) { + if ((file->flags & 3) != LFS_O_WRONLY) { + lfs_stag_t res = lfs_dir_get(lfs, &file->m, + LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_USERATTR + file->cfg->attrs[i].type, + file->id, file->cfg->attrs[i].size), + file->cfg->attrs[i].buffer); + if (res < 0 && res != LFS_ERR_NOENT) { + err = res; + goto cleanup; + } + } + + if ((file->flags & 3) != LFS_O_RDONLY) { + if (file->cfg->attrs[i].size > lfs->attr_max) { + err = LFS_ERR_NOSPC; + goto cleanup; + } + + file->flags |= LFS_F_DIRTY; + } + } + + // allocate buffer if needed + if (file->cfg->buffer) { + file->cache.buffer = file->cfg->buffer; + } else { + file->cache.buffer = lfs_malloc(lfs->cfg->cache_size); + if (!file->cache.buffer) { + err = LFS_ERR_NOMEM; + goto cleanup; + } + } + + // zero to avoid information leak + lfs_cache_zero(lfs, &file->cache); + + if (lfs_tag_type3(tag) == LFS_TYPE_INLINESTRUCT) { + // load inline files + file->ctz.head = LFS_BLOCK_INLINE; + file->ctz.size = lfs_tag_size(tag); + file->flags |= LFS_F_INLINE; + file->cache.block = file->ctz.head; + file->cache.off = 0; + file->cache.size = lfs->cfg->cache_size; + + // don't always read (may be new/trunc file) + if (file->ctz.size > 0) { + lfs_stag_t res = lfs_dir_get(lfs, &file->m, + LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, file->id, + lfs_min(file->cache.size, 0x3fe)), + file->cache.buffer); + if (res < 0) { + err = res; + goto cleanup; + } + } + } + + LFS_TRACE("lfs_file_opencfg -> %d", 0); + return 0; + +cleanup: + // clean up lingering resources + file->flags |= LFS_F_ERRED; + lfs_file_close(lfs, file); + LFS_TRACE("lfs_file_opencfg -> %d", err); + return err; +} + +int lfs_file_open(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags) { + LFS_TRACE("lfs_file_open(%p, %p, \"%s\", %x)", + (void*)lfs, (void*)file, path, flags); + static const struct lfs_file_config defaults = {0}; + int err = lfs_file_opencfg(lfs, file, path, flags, &defaults); + LFS_TRACE("lfs_file_open -> %d", err); + return err; +} + +int lfs_file_close(lfs_t *lfs, lfs_file_t *file) { + LFS_TRACE("lfs_file_close(%p, %p)", (void*)lfs, (void*)file); + LFS_ASSERT(file->flags & LFS_F_OPENED); + + int err = lfs_file_sync(lfs, file); + + // remove from list of mdirs + for (struct lfs_mlist **p = &lfs->mlist; *p; p = &(*p)->next) { + if (*p == (struct lfs_mlist*)file) { + *p = (*p)->next; + break; + } + } + + // clean up memory + if (!file->cfg->buffer) { + lfs_free(file->cache.buffer); + } + + file->flags &= ~LFS_F_OPENED; + LFS_TRACE("lfs_file_close -> %d", err); + return err; +} + +static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) { + LFS_ASSERT(file->flags & LFS_F_OPENED); + + while (true) { + // just relocate what exists into new block + lfs_block_t nblock; + int err = lfs_alloc(lfs, &nblock); + if (err) { + return err; + } + + err = lfs_bd_erase(lfs, nblock); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // either read from dirty cache or disk + for (lfs_off_t i = 0; i < file->off; i++) { + uint8_t data; + if (file->flags & LFS_F_INLINE) { + err = lfs_dir_getread(lfs, &file->m, + // note we evict inline files before they can be dirty + NULL, &file->cache, file->off-i, + LFS_MKTAG(0xfff, 0x1ff, 0), + LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0), + i, &data, 1); + if (err) { + return err; + } + } else { + err = lfs_bd_read(lfs, + &file->cache, &lfs->rcache, file->off-i, + file->block, i, &data, 1); + if (err) { + return err; + } + } + + err = lfs_bd_prog(lfs, + &lfs->pcache, &lfs->rcache, true, + nblock, i, &data, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + // copy over new state of file + memcpy(file->cache.buffer, lfs->pcache.buffer, lfs->cfg->cache_size); + file->cache.block = lfs->pcache.block; + file->cache.off = lfs->pcache.off; + file->cache.size = lfs->pcache.size; + lfs_cache_zero(lfs, &lfs->pcache); + + file->block = nblock; + file->flags |= LFS_F_WRITING; + return 0; + +relocate: + LFS_DEBUG("Bad block at 0x%"PRIx32, nblock); + + // just clear cache and try a new block + lfs_cache_drop(lfs, &lfs->pcache); + } +} + +static int lfs_file_outline(lfs_t *lfs, lfs_file_t *file) { + file->off = file->pos; + lfs_alloc_ack(lfs); + int err = lfs_file_relocate(lfs, file); + if (err) { + return err; + } + + file->flags &= ~LFS_F_INLINE; + return 0; +} + +static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) { + LFS_ASSERT(file->flags & LFS_F_OPENED); + + if (file->flags & LFS_F_READING) { + if (!(file->flags & LFS_F_INLINE)) { + lfs_cache_drop(lfs, &file->cache); + } + file->flags &= ~LFS_F_READING; + } + + if (file->flags & LFS_F_WRITING) { + lfs_off_t pos = file->pos; + + if (!(file->flags & LFS_F_INLINE)) { + // copy over anything after current branch + lfs_file_t orig = { + .ctz.head = file->ctz.head, + .ctz.size = file->ctz.size, + .flags = LFS_O_RDONLY | LFS_F_OPENED, + .pos = file->pos, + .cache = lfs->rcache, + }; + lfs_cache_drop(lfs, &lfs->rcache); + + while (file->pos < file->ctz.size) { + // copy over a byte at a time, leave it up to caching + // to make this efficient + uint8_t data; + lfs_ssize_t res = lfs_file_read(lfs, &orig, &data, 1); + if (res < 0) { + return res; + } + + res = lfs_file_write(lfs, file, &data, 1); + if (res < 0) { + return res; + } + + // keep our reference to the rcache in sync + if (lfs->rcache.block != LFS_BLOCK_NULL) { + lfs_cache_drop(lfs, &orig.cache); + lfs_cache_drop(lfs, &lfs->rcache); + } + } + + // write out what we have + while (true) { + int err = lfs_bd_flush(lfs, &file->cache, &lfs->rcache, true); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + break; + +relocate: + LFS_DEBUG("Bad block at 0x%"PRIx32, file->block); + err = lfs_file_relocate(lfs, file); + if (err) { + return err; + } + } + } else { + file->pos = lfs_max(file->pos, file->ctz.size); + } + + // actual file updates + file->ctz.head = file->block; + file->ctz.size = file->pos; + file->flags &= ~LFS_F_WRITING; + file->flags |= LFS_F_DIRTY; + + file->pos = pos; + } + + return 0; +} + +int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) { + LFS_TRACE("lfs_file_sync(%p, %p)", (void*)lfs, (void*)file); + LFS_ASSERT(file->flags & LFS_F_OPENED); + + if (file->flags & LFS_F_ERRED) { + // it's not safe to do anything if our file errored + LFS_TRACE("lfs_file_sync -> %d", 0); + return 0; + } + + int err = lfs_file_flush(lfs, file); + if (err) { + file->flags |= LFS_F_ERRED; + LFS_TRACE("lfs_file_sync -> %d", err); + return err; + } + + if ((file->flags & LFS_F_DIRTY) && + !lfs_pair_isnull(file->m.pair)) { + // update dir entry + uint16_t type; + const void *buffer; + lfs_size_t size; + struct lfs_ctz ctz; + if (file->flags & LFS_F_INLINE) { + // inline the whole file + type = LFS_TYPE_INLINESTRUCT; + buffer = file->cache.buffer; + size = file->ctz.size; + } else { + // update the ctz reference + type = LFS_TYPE_CTZSTRUCT; + // copy ctz so alloc will work during a relocate + ctz = file->ctz; + lfs_ctz_tole32(&ctz); + buffer = &ctz; + size = sizeof(ctz); + } + + // commit file data and attributes + err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS( + {LFS_MKTAG(type, file->id, size), buffer}, + {LFS_MKTAG(LFS_FROM_USERATTRS, file->id, + file->cfg->attr_count), file->cfg->attrs})); + if (err) { + file->flags |= LFS_F_ERRED; + LFS_TRACE("lfs_file_sync -> %d", err); + return err; + } + + file->flags &= ~LFS_F_DIRTY; + } + + LFS_TRACE("lfs_file_sync -> %d", 0); + return 0; +} + +lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size) { + LFS_TRACE("lfs_file_read(%p, %p, %p, %"PRIu32")", + (void*)lfs, (void*)file, buffer, size); + LFS_ASSERT(file->flags & LFS_F_OPENED); + LFS_ASSERT((file->flags & 3) != LFS_O_WRONLY); + + uint8_t *data = buffer; + lfs_size_t nsize = size; + + if (file->flags & LFS_F_WRITING) { + // flush out any writes + int err = lfs_file_flush(lfs, file); + if (err) { + LFS_TRACE("lfs_file_read -> %d", err); + return err; + } + } + + if (file->pos >= file->ctz.size) { + // eof if past end + LFS_TRACE("lfs_file_read -> %d", 0); + return 0; + } + + size = lfs_min(size, file->ctz.size - file->pos); + nsize = size; + + while (nsize > 0) { + // check if we need a new block + if (!(file->flags & LFS_F_READING) || + file->off == lfs->cfg->block_size) { + if (!(file->flags & LFS_F_INLINE)) { + int err = lfs_ctz_find(lfs, NULL, &file->cache, + file->ctz.head, file->ctz.size, + file->pos, &file->block, &file->off); + if (err) { + LFS_TRACE("lfs_file_read -> %d", err); + return err; + } + } else { + file->block = LFS_BLOCK_INLINE; + file->off = file->pos; + } + + file->flags |= LFS_F_READING; + } + + // read as much as we can in current block + lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off); + if (file->flags & LFS_F_INLINE) { + int err = lfs_dir_getread(lfs, &file->m, + NULL, &file->cache, lfs->cfg->block_size, + LFS_MKTAG(0xfff, 0x1ff, 0), + LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0), + file->off, data, diff); + if (err) { + LFS_TRACE("lfs_file_read -> %d", err); + return err; + } + } else { + int err = lfs_bd_read(lfs, + NULL, &file->cache, lfs->cfg->block_size, + file->block, file->off, data, diff); + if (err) { + LFS_TRACE("lfs_file_read -> %d", err); + return err; + } + } + + file->pos += diff; + file->off += diff; + data += diff; + nsize -= diff; + } + + LFS_TRACE("lfs_file_read -> %"PRId32, size); + return size; +} + +lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size) { + LFS_TRACE("lfs_file_write(%p, %p, %p, %"PRIu32")", + (void*)lfs, (void*)file, buffer, size); + LFS_ASSERT(file->flags & LFS_F_OPENED); + LFS_ASSERT((file->flags & 3) != LFS_O_RDONLY); + + const uint8_t *data = buffer; + lfs_size_t nsize = size; + + if (file->flags & LFS_F_READING) { + // drop any reads + int err = lfs_file_flush(lfs, file); + if (err) { + LFS_TRACE("lfs_file_write -> %d", err); + return err; + } + } + + if ((file->flags & LFS_O_APPEND) && file->pos < file->ctz.size) { + file->pos = file->ctz.size; + } + + if (file->pos + size > lfs->file_max) { + // Larger than file limit? + LFS_TRACE("lfs_file_write -> %d", LFS_ERR_FBIG); + return LFS_ERR_FBIG; + } + + if (!(file->flags & LFS_F_WRITING) && file->pos > file->ctz.size) { + // fill with zeros + lfs_off_t pos = file->pos; + file->pos = file->ctz.size; + + while (file->pos < pos) { + lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1); + if (res < 0) { + LFS_TRACE("lfs_file_write -> %"PRId32, res); + return res; + } + } + } + + if ((file->flags & LFS_F_INLINE) && + lfs_max(file->pos+nsize, file->ctz.size) > + lfs_min(0x3fe, lfs_min( + lfs->cfg->cache_size, lfs->cfg->block_size/8))) { + // inline file doesn't fit anymore + int err = lfs_file_outline(lfs, file); + if (err) { + file->flags |= LFS_F_ERRED; + LFS_TRACE("lfs_file_write -> %d", err); + return err; + } + } + + while (nsize > 0) { + // check if we need a new block + if (!(file->flags & LFS_F_WRITING) || + file->off == lfs->cfg->block_size) { + if (!(file->flags & LFS_F_INLINE)) { + if (!(file->flags & LFS_F_WRITING) && file->pos > 0) { + // find out which block we're extending from + int err = lfs_ctz_find(lfs, NULL, &file->cache, + file->ctz.head, file->ctz.size, + file->pos-1, &file->block, &file->off); + if (err) { + file->flags |= LFS_F_ERRED; + LFS_TRACE("lfs_file_write -> %d", err); + return err; + } + + // mark cache as dirty since we may have read data into it + lfs_cache_zero(lfs, &file->cache); + } + + // extend file with new blocks + lfs_alloc_ack(lfs); + int err = lfs_ctz_extend(lfs, &file->cache, &lfs->rcache, + file->block, file->pos, + &file->block, &file->off); + if (err) { + file->flags |= LFS_F_ERRED; + LFS_TRACE("lfs_file_write -> %d", err); + return err; + } + } else { + file->block = LFS_BLOCK_INLINE; + file->off = file->pos; + } + + file->flags |= LFS_F_WRITING; + } + + // program as much as we can in current block + lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off); + while (true) { + int err = lfs_bd_prog(lfs, &file->cache, &lfs->rcache, true, + file->block, file->off, data, diff); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + file->flags |= LFS_F_ERRED; + LFS_TRACE("lfs_file_write -> %d", err); + return err; + } + + break; +relocate: + err = lfs_file_relocate(lfs, file); + if (err) { + file->flags |= LFS_F_ERRED; + LFS_TRACE("lfs_file_write -> %d", err); + return err; + } + } + + file->pos += diff; + file->off += diff; + data += diff; + nsize -= diff; + + lfs_alloc_ack(lfs); + } + + file->flags &= ~LFS_F_ERRED; + LFS_TRACE("lfs_file_write -> %"PRId32, size); + return size; +} + +lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, + lfs_soff_t off, int whence) { + LFS_TRACE("lfs_file_seek(%p, %p, %"PRId32", %d)", + (void*)lfs, (void*)file, off, whence); + LFS_ASSERT(file->flags & LFS_F_OPENED); + + // write out everything beforehand, may be noop if rdonly + int err = lfs_file_flush(lfs, file); + if (err) { + LFS_TRACE("lfs_file_seek -> %d", err); + return err; + } + + // find new pos + lfs_off_t npos = file->pos; + if (whence == LFS_SEEK_SET) { + npos = off; + } else if (whence == LFS_SEEK_CUR) { + npos = file->pos + off; + } else if (whence == LFS_SEEK_END) { + npos = file->ctz.size + off; + } + + if (npos > lfs->file_max) { + // file position out of range + LFS_TRACE("lfs_file_seek -> %d", LFS_ERR_INVAL); + return LFS_ERR_INVAL; + } + + // update pos + file->pos = npos; + LFS_TRACE("lfs_file_seek -> %"PRId32, npos); + return npos; +} + +int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) { + LFS_TRACE("lfs_file_truncate(%p, %p, %"PRIu32")", + (void*)lfs, (void*)file, size); + LFS_ASSERT(file->flags & LFS_F_OPENED); + LFS_ASSERT((file->flags & 3) != LFS_O_RDONLY); + + if (size > LFS_FILE_MAX) { + LFS_TRACE("lfs_file_truncate -> %d", LFS_ERR_INVAL); + return LFS_ERR_INVAL; + } + + lfs_off_t pos = file->pos; + lfs_off_t oldsize = lfs_file_size(lfs, file); + if (size < oldsize) { + // need to flush since directly changing metadata + int err = lfs_file_flush(lfs, file); + if (err) { + LFS_TRACE("lfs_file_truncate -> %d", err); + return err; + } + + // lookup new head in ctz skip list + err = lfs_ctz_find(lfs, NULL, &file->cache, + file->ctz.head, file->ctz.size, + size, &file->block, &file->off); + if (err) { + LFS_TRACE("lfs_file_truncate -> %d", err); + return err; + } + + file->ctz.head = file->block; + file->ctz.size = size; + file->flags |= LFS_F_DIRTY | LFS_F_READING; + } else if (size > oldsize) { + // flush+seek if not already at end + if (file->pos != oldsize) { + lfs_soff_t res = lfs_file_seek(lfs, file, 0, LFS_SEEK_END); + if (res < 0) { + LFS_TRACE("lfs_file_truncate -> %"PRId32, res); + return (int)res; + } + } + + // fill with zeros + while (file->pos < size) { + lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1); + if (res < 0) { + LFS_TRACE("lfs_file_truncate -> %"PRId32, res); + return (int)res; + } + } + } + + // restore pos + lfs_soff_t res = lfs_file_seek(lfs, file, pos, LFS_SEEK_SET); + if (res < 0) { + LFS_TRACE("lfs_file_truncate -> %"PRId32, res); + return (int)res; + } + + LFS_TRACE("lfs_file_truncate -> %d", 0); + return 0; +} + +lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) { + LFS_TRACE("lfs_file_tell(%p, %p)", (void*)lfs, (void*)file); + LFS_ASSERT(file->flags & LFS_F_OPENED); + (void)lfs; + LFS_TRACE("lfs_file_tell -> %"PRId32, file->pos); + return file->pos; +} + +int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file) { + LFS_TRACE("lfs_file_rewind(%p, %p)", (void*)lfs, (void*)file); + lfs_soff_t res = lfs_file_seek(lfs, file, 0, LFS_SEEK_SET); + if (res < 0) { + LFS_TRACE("lfs_file_rewind -> %"PRId32, res); + return (int)res; + } + + LFS_TRACE("lfs_file_rewind -> %d", 0); + return 0; +} + +lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) { + LFS_TRACE("lfs_file_size(%p, %p)", (void*)lfs, (void*)file); + LFS_ASSERT(file->flags & LFS_F_OPENED); + (void)lfs; + if (file->flags & LFS_F_WRITING) { + LFS_TRACE("lfs_file_size -> %"PRId32, + lfs_max(file->pos, file->ctz.size)); + return lfs_max(file->pos, file->ctz.size); + } else { + LFS_TRACE("lfs_file_size -> %"PRId32, file->ctz.size); + return file->ctz.size; + } +} + + +/// General fs operations /// +int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) { + LFS_TRACE("lfs_stat(%p, \"%s\", %p)", (void*)lfs, path, (void*)info); + lfs_mdir_t cwd; + lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); + if (tag < 0) { + LFS_TRACE("lfs_stat -> %"PRId32, tag); + return (int)tag; + } + + int err = lfs_dir_getinfo(lfs, &cwd, lfs_tag_id(tag), info); + LFS_TRACE("lfs_stat -> %d", err); + return err; +} + +int lfs_remove(lfs_t *lfs, const char *path) { + LFS_TRACE("lfs_remove(%p, \"%s\")", (void*)lfs, path); + // deorphan if we haven't yet, needed at most once after poweron + int err = lfs_fs_forceconsistency(lfs); + if (err) { + LFS_TRACE("lfs_remove -> %d", err); + return err; + } + + lfs_mdir_t cwd; + lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); + if (tag < 0 || lfs_tag_id(tag) == 0x3ff) { + LFS_TRACE("lfs_remove -> %"PRId32, (tag < 0) ? tag : LFS_ERR_INVAL); + return (tag < 0) ? (int)tag : LFS_ERR_INVAL; + } + + struct lfs_mlist dir; + dir.next = lfs->mlist; + if (lfs_tag_type3(tag) == LFS_TYPE_DIR) { + // must be empty before removal + lfs_block_t pair[2]; + lfs_stag_t res = lfs_dir_get(lfs, &cwd, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), pair); + if (res < 0) { + LFS_TRACE("lfs_remove -> %"PRId32, res); + return (int)res; + } + lfs_pair_fromle32(pair); + + err = lfs_dir_fetch(lfs, &dir.m, pair); + if (err) { + LFS_TRACE("lfs_remove -> %d", err); + return err; + } + + if (dir.m.count > 0 || dir.m.split) { + LFS_TRACE("lfs_remove -> %d", LFS_ERR_NOTEMPTY); + return LFS_ERR_NOTEMPTY; + } + + // mark fs as orphaned + lfs_fs_preporphans(lfs, +1); + + // I know it's crazy but yes, dir can be changed by our parent's + // commit (if predecessor is child) + dir.type = 0; + dir.id = 0; + lfs->mlist = &dir; + } + + // delete the entry + err = lfs_dir_commit(lfs, &cwd, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(tag), 0), NULL})); + if (err) { + lfs->mlist = dir.next; + LFS_TRACE("lfs_remove -> %d", err); + return err; + } + + lfs->mlist = dir.next; + if (lfs_tag_type3(tag) == LFS_TYPE_DIR) { + // fix orphan + lfs_fs_preporphans(lfs, -1); + + err = lfs_fs_pred(lfs, dir.m.pair, &cwd); + if (err) { + LFS_TRACE("lfs_remove -> %d", err); + return err; + } + + err = lfs_dir_drop(lfs, &cwd, &dir.m); + if (err) { + LFS_TRACE("lfs_remove -> %d", err); + return err; + } + } + + LFS_TRACE("lfs_remove -> %d", 0); + return 0; +} + +int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { + LFS_TRACE("lfs_rename(%p, \"%s\", \"%s\")", (void*)lfs, oldpath, newpath); + + // deorphan if we haven't yet, needed at most once after poweron + int err = lfs_fs_forceconsistency(lfs); + if (err) { + LFS_TRACE("lfs_rename -> %d", err); + return err; + } + + // find old entry + lfs_mdir_t oldcwd; + lfs_stag_t oldtag = lfs_dir_find(lfs, &oldcwd, &oldpath, NULL); + if (oldtag < 0 || lfs_tag_id(oldtag) == 0x3ff) { + LFS_TRACE("lfs_rename -> %"PRId32, + (oldtag < 0) ? oldtag : LFS_ERR_INVAL); + return (oldtag < 0) ? (int)oldtag : LFS_ERR_INVAL; + } + + // find new entry + lfs_mdir_t newcwd; + uint16_t newid; + lfs_stag_t prevtag = lfs_dir_find(lfs, &newcwd, &newpath, &newid); + if ((prevtag < 0 || lfs_tag_id(prevtag) == 0x3ff) && + !(prevtag == LFS_ERR_NOENT && newid != 0x3ff)) { + LFS_TRACE("lfs_rename -> %"PRId32, + (prevtag < 0) ? prevtag : LFS_ERR_INVAL); + return (prevtag < 0) ? (int)prevtag : LFS_ERR_INVAL; + } + + // if we're in the same pair there's a few special cases... + bool samepair = (lfs_pair_cmp(oldcwd.pair, newcwd.pair) == 0); + uint16_t newoldid = lfs_tag_id(oldtag); + + struct lfs_mlist prevdir; + prevdir.next = lfs->mlist; + if (prevtag == LFS_ERR_NOENT) { + // check that name fits + lfs_size_t nlen = strlen(newpath); + if (nlen > lfs->name_max) { + LFS_TRACE("lfs_rename -> %d", LFS_ERR_NAMETOOLONG); + return LFS_ERR_NAMETOOLONG; + } + + // there is a small chance we are being renamed in the same + // directory/ to an id less than our old id, the global update + // to handle this is a bit messy + if (samepair && newid <= newoldid) { + newoldid += 1; + } + } else if (lfs_tag_type3(prevtag) != lfs_tag_type3(oldtag)) { + LFS_TRACE("lfs_rename -> %d", LFS_ERR_ISDIR); + return LFS_ERR_ISDIR; + } else if (samepair && newid == newoldid) { + // we're renaming to ourselves?? + LFS_TRACE("lfs_rename -> %d", 0); + return 0; + } else if (lfs_tag_type3(prevtag) == LFS_TYPE_DIR) { + // must be empty before removal + lfs_block_t prevpair[2]; + lfs_stag_t res = lfs_dir_get(lfs, &newcwd, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, newid, 8), prevpair); + if (res < 0) { + LFS_TRACE("lfs_rename -> %"PRId32, res); + return (int)res; + } + lfs_pair_fromle32(prevpair); + + // must be empty before removal + err = lfs_dir_fetch(lfs, &prevdir.m, prevpair); + if (err) { + LFS_TRACE("lfs_rename -> %d", err); + return err; + } + + if (prevdir.m.count > 0 || prevdir.m.split) { + LFS_TRACE("lfs_rename -> %d", LFS_ERR_NOTEMPTY); + return LFS_ERR_NOTEMPTY; + } + + // mark fs as orphaned + lfs_fs_preporphans(lfs, +1); + + // I know it's crazy but yes, dir can be changed by our parent's + // commit (if predecessor is child) + prevdir.type = 0; + prevdir.id = 0; + lfs->mlist = &prevdir; + } + + if (!samepair) { + lfs_fs_prepmove(lfs, newoldid, oldcwd.pair); + } + + // move over all attributes + err = lfs_dir_commit(lfs, &newcwd, LFS_MKATTRS( + {LFS_MKTAG_IF(prevtag != LFS_ERR_NOENT, + LFS_TYPE_DELETE, newid, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_CREATE, newid, 0), NULL}, + {LFS_MKTAG(lfs_tag_type3(oldtag), newid, strlen(newpath)), newpath}, + {LFS_MKTAG(LFS_FROM_MOVE, newid, lfs_tag_id(oldtag)), &oldcwd}, + {LFS_MKTAG_IF(samepair, + LFS_TYPE_DELETE, newoldid, 0), NULL})); + if (err) { + lfs->mlist = prevdir.next; + LFS_TRACE("lfs_rename -> %d", err); + return err; + } + + // let commit clean up after move (if we're different! otherwise move + // logic already fixed it for us) + if (!samepair && lfs_gstate_hasmove(&lfs->gstate)) { + // prep gstate and delete move id + lfs_fs_prepmove(lfs, 0x3ff, NULL); + err = lfs_dir_commit(lfs, &oldcwd, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(oldtag), 0), NULL})); + if (err) { + lfs->mlist = prevdir.next; + LFS_TRACE("lfs_rename -> %d", err); + return err; + } + } + + lfs->mlist = prevdir.next; + if (prevtag != LFS_ERR_NOENT && lfs_tag_type3(prevtag) == LFS_TYPE_DIR) { + // fix orphan + lfs_fs_preporphans(lfs, -1); + + err = lfs_fs_pred(lfs, prevdir.m.pair, &newcwd); + if (err) { + LFS_TRACE("lfs_rename -> %d", err); + return err; + } + + err = lfs_dir_drop(lfs, &newcwd, &prevdir.m); + if (err) { + LFS_TRACE("lfs_rename -> %d", err); + return err; + } + } + + LFS_TRACE("lfs_rename -> %d", 0); + return 0; +} + +lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, + uint8_t type, void *buffer, lfs_size_t size) { + LFS_TRACE("lfs_getattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", + (void*)lfs, path, type, buffer, size); + lfs_mdir_t cwd; + lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); + if (tag < 0) { + LFS_TRACE("lfs_getattr -> %"PRId32, tag); + return tag; + } + + uint16_t id = lfs_tag_id(tag); + if (id == 0x3ff) { + // special case for root + id = 0; + int err = lfs_dir_fetch(lfs, &cwd, lfs->root); + if (err) { + LFS_TRACE("lfs_getattr -> %d", err); + return err; + } + } + + tag = lfs_dir_get(lfs, &cwd, LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_USERATTR + type, + id, lfs_min(size, lfs->attr_max)), + buffer); + if (tag < 0) { + if (tag == LFS_ERR_NOENT) { + LFS_TRACE("lfs_getattr -> %d", LFS_ERR_NOATTR); + return LFS_ERR_NOATTR; + } + + LFS_TRACE("lfs_getattr -> %"PRId32, tag); + return tag; + } + + size = lfs_tag_size(tag); + LFS_TRACE("lfs_getattr -> %"PRId32, size); + return size; +} + +static int lfs_commitattr(lfs_t *lfs, const char *path, + uint8_t type, const void *buffer, lfs_size_t size) { + lfs_mdir_t cwd; + lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); + if (tag < 0) { + return tag; + } + + uint16_t id = lfs_tag_id(tag); + if (id == 0x3ff) { + // special case for root + id = 0; + int err = lfs_dir_fetch(lfs, &cwd, lfs->root); + if (err) { + return err; + } + } + + return lfs_dir_commit(lfs, &cwd, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_USERATTR + type, id, size), buffer})); +} + +int lfs_setattr(lfs_t *lfs, const char *path, + uint8_t type, const void *buffer, lfs_size_t size) { + LFS_TRACE("lfs_setattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", + (void*)lfs, path, type, buffer, size); + if (size > lfs->attr_max) { + LFS_TRACE("lfs_setattr -> %d", LFS_ERR_NOSPC); + return LFS_ERR_NOSPC; + } + + int err = lfs_commitattr(lfs, path, type, buffer, size); + LFS_TRACE("lfs_setattr -> %d", err); + return err; +} + +int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type) { + LFS_TRACE("lfs_removeattr(%p, \"%s\", %"PRIu8")", (void*)lfs, path, type); + int err = lfs_commitattr(lfs, path, type, NULL, 0x3ff); + LFS_TRACE("lfs_removeattr -> %d", err); + return err; +} + + +/// Filesystem operations /// +static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { + lfs->cfg = cfg; + int err = 0; + + // validate that the lfs-cfg sizes were initiated properly before + // performing any arithmetic logics with them + LFS_ASSERT(lfs->cfg->read_size != 0); + LFS_ASSERT(lfs->cfg->prog_size != 0); + LFS_ASSERT(lfs->cfg->cache_size != 0); + + // check that block size is a multiple of cache size is a multiple + // of prog and read sizes + LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->read_size == 0); + LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->prog_size == 0); + LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->cache_size == 0); + + // check that the block size is large enough to fit ctz pointers + LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4)) + <= lfs->cfg->block_size); + + // block_cycles = 0 is no longer supported. + // + // block_cycles is the number of erase cycles before littlefs evicts + // metadata logs as a part of wear leveling. Suggested values are in the + // range of 100-1000, or set block_cycles to -1 to disable block-level + // wear-leveling. + LFS_ASSERT(lfs->cfg->block_cycles != 0); + + + // setup read cache + if (lfs->cfg->read_buffer) { + lfs->rcache.buffer = lfs->cfg->read_buffer; + } else { + lfs->rcache.buffer = lfs_malloc(lfs->cfg->cache_size); + if (!lfs->rcache.buffer) { + err = LFS_ERR_NOMEM; + goto cleanup; + } + } + + // setup program cache + if (lfs->cfg->prog_buffer) { + lfs->pcache.buffer = lfs->cfg->prog_buffer; + } else { + lfs->pcache.buffer = lfs_malloc(lfs->cfg->cache_size); + if (!lfs->pcache.buffer) { + err = LFS_ERR_NOMEM; + goto cleanup; + } + } + + // zero to avoid information leaks + lfs_cache_zero(lfs, &lfs->rcache); + lfs_cache_zero(lfs, &lfs->pcache); + + // setup lookahead, must be multiple of 64-bits, 32-bit aligned + LFS_ASSERT(lfs->cfg->lookahead_size > 0); + LFS_ASSERT(lfs->cfg->lookahead_size % 8 == 0 && + (uintptr_t)lfs->cfg->lookahead_buffer % 4 == 0); + if (lfs->cfg->lookahead_buffer) { + lfs->free.buffer = lfs->cfg->lookahead_buffer; + } else { + lfs->free.buffer = lfs_malloc(lfs->cfg->lookahead_size); + if (!lfs->free.buffer) { + err = LFS_ERR_NOMEM; + goto cleanup; + } + } + + // check that the size limits are sane + LFS_ASSERT(lfs->cfg->name_max <= LFS_NAME_MAX); + lfs->name_max = lfs->cfg->name_max; + if (!lfs->name_max) { + lfs->name_max = LFS_NAME_MAX; + } + + LFS_ASSERT(lfs->cfg->file_max <= LFS_FILE_MAX); + lfs->file_max = lfs->cfg->file_max; + if (!lfs->file_max) { + lfs->file_max = LFS_FILE_MAX; + } + + LFS_ASSERT(lfs->cfg->attr_max <= LFS_ATTR_MAX); + lfs->attr_max = lfs->cfg->attr_max; + if (!lfs->attr_max) { + lfs->attr_max = LFS_ATTR_MAX; + } + + // setup default state + lfs->root[0] = LFS_BLOCK_NULL; + lfs->root[1] = LFS_BLOCK_NULL; + lfs->mlist = NULL; + lfs->seed = 0; + lfs->gdisk = (lfs_gstate_t){0}; + lfs->gstate = (lfs_gstate_t){0}; + lfs->gdelta = (lfs_gstate_t){0}; +#ifdef LFS_MIGRATE + lfs->lfs1 = NULL; +#endif + + return 0; + +cleanup: + lfs_deinit(lfs); + return err; +} + +static int lfs_deinit(lfs_t *lfs) { + // free allocated memory + if (!lfs->cfg->read_buffer) { + lfs_free(lfs->rcache.buffer); + } + + if (!lfs->cfg->prog_buffer) { + lfs_free(lfs->pcache.buffer); + } + + if (!lfs->cfg->lookahead_buffer) { + lfs_free(lfs->free.buffer); + } + + return 0; +} + +int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { + LFS_TRACE("lfs_format(%p, %p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32", " + ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " + ".lookahead_size=%"PRIu32", .read_buffer=%p, " + ".prog_buffer=%p, .lookahead_buffer=%p, " + ".name_max=%"PRIu32", .file_max=%"PRIu32", " + ".attr_max=%"PRIu32"})", + (void*)lfs, (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, + cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, + cfg->name_max, cfg->file_max, cfg->attr_max); + int err = 0; + { + err = lfs_init(lfs, cfg); + if (err) { + LFS_TRACE("lfs_format -> %d", err); + return err; + } + + // create free lookahead + memset(lfs->free.buffer, 0, lfs->cfg->lookahead_size); + lfs->free.off = 0; + lfs->free.size = lfs_min(8*lfs->cfg->lookahead_size, + lfs->cfg->block_count); + lfs->free.i = 0; + lfs_alloc_ack(lfs); + + // create root dir + lfs_mdir_t root; + err = lfs_dir_alloc(lfs, &root); + if (err) { + goto cleanup; + } + + // write one superblock + lfs_superblock_t superblock = { + .version = LFS_DISK_VERSION, + .block_size = lfs->cfg->block_size, + .block_count = lfs->cfg->block_count, + .name_max = lfs->name_max, + .file_max = lfs->file_max, + .attr_max = lfs->attr_max, + }; + + lfs_superblock_tole32(&superblock); + err = lfs_dir_commit(lfs, &root, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_CREATE, 0, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), "littlefs"}, + {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock})); + if (err) { + goto cleanup; + } + + // sanity check that fetch works + err = lfs_dir_fetch(lfs, &root, (const lfs_block_t[2]){0, 1}); + if (err) { + goto cleanup; + } + + // force compaction to prevent accidentally mounting any + // older version of littlefs that may live on disk + root.erased = false; + err = lfs_dir_commit(lfs, &root, NULL, 0); + if (err) { + goto cleanup; + } + } + +cleanup: + lfs_deinit(lfs); + LFS_TRACE("lfs_format -> %d", err); + return err; +} + +int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { + LFS_TRACE("lfs_mount(%p, %p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32", " + ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " + ".lookahead_size=%"PRIu32", .read_buffer=%p, " + ".prog_buffer=%p, .lookahead_buffer=%p, " + ".name_max=%"PRIu32", .file_max=%"PRIu32", " + ".attr_max=%"PRIu32"})", + (void*)lfs, (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, + cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, + cfg->name_max, cfg->file_max, cfg->attr_max); + int err = lfs_init(lfs, cfg); + if (err) { + LFS_TRACE("lfs_mount -> %d", err); + return err; + } + + // scan directory blocks for superblock and any global updates + lfs_mdir_t dir = {.tail = {0, 1}}; + lfs_block_t cycle = 0; + while (!lfs_pair_isnull(dir.tail)) { + if (cycle >= lfs->cfg->block_count/2) { + // loop detected + err = LFS_ERR_CORRUPT; + goto cleanup; + } + cycle += 1; + + // fetch next block in tail list + lfs_stag_t tag = lfs_dir_fetchmatch(lfs, &dir, dir.tail, + LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), + NULL, + lfs_dir_find_match, &(struct lfs_dir_find_match){ + lfs, "littlefs", 8}); + if (tag < 0) { + err = tag; + goto cleanup; + } + + // has superblock? + if (tag && !lfs_tag_isdelete(tag)) { + // update root + lfs->root[0] = dir.pair[0]; + lfs->root[1] = dir.pair[1]; + + // grab superblock + lfs_superblock_t superblock; + tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock); + if (tag < 0) { + err = tag; + goto cleanup; + } + lfs_superblock_fromle32(&superblock); + + // check version + uint16_t major_version = (0xffff & (superblock.version >> 16)); + uint16_t minor_version = (0xffff & (superblock.version >> 0)); + if ((major_version != LFS_DISK_VERSION_MAJOR || + minor_version > LFS_DISK_VERSION_MINOR)) { + LFS_ERROR("Invalid version v%"PRIu16".%"PRIu16, + major_version, minor_version); + err = LFS_ERR_INVAL; + goto cleanup; + } + + // check superblock configuration + if (superblock.name_max) { + if (superblock.name_max > lfs->name_max) { + LFS_ERROR("Unsupported name_max (%"PRIu32" > %"PRIu32")", + superblock.name_max, lfs->name_max); + err = LFS_ERR_INVAL; + goto cleanup; + } + + lfs->name_max = superblock.name_max; + } + + if (superblock.file_max) { + if (superblock.file_max > lfs->file_max) { + LFS_ERROR("Unsupported file_max (%"PRIu32" > %"PRIu32")", + superblock.file_max, lfs->file_max); + err = LFS_ERR_INVAL; + goto cleanup; + } + + lfs->file_max = superblock.file_max; + } + + if (superblock.attr_max) { + if (superblock.attr_max > lfs->attr_max) { + LFS_ERROR("Unsupported attr_max (%"PRIu32" > %"PRIu32")", + superblock.attr_max, lfs->attr_max); + err = LFS_ERR_INVAL; + goto cleanup; + } + + lfs->attr_max = superblock.attr_max; + } + } + + // has gstate? + err = lfs_dir_getgstate(lfs, &dir, &lfs->gstate); + if (err) { + goto cleanup; + } + } + + // found superblock? + if (lfs_pair_isnull(lfs->root)) { + err = LFS_ERR_INVAL; + goto cleanup; + } + + // update littlefs with gstate + if (!lfs_gstate_iszero(&lfs->gstate)) { + LFS_DEBUG("Found pending gstate 0x%08"PRIx32"%08"PRIx32"%08"PRIx32, + lfs->gstate.tag, + lfs->gstate.pair[0], + lfs->gstate.pair[1]); + } + lfs->gstate.tag += !lfs_tag_isvalid(lfs->gstate.tag); + lfs->gdisk = lfs->gstate; + + // setup free lookahead + lfs_alloc_reset(lfs); + + LFS_TRACE("lfs_mount -> %d", 0); + return 0; + +cleanup: + lfs_unmount(lfs); + LFS_TRACE("lfs_mount -> %d", err); + return err; +} + +int lfs_unmount(lfs_t *lfs) { + LFS_TRACE("lfs_unmount(%p)", (void*)lfs); + int err = lfs_deinit(lfs); + LFS_TRACE("lfs_unmount -> %d", err); + return err; +} + + +/// Filesystem filesystem operations /// +int lfs_fs_traverseraw(lfs_t *lfs, + int (*cb)(void *data, lfs_block_t block), void *data, + bool includeorphans) { + // iterate over metadata pairs + lfs_mdir_t dir = {.tail = {0, 1}}; + +#ifdef LFS_MIGRATE + // also consider v1 blocks during migration + if (lfs->lfs1) { + int err = lfs1_traverse(lfs, cb, data); + if (err) { + return err; + } + + dir.tail[0] = lfs->root[0]; + dir.tail[1] = lfs->root[1]; + } +#endif + + lfs_block_t cycle = 0; + while (!lfs_pair_isnull(dir.tail)) { + if (cycle >= lfs->cfg->block_count/2) { + // loop detected + return LFS_ERR_CORRUPT; + } + cycle += 1; + + for (int i = 0; i < 2; i++) { + int err = cb(data, dir.tail[i]); + if (err) { + return err; + } + } + + // iterate through ids in directory + int err = lfs_dir_fetch(lfs, &dir, dir.tail); + if (err) { + return err; + } + + for (uint16_t id = 0; id < dir.count; id++) { + struct lfs_ctz ctz; + lfs_stag_t tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, id, sizeof(ctz)), &ctz); + if (tag < 0) { + if (tag == LFS_ERR_NOENT) { + continue; + } + return tag; + } + lfs_ctz_fromle32(&ctz); + + if (lfs_tag_type3(tag) == LFS_TYPE_CTZSTRUCT) { + err = lfs_ctz_traverse(lfs, NULL, &lfs->rcache, + ctz.head, ctz.size, cb, data); + if (err) { + return err; + } + } else if (includeorphans && + lfs_tag_type3(tag) == LFS_TYPE_DIRSTRUCT) { + for (int i = 0; i < 2; i++) { + err = cb(data, (&ctz.head)[i]); + if (err) { + return err; + } + } + } + } + } + + // iterate over any open files + for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) { + if (f->type != LFS_TYPE_REG) { + continue; + } + + if ((f->flags & LFS_F_DIRTY) && !(f->flags & LFS_F_INLINE)) { + int err = lfs_ctz_traverse(lfs, &f->cache, &lfs->rcache, + f->ctz.head, f->ctz.size, cb, data); + if (err) { + return err; + } + } + + if ((f->flags & LFS_F_WRITING) && !(f->flags & LFS_F_INLINE)) { + int err = lfs_ctz_traverse(lfs, &f->cache, &lfs->rcache, + f->block, f->pos, cb, data); + if (err) { + return err; + } + } + } + + return 0; +} + +int lfs_fs_traverse(lfs_t *lfs, + int (*cb)(void *data, lfs_block_t block), void *data) { + LFS_TRACE("lfs_fs_traverse(%p, %p, %p)", + (void*)lfs, (void*)(uintptr_t)cb, data); + int err = lfs_fs_traverseraw(lfs, cb, data, true); + LFS_TRACE("lfs_fs_traverse -> %d", 0); + return err; +} + +static int lfs_fs_pred(lfs_t *lfs, + const lfs_block_t pair[2], lfs_mdir_t *pdir) { + // iterate over all directory directory entries + pdir->tail[0] = 0; + pdir->tail[1] = 1; + lfs_block_t cycle = 0; + while (!lfs_pair_isnull(pdir->tail)) { + if (cycle >= lfs->cfg->block_count/2) { + // loop detected + return LFS_ERR_CORRUPT; + } + cycle += 1; + + if (lfs_pair_cmp(pdir->tail, pair) == 0) { + return 0; + } + + int err = lfs_dir_fetch(lfs, pdir, pdir->tail); + if (err) { + return err; + } + } + + return LFS_ERR_NOENT; +} + +struct lfs_fs_parent_match { + lfs_t *lfs; + const lfs_block_t pair[2]; +}; + +static int lfs_fs_parent_match(void *data, + lfs_tag_t tag, const void *buffer) { + struct lfs_fs_parent_match *find = data; + lfs_t *lfs = find->lfs; + const struct lfs_diskoff *disk = buffer; + (void)tag; + + lfs_block_t child[2]; + int err = lfs_bd_read(lfs, + &lfs->pcache, &lfs->rcache, lfs->cfg->block_size, + disk->block, disk->off, &child, sizeof(child)); + if (err) { + return err; + } + + lfs_pair_fromle32(child); + return (lfs_pair_cmp(child, find->pair) == 0) ? LFS_CMP_EQ : LFS_CMP_LT; +} + +static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2], + lfs_mdir_t *parent) { + // use fetchmatch with callback to find pairs + parent->tail[0] = 0; + parent->tail[1] = 1; + lfs_block_t cycle = 0; + while (!lfs_pair_isnull(parent->tail)) { + if (cycle >= lfs->cfg->block_count/2) { + // loop detected + return LFS_ERR_CORRUPT; + } + cycle += 1; + + lfs_stag_t tag = lfs_dir_fetchmatch(lfs, parent, parent->tail, + LFS_MKTAG(0x7ff, 0, 0x3ff), + LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 0, 8), + NULL, + lfs_fs_parent_match, &(struct lfs_fs_parent_match){ + lfs, {pair[0], pair[1]}}); + if (tag && tag != LFS_ERR_NOENT) { + return tag; + } + } + + return LFS_ERR_NOENT; +} + +static int lfs_fs_relocate(lfs_t *lfs, + const lfs_block_t oldpair[2], lfs_block_t newpair[2]) { + // update internal root + if (lfs_pair_cmp(oldpair, lfs->root) == 0) { + lfs->root[0] = newpair[0]; + lfs->root[1] = newpair[1]; + } + + // update internally tracked dirs + for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) { + if (lfs_pair_cmp(oldpair, d->m.pair) == 0) { + d->m.pair[0] = newpair[0]; + d->m.pair[1] = newpair[1]; + } + + if (d->type == LFS_TYPE_DIR && + lfs_pair_cmp(oldpair, ((lfs_dir_t*)d)->head) == 0) { + ((lfs_dir_t*)d)->head[0] = newpair[0]; + ((lfs_dir_t*)d)->head[1] = newpair[1]; + } + } + + // find parent + lfs_mdir_t parent; + lfs_stag_t tag = lfs_fs_parent(lfs, oldpair, &parent); + if (tag < 0 && tag != LFS_ERR_NOENT) { + return tag; + } + + if (tag != LFS_ERR_NOENT) { + // update disk, this creates a desync + lfs_fs_preporphans(lfs, +1); + + // fix pending move in this pair? this looks like an optimization but + // is in fact _required_ since relocating may outdate the move. + uint16_t moveid = 0x3ff; + if (lfs_gstate_hasmovehere(&lfs->gstate, parent.pair)) { + moveid = lfs_tag_id(lfs->gstate.tag); + LFS_DEBUG("Fixing move while relocating " + "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n", + parent.pair[0], parent.pair[1], moveid); + lfs_fs_prepmove(lfs, 0x3ff, NULL); + if (moveid < lfs_tag_id(tag)) { + tag -= LFS_MKTAG(0, 1, 0); + } + } + + lfs_pair_tole32(newpair); + int err = lfs_dir_commit(lfs, &parent, LFS_MKATTRS( + {LFS_MKTAG_IF(moveid != 0x3ff, + LFS_TYPE_DELETE, moveid, 0), NULL}, + {tag, newpair})); + lfs_pair_fromle32(newpair); + if (err) { + return err; + } + + // next step, clean up orphans + lfs_fs_preporphans(lfs, -1); + } + + // find pred + int err = lfs_fs_pred(lfs, oldpair, &parent); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + // if we can't find dir, it must be new + if (err != LFS_ERR_NOENT) { + // fix pending move in this pair? this looks like an optimization but + // is in fact _required_ since relocating may outdate the move. + uint16_t moveid = 0x3ff; + if (lfs_gstate_hasmovehere(&lfs->gstate, parent.pair)) { + moveid = lfs_tag_id(lfs->gstate.tag); + LFS_DEBUG("Fixing move while relocating " + "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n", + parent.pair[0], parent.pair[1], moveid); + lfs_fs_prepmove(lfs, 0x3ff, NULL); + } + + // replace bad pair, either we clean up desync, or no desync occured + lfs_pair_tole32(newpair); + err = lfs_dir_commit(lfs, &parent, LFS_MKATTRS( + {LFS_MKTAG_IF(moveid != 0x3ff, + LFS_TYPE_DELETE, moveid, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_TAIL + parent.split, 0x3ff, 8), newpair})); + lfs_pair_fromle32(newpair); + if (err) { + return err; + } + } + + return 0; +} + +static void lfs_fs_preporphans(lfs_t *lfs, int8_t orphans) { + LFS_ASSERT(lfs_tag_size(lfs->gstate.tag) > 0 || orphans >= 0); + lfs->gstate.tag += orphans; + lfs->gstate.tag = ((lfs->gstate.tag & ~LFS_MKTAG(0x800, 0, 0)) | + ((uint32_t)lfs_gstate_hasorphans(&lfs->gstate) << 31)); +} + +static void lfs_fs_prepmove(lfs_t *lfs, + uint16_t id, const lfs_block_t pair[2]) { + lfs->gstate.tag = ((lfs->gstate.tag & ~LFS_MKTAG(0x7ff, 0x3ff, 0)) | + ((id != 0x3ff) ? LFS_MKTAG(LFS_TYPE_DELETE, id, 0) : 0)); + lfs->gstate.pair[0] = (id != 0x3ff) ? pair[0] : 0; + lfs->gstate.pair[1] = (id != 0x3ff) ? pair[1] : 0; +} + +static int lfs_fs_demove(lfs_t *lfs) { + if (!lfs_gstate_hasmove(&lfs->gdisk)) { + return 0; + } + + // Fix bad moves + LFS_DEBUG("Fixing move {0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16, + lfs->gdisk.pair[0], + lfs->gdisk.pair[1], + lfs_tag_id(lfs->gdisk.tag)); + + // fetch and delete the moved entry + lfs_mdir_t movedir; + int err = lfs_dir_fetch(lfs, &movedir, lfs->gdisk.pair); + if (err) { + return err; + } + + // prep gstate and delete move id + uint16_t moveid = lfs_tag_id(lfs->gdisk.tag); + lfs_fs_prepmove(lfs, 0x3ff, NULL); + err = lfs_dir_commit(lfs, &movedir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_DELETE, moveid, 0), NULL})); + if (err) { + return err; + } + + return 0; +} + +static int lfs_fs_deorphan(lfs_t *lfs) { + if (!lfs_gstate_hasorphans(&lfs->gstate)) { + return 0; + } + + // Fix any orphans + lfs_mdir_t pdir = {.split = true, .tail = {0, 1}}; + lfs_mdir_t dir; + + // iterate over all directory directory entries + while (!lfs_pair_isnull(pdir.tail)) { + int err = lfs_dir_fetch(lfs, &dir, pdir.tail); + if (err) { + return err; + } + + // check head blocks for orphans + if (!pdir.split) { + // check if we have a parent + lfs_mdir_t parent; + lfs_stag_t tag = lfs_fs_parent(lfs, pdir.tail, &parent); + if (tag < 0 && tag != LFS_ERR_NOENT) { + return tag; + } + + if (tag == LFS_ERR_NOENT) { + // we are an orphan + LFS_DEBUG("Fixing orphan {0x%"PRIx32", 0x%"PRIx32"}", + pdir.tail[0], pdir.tail[1]); + + err = lfs_dir_drop(lfs, &pdir, &dir); + if (err) { + return err; + } + + // refetch tail + continue; + } + + lfs_block_t pair[2]; + lfs_stag_t res = lfs_dir_get(lfs, &parent, + LFS_MKTAG(0x7ff, 0x3ff, 0), tag, pair); + if (res < 0) { + return res; + } + lfs_pair_fromle32(pair); + + if (!lfs_pair_sync(pair, pdir.tail)) { + // we have desynced + LFS_DEBUG("Fixing half-orphan {0x%"PRIx32", 0x%"PRIx32"} " + "-> {0x%"PRIx32", 0x%"PRIx32"}", + pdir.tail[0], pdir.tail[1], pair[0], pair[1]); + + lfs_pair_tole32(pair); + err = lfs_dir_commit(lfs, &pdir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), pair})); + lfs_pair_fromle32(pair); + if (err) { + return err; + } + + // refetch tail + continue; + } + } + + pdir = dir; + } + + // mark orphans as fixed + lfs_fs_preporphans(lfs, -lfs_gstate_getorphans(&lfs->gstate)); + return 0; +} + +static int lfs_fs_forceconsistency(lfs_t *lfs) { + int err = lfs_fs_demove(lfs); + if (err) { + return err; + } + + err = lfs_fs_deorphan(lfs); + if (err) { + return err; + } + + return 0; +} + +static int lfs_fs_size_count(void *p, lfs_block_t block) { + (void)block; + lfs_size_t *size = p; + *size += 1; + return 0; +} + +lfs_ssize_t lfs_fs_size(lfs_t *lfs) { + LFS_TRACE("lfs_fs_size(%p)", (void*)lfs); + lfs_size_t size = 0; + int err = lfs_fs_traverseraw(lfs, lfs_fs_size_count, &size, false); + if (err) { + LFS_TRACE("lfs_fs_size -> %d", err); + return err; + } + + LFS_TRACE("lfs_fs_size -> %d", err); + return size; +} + +#ifdef LFS_MIGRATE +////// Migration from littelfs v1 below this ////// + +/// Version info /// + +// Software library version +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS1_VERSION 0x00010007 +#define LFS1_VERSION_MAJOR (0xffff & (LFS1_VERSION >> 16)) +#define LFS1_VERSION_MINOR (0xffff & (LFS1_VERSION >> 0)) + +// Version of On-disk data structures +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS1_DISK_VERSION 0x00010001 +#define LFS1_DISK_VERSION_MAJOR (0xffff & (LFS1_DISK_VERSION >> 16)) +#define LFS1_DISK_VERSION_MINOR (0xffff & (LFS1_DISK_VERSION >> 0)) + + +/// v1 Definitions /// + +// File types +enum lfs1_type { + LFS1_TYPE_REG = 0x11, + LFS1_TYPE_DIR = 0x22, + LFS1_TYPE_SUPERBLOCK = 0x2e, +}; + +typedef struct lfs1 { + lfs_block_t root[2]; +} lfs1_t; + +typedef struct lfs1_entry { + lfs_off_t off; + + struct lfs1_disk_entry { + uint8_t type; + uint8_t elen; + uint8_t alen; + uint8_t nlen; + union { + struct { + lfs_block_t head; + lfs_size_t size; + } file; + lfs_block_t dir[2]; + } u; + } d; +} lfs1_entry_t; + +typedef struct lfs1_dir { + struct lfs1_dir *next; + lfs_block_t pair[2]; + lfs_off_t off; + + lfs_block_t head[2]; + lfs_off_t pos; + + struct lfs1_disk_dir { + uint32_t rev; + lfs_size_t size; + lfs_block_t tail[2]; + } d; +} lfs1_dir_t; + +typedef struct lfs1_superblock { + lfs_off_t off; + + struct lfs1_disk_superblock { + uint8_t type; + uint8_t elen; + uint8_t alen; + uint8_t nlen; + lfs_block_t root[2]; + uint32_t block_size; + uint32_t block_count; + uint32_t version; + char magic[8]; + } d; +} lfs1_superblock_t; + + +/// Low-level wrappers v1->v2 /// +static void lfs1_crc(uint32_t *crc, const void *buffer, size_t size) { + *crc = lfs_crc(*crc, buffer, size); +} + +static int lfs1_bd_read(lfs_t *lfs, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) { + // if we ever do more than writes to alternating pairs, + // this may need to consider pcache + return lfs_bd_read(lfs, &lfs->pcache, &lfs->rcache, size, + block, off, buffer, size); +} + +static int lfs1_bd_crc(lfs_t *lfs, lfs_block_t block, + lfs_off_t off, lfs_size_t size, uint32_t *crc) { + for (lfs_off_t i = 0; i < size; i++) { + uint8_t c; + int err = lfs1_bd_read(lfs, block, off+i, &c, 1); + if (err) { + return err; + } + + lfs1_crc(crc, &c, 1); + } + + return 0; +} + + +/// Endian swapping functions /// +static void lfs1_dir_fromle32(struct lfs1_disk_dir *d) { + d->rev = lfs_fromle32(d->rev); + d->size = lfs_fromle32(d->size); + d->tail[0] = lfs_fromle32(d->tail[0]); + d->tail[1] = lfs_fromle32(d->tail[1]); +} + +static void lfs1_dir_tole32(struct lfs1_disk_dir *d) { + d->rev = lfs_tole32(d->rev); + d->size = lfs_tole32(d->size); + d->tail[0] = lfs_tole32(d->tail[0]); + d->tail[1] = lfs_tole32(d->tail[1]); +} + +static void lfs1_entry_fromle32(struct lfs1_disk_entry *d) { + d->u.dir[0] = lfs_fromle32(d->u.dir[0]); + d->u.dir[1] = lfs_fromle32(d->u.dir[1]); +} + +static void lfs1_entry_tole32(struct lfs1_disk_entry *d) { + d->u.dir[0] = lfs_tole32(d->u.dir[0]); + d->u.dir[1] = lfs_tole32(d->u.dir[1]); +} + +static void lfs1_superblock_fromle32(struct lfs1_disk_superblock *d) { + d->root[0] = lfs_fromle32(d->root[0]); + d->root[1] = lfs_fromle32(d->root[1]); + d->block_size = lfs_fromle32(d->block_size); + d->block_count = lfs_fromle32(d->block_count); + d->version = lfs_fromle32(d->version); +} + + +///// Metadata pair and directory operations /// +static inline lfs_size_t lfs1_entry_size(const lfs1_entry_t *entry) { + return 4 + entry->d.elen + entry->d.alen + entry->d.nlen; +} + +static int lfs1_dir_fetch(lfs_t *lfs, + lfs1_dir_t *dir, const lfs_block_t pair[2]) { + // copy out pair, otherwise may be aliasing dir + const lfs_block_t tpair[2] = {pair[0], pair[1]}; + bool valid = false; + + // check both blocks for the most recent revision + for (int i = 0; i < 2; i++) { + struct lfs1_disk_dir test; + int err = lfs1_bd_read(lfs, tpair[i], 0, &test, sizeof(test)); + lfs1_dir_fromle32(&test); + if (err) { + if (err == LFS_ERR_CORRUPT) { + continue; + } + return err; + } + + if (valid && lfs_scmp(test.rev, dir->d.rev) < 0) { + continue; + } + + if ((0x7fffffff & test.size) < sizeof(test)+4 || + (0x7fffffff & test.size) > lfs->cfg->block_size) { + continue; + } + + uint32_t crc = 0xffffffff; + lfs1_dir_tole32(&test); + lfs1_crc(&crc, &test, sizeof(test)); + lfs1_dir_fromle32(&test); + err = lfs1_bd_crc(lfs, tpair[i], sizeof(test), + (0x7fffffff & test.size) - sizeof(test), &crc); + if (err) { + if (err == LFS_ERR_CORRUPT) { + continue; + } + return err; + } + + if (crc != 0) { + continue; + } + + valid = true; + + // setup dir in case it's valid + dir->pair[0] = tpair[(i+0) % 2]; + dir->pair[1] = tpair[(i+1) % 2]; + dir->off = sizeof(dir->d); + dir->d = test; + } + + if (!valid) { + LFS_ERROR("Corrupted dir pair at {0x%"PRIx32", 0x%"PRIx32"}", + tpair[0], tpair[1]); + return LFS_ERR_CORRUPT; + } + + return 0; +} + +static int lfs1_dir_next(lfs_t *lfs, lfs1_dir_t *dir, lfs1_entry_t *entry) { + while (dir->off + sizeof(entry->d) > (0x7fffffff & dir->d.size)-4) { + if (!(0x80000000 & dir->d.size)) { + entry->off = dir->off; + return LFS_ERR_NOENT; + } + + int err = lfs1_dir_fetch(lfs, dir, dir->d.tail); + if (err) { + return err; + } + + dir->off = sizeof(dir->d); + dir->pos += sizeof(dir->d) + 4; + } + + int err = lfs1_bd_read(lfs, dir->pair[0], dir->off, + &entry->d, sizeof(entry->d)); + lfs1_entry_fromle32(&entry->d); + if (err) { + return err; + } + + entry->off = dir->off; + dir->off += lfs1_entry_size(entry); + dir->pos += lfs1_entry_size(entry); + return 0; +} + +/// littlefs v1 specific operations /// +int lfs1_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) { + if (lfs_pair_isnull(lfs->lfs1->root)) { + return 0; + } + + // iterate over metadata pairs + lfs1_dir_t dir; + lfs1_entry_t entry; + lfs_block_t cwd[2] = {0, 1}; + + while (true) { + for (int i = 0; i < 2; i++) { + int err = cb(data, cwd[i]); + if (err) { + return err; + } + } + + int err = lfs1_dir_fetch(lfs, &dir, cwd); + if (err) { + return err; + } + + // iterate over contents + while (dir.off + sizeof(entry.d) <= (0x7fffffff & dir.d.size)-4) { + err = lfs1_bd_read(lfs, dir.pair[0], dir.off, + &entry.d, sizeof(entry.d)); + lfs1_entry_fromle32(&entry.d); + if (err) { + return err; + } + + dir.off += lfs1_entry_size(&entry); + if ((0x70 & entry.d.type) == (0x70 & LFS1_TYPE_REG)) { + err = lfs_ctz_traverse(lfs, NULL, &lfs->rcache, + entry.d.u.file.head, entry.d.u.file.size, cb, data); + if (err) { + return err; + } + } + } + + // we also need to check if we contain a threaded v2 directory + lfs_mdir_t dir2 = {.split=true, .tail={cwd[0], cwd[1]}}; + while (dir2.split) { + err = lfs_dir_fetch(lfs, &dir2, dir2.tail); + if (err) { + break; + } + + for (int i = 0; i < 2; i++) { + err = cb(data, dir2.pair[i]); + if (err) { + return err; + } + } + } + + cwd[0] = dir.d.tail[0]; + cwd[1] = dir.d.tail[1]; + + if (lfs_pair_isnull(cwd)) { + break; + } + } + + return 0; +} + +static int lfs1_moved(lfs_t *lfs, const void *e) { + if (lfs_pair_isnull(lfs->lfs1->root)) { + return 0; + } + + // skip superblock + lfs1_dir_t cwd; + int err = lfs1_dir_fetch(lfs, &cwd, (const lfs_block_t[2]){0, 1}); + if (err) { + return err; + } + + // iterate over all directory directory entries + lfs1_entry_t entry; + while (!lfs_pair_isnull(cwd.d.tail)) { + err = lfs1_dir_fetch(lfs, &cwd, cwd.d.tail); + if (err) { + return err; + } + + while (true) { + err = lfs1_dir_next(lfs, &cwd, &entry); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + if (err == LFS_ERR_NOENT) { + break; + } + + if (!(0x80 & entry.d.type) && + memcmp(&entry.d.u, e, sizeof(entry.d.u)) == 0) { + return true; + } + } + } + + return false; +} + +/// Filesystem operations /// +static int lfs1_mount(lfs_t *lfs, struct lfs1 *lfs1, + const struct lfs_config *cfg) { + int err = 0; + { + err = lfs_init(lfs, cfg); + if (err) { + return err; + } + + lfs->lfs1 = lfs1; + lfs->lfs1->root[0] = LFS_BLOCK_NULL; + lfs->lfs1->root[1] = LFS_BLOCK_NULL; + + // setup free lookahead + lfs->free.off = 0; + lfs->free.size = 0; + lfs->free.i = 0; + lfs_alloc_ack(lfs); + + // load superblock + lfs1_dir_t dir; + lfs1_superblock_t superblock; + err = lfs1_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1}); + if (err && err != LFS_ERR_CORRUPT) { + goto cleanup; + } + + if (!err) { + err = lfs1_bd_read(lfs, dir.pair[0], sizeof(dir.d), + &superblock.d, sizeof(superblock.d)); + lfs1_superblock_fromle32(&superblock.d); + if (err) { + goto cleanup; + } + + lfs->lfs1->root[0] = superblock.d.root[0]; + lfs->lfs1->root[1] = superblock.d.root[1]; + } + + if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) { + LFS_ERROR("Invalid superblock at {0x%"PRIx32", 0x%"PRIx32"}", + 0, 1); + err = LFS_ERR_CORRUPT; + goto cleanup; + } + + uint16_t major_version = (0xffff & (superblock.d.version >> 16)); + uint16_t minor_version = (0xffff & (superblock.d.version >> 0)); + if ((major_version != LFS1_DISK_VERSION_MAJOR || + minor_version > LFS1_DISK_VERSION_MINOR)) { + LFS_ERROR("Invalid version v%d.%d", major_version, minor_version); + err = LFS_ERR_INVAL; + goto cleanup; + } + + return 0; + } + +cleanup: + lfs_deinit(lfs); + return err; +} + +static int lfs1_unmount(lfs_t *lfs) { + return lfs_deinit(lfs); +} + +/// v1 migration /// +int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) { + LFS_TRACE("lfs_migrate(%p, %p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32", " + ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " + ".lookahead_size=%"PRIu32", .read_buffer=%p, " + ".prog_buffer=%p, .lookahead_buffer=%p, " + ".name_max=%"PRIu32", .file_max=%"PRIu32", " + ".attr_max=%"PRIu32"})", + (void*)lfs, (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, + cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, + cfg->name_max, cfg->file_max, cfg->attr_max); + struct lfs1 lfs1; + int err = lfs1_mount(lfs, &lfs1, cfg); + if (err) { + LFS_TRACE("lfs_migrate -> %d", err); + return err; + } + + { + // iterate through each directory, copying over entries + // into new directory + lfs1_dir_t dir1; + lfs_mdir_t dir2; + dir1.d.tail[0] = lfs->lfs1->root[0]; + dir1.d.tail[1] = lfs->lfs1->root[1]; + while (!lfs_pair_isnull(dir1.d.tail)) { + // iterate old dir + err = lfs1_dir_fetch(lfs, &dir1, dir1.d.tail); + if (err) { + goto cleanup; + } + + // create new dir and bind as temporary pretend root + err = lfs_dir_alloc(lfs, &dir2); + if (err) { + goto cleanup; + } + + dir2.rev = dir1.d.rev; + dir1.head[0] = dir1.pair[0]; + dir1.head[1] = dir1.pair[1]; + lfs->root[0] = dir2.pair[0]; + lfs->root[1] = dir2.pair[1]; + + err = lfs_dir_commit(lfs, &dir2, NULL, 0); + if (err) { + goto cleanup; + } + + while (true) { + lfs1_entry_t entry1; + err = lfs1_dir_next(lfs, &dir1, &entry1); + if (err && err != LFS_ERR_NOENT) { + goto cleanup; + } + + if (err == LFS_ERR_NOENT) { + break; + } + + // check that entry has not been moved + if (entry1.d.type & 0x80) { + int moved = lfs1_moved(lfs, &entry1.d.u); + if (moved < 0) { + err = moved; + goto cleanup; + } + + if (moved) { + continue; + } + + entry1.d.type &= ~0x80; + } + + // also fetch name + char name[LFS_NAME_MAX+1]; + memset(name, 0, sizeof(name)); + err = lfs1_bd_read(lfs, dir1.pair[0], + entry1.off + 4+entry1.d.elen+entry1.d.alen, + name, entry1.d.nlen); + if (err) { + goto cleanup; + } + + bool isdir = (entry1.d.type == LFS1_TYPE_DIR); + + // create entry in new dir + err = lfs_dir_fetch(lfs, &dir2, lfs->root); + if (err) { + goto cleanup; + } + + uint16_t id; + err = lfs_dir_find(lfs, &dir2, &(const char*){name}, &id); + if (!(err == LFS_ERR_NOENT && id != 0x3ff)) { + err = (err < 0) ? err : LFS_ERR_EXIST; + goto cleanup; + } + + lfs1_entry_tole32(&entry1.d); + err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_CREATE, id, 0)}, + {LFS_MKTAG_IF_ELSE(isdir, + LFS_TYPE_DIR, id, entry1.d.nlen, + LFS_TYPE_REG, id, entry1.d.nlen), + name}, + {LFS_MKTAG_IF_ELSE(isdir, + LFS_TYPE_DIRSTRUCT, id, sizeof(entry1.d.u), + LFS_TYPE_CTZSTRUCT, id, sizeof(entry1.d.u)), + &entry1.d.u})); + lfs1_entry_fromle32(&entry1.d); + if (err) { + goto cleanup; + } + } + + if (!lfs_pair_isnull(dir1.d.tail)) { + // find last block and update tail to thread into fs + err = lfs_dir_fetch(lfs, &dir2, lfs->root); + if (err) { + goto cleanup; + } + + while (dir2.split) { + err = lfs_dir_fetch(lfs, &dir2, dir2.tail); + if (err) { + goto cleanup; + } + } + + lfs_pair_tole32(dir2.pair); + err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir1.d.tail})); + lfs_pair_fromle32(dir2.pair); + if (err) { + goto cleanup; + } + } + + // Copy over first block to thread into fs. Unfortunately + // if this fails there is not much we can do. + LFS_DEBUG("Migrating {0x%"PRIx32", 0x%"PRIx32"} " + "-> {0x%"PRIx32", 0x%"PRIx32"}", + lfs->root[0], lfs->root[1], dir1.head[0], dir1.head[1]); + + err = lfs_bd_erase(lfs, dir1.head[1]); + if (err) { + goto cleanup; + } + + err = lfs_dir_fetch(lfs, &dir2, lfs->root); + if (err) { + goto cleanup; + } + + for (lfs_off_t i = 0; i < dir2.off; i++) { + uint8_t dat; + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, dir2.off, + dir2.pair[0], i, &dat, 1); + if (err) { + goto cleanup; + } + + err = lfs_bd_prog(lfs, + &lfs->pcache, &lfs->rcache, true, + dir1.head[1], i, &dat, 1); + if (err) { + goto cleanup; + } + } + + err = lfs_bd_flush(lfs, &lfs->pcache, &lfs->rcache, true); + if (err) { + goto cleanup; + } + } + + // Create new superblock. This marks a successful migration! + err = lfs1_dir_fetch(lfs, &dir1, (const lfs_block_t[2]){0, 1}); + if (err) { + goto cleanup; + } + + dir2.pair[0] = dir1.pair[0]; + dir2.pair[1] = dir1.pair[1]; + dir2.rev = dir1.d.rev; + dir2.off = sizeof(dir2.rev); + dir2.etag = 0xffffffff; + dir2.count = 0; + dir2.tail[0] = lfs->lfs1->root[0]; + dir2.tail[1] = lfs->lfs1->root[1]; + dir2.erased = false; + dir2.split = true; + + lfs_superblock_t superblock = { + .version = LFS_DISK_VERSION, + .block_size = lfs->cfg->block_size, + .block_count = lfs->cfg->block_count, + .name_max = lfs->name_max, + .file_max = lfs->file_max, + .attr_max = lfs->attr_max, + }; + + lfs_superblock_tole32(&superblock); + err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_CREATE, 0, 0)}, + {LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), "littlefs"}, + {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock})); + if (err) { + goto cleanup; + } + + // sanity check that fetch works + err = lfs_dir_fetch(lfs, &dir2, (const lfs_block_t[2]){0, 1}); + if (err) { + goto cleanup; + } + + // force compaction to prevent accidentally mounting v1 + dir2.erased = false; + err = lfs_dir_commit(lfs, &dir2, NULL, 0); + if (err) { + goto cleanup; + } + } + +cleanup: + lfs1_unmount(lfs); + LFS_TRACE("lfs_migrate -> %d", err); + return err; +} + +#endif diff --git a/Firmware_V3/lib/LittleFS/littlefs/lfs.h b/Firmware_V3/lib/LittleFS/littlefs/lfs.h new file mode 100644 index 0000000..6153036 --- /dev/null +++ b/Firmware_V3/lib/LittleFS/littlefs/lfs.h @@ -0,0 +1,656 @@ +/* + * The little filesystem + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef LFS_H +#define LFS_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + +/// Version info /// + +// Software library version +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS_VERSION 0x00020002 +#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16)) +#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0)) + +// Version of On-disk data structures +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS_DISK_VERSION 0x00020000 +#define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16)) +#define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0)) + + +/// Definitions /// + +// Type definitions +typedef uint32_t lfs_size_t; +typedef uint32_t lfs_off_t; + +typedef int32_t lfs_ssize_t; +typedef int32_t lfs_soff_t; + +typedef uint32_t lfs_block_t; + +// Maximum name size in bytes, may be redefined to reduce the size of the +// info struct. Limited to <= 1022. Stored in superblock and must be +// respected by other littlefs drivers. +#ifndef LFS_NAME_MAX +//#define LFS_NAME_MAX 255 +#define LFS_NAME_MAX 39 +#endif + +// Maximum size of a file in bytes, may be redefined to limit to support other +// drivers. Limited on disk to <= 4294967296. However, above 2147483647 the +// functions lfs_file_seek, lfs_file_size, and lfs_file_tell will return +// incorrect values due to using signed integers. Stored in superblock and +// must be respected by other littlefs drivers. +#ifndef LFS_FILE_MAX +#define LFS_FILE_MAX 2147483647 +#endif + +// Maximum size of custom attributes in bytes, may be redefined, but there is +// no real benefit to using a smaller LFS_ATTR_MAX. Limited to <= 1022. +#ifndef LFS_ATTR_MAX +#define LFS_ATTR_MAX 1022 +#endif + +// Possible error codes, these are negative to allow +// valid positive return values +enum lfs_error { + LFS_ERR_OK = 0, // No error + LFS_ERR_IO = -5, // Error during device operation + LFS_ERR_CORRUPT = -84, // Corrupted + LFS_ERR_NOENT = -2, // No directory entry + LFS_ERR_EXIST = -17, // Entry already exists + LFS_ERR_NOTDIR = -20, // Entry is not a dir + LFS_ERR_ISDIR = -21, // Entry is a dir + LFS_ERR_NOTEMPTY = -39, // Dir is not empty + LFS_ERR_BADF = -9, // Bad file number + LFS_ERR_FBIG = -27, // File too large + LFS_ERR_INVAL = -22, // Invalid parameter + LFS_ERR_NOSPC = -28, // No space left on device + LFS_ERR_NOMEM = -12, // No more memory available + LFS_ERR_NOATTR = -61, // No data/attr available + LFS_ERR_NAMETOOLONG = -36, // File name too long +}; + +// File types +enum lfs_type { + // file types + LFS_TYPE_REG = 0x001, + LFS_TYPE_DIR = 0x002, + + // internally used types + LFS_TYPE_SPLICE = 0x400, + LFS_TYPE_NAME = 0x000, + LFS_TYPE_STRUCT = 0x200, + LFS_TYPE_USERATTR = 0x300, + LFS_TYPE_FROM = 0x100, + LFS_TYPE_TAIL = 0x600, + LFS_TYPE_GLOBALS = 0x700, + LFS_TYPE_CRC = 0x500, + + // internally used type specializations + LFS_TYPE_CREATE = 0x401, + LFS_TYPE_DELETE = 0x4ff, + LFS_TYPE_SUPERBLOCK = 0x0ff, + LFS_TYPE_DIRSTRUCT = 0x200, + LFS_TYPE_CTZSTRUCT = 0x202, + LFS_TYPE_INLINESTRUCT = 0x201, + LFS_TYPE_SOFTTAIL = 0x600, + LFS_TYPE_HARDTAIL = 0x601, + LFS_TYPE_MOVESTATE = 0x7ff, + + // internal chip sources + LFS_FROM_NOOP = 0x000, + LFS_FROM_MOVE = 0x101, + LFS_FROM_USERATTRS = 0x102, +}; + +// File open flags +enum lfs_open_flags { + // open flags + LFS_O_RDONLY = 1, // Open a file as read only + LFS_O_WRONLY = 2, // Open a file as write only + LFS_O_RDWR = 3, // Open a file as read and write + LFS_O_CREAT = 0x0100, // Create a file if it does not exist + LFS_O_EXCL = 0x0200, // Fail if a file already exists + LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size + LFS_O_APPEND = 0x0800, // Move to end of file on every write + + // internally used flags + LFS_F_DIRTY = 0x010000, // File does not match storage + LFS_F_WRITING = 0x020000, // File has been written since last flush + LFS_F_READING = 0x040000, // File has been read since last flush + LFS_F_ERRED = 0x080000, // An error occured during write + LFS_F_INLINE = 0x100000, // Currently inlined in directory entry + LFS_F_OPENED = 0x200000, // File has been opened +}; + +// File seek flags +enum lfs_whence_flags { + LFS_SEEK_SET = 0, // Seek relative to an absolute position + LFS_SEEK_CUR = 1, // Seek relative to the current file position + LFS_SEEK_END = 2, // Seek relative to the end of the file +}; + + +// Configuration provided during initialization of the littlefs +struct lfs_config { + // Opaque user provided context that can be used to pass + // information to the block device operations + void *context; + + // Read a region in a block. Negative error codes are propogated + // to the user. + int (*read)(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size); + + // Program a region in a block. The block must have previously + // been erased. Negative error codes are propogated to the user. + // May return LFS_ERR_CORRUPT if the block should be considered bad. + int (*prog)(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size); + + // Erase a block. A block must be erased before being programmed. + // The state of an erased block is undefined. Negative error codes + // are propogated to the user. + // May return LFS_ERR_CORRUPT if the block should be considered bad. + int (*erase)(const struct lfs_config *c, lfs_block_t block); + + // Sync the state of the underlying block device. Negative error codes + // are propogated to the user. + int (*sync)(const struct lfs_config *c); + + // Minimum size of a block read. All read operations will be a + // multiple of this value. + lfs_size_t read_size; + + // Minimum size of a block program. All program operations will be a + // multiple of this value. + lfs_size_t prog_size; + + // Size of an erasable block. This does not impact ram consumption and + // may be larger than the physical erase size. However, non-inlined files + // take up at minimum one block. Must be a multiple of the read + // and program sizes. + lfs_size_t block_size; + + // Number of erasable blocks on the device. + lfs_size_t block_count; + + // Number of erase cycles before littlefs evicts metadata logs and moves + // the metadata to another block. Suggested values are in the + // range 100-1000, with large values having better performance at the cost + // of less consistent wear distribution. + // + // Set to -1 to disable block-level wear-leveling. + int32_t block_cycles; + + // Size of block caches. Each cache buffers a portion of a block in RAM. + // The littlefs needs a read cache, a program cache, and one additional + // cache per file. Larger caches can improve performance by storing more + // data and reducing the number of disk accesses. Must be a multiple of + // the read and program sizes, and a factor of the block size. + lfs_size_t cache_size; + + // Size of the lookahead buffer in bytes. A larger lookahead buffer + // increases the number of blocks found during an allocation pass. The + // lookahead buffer is stored as a compact bitmap, so each byte of RAM + // can track 8 blocks. Must be a multiple of 8. + lfs_size_t lookahead_size; + + // Optional statically allocated read buffer. Must be cache_size. + // By default lfs_malloc is used to allocate this buffer. + void *read_buffer; + + // Optional statically allocated program buffer. Must be cache_size. + // By default lfs_malloc is used to allocate this buffer. + void *prog_buffer; + + // Optional statically allocated lookahead buffer. Must be lookahead_size + // and aligned to a 32-bit boundary. By default lfs_malloc is used to + // allocate this buffer. + void *lookahead_buffer; + + // Optional upper limit on length of file names in bytes. No downside for + // larger names except the size of the info struct which is controlled by + // the LFS_NAME_MAX define. Defaults to LFS_NAME_MAX when zero. Stored in + // superblock and must be respected by other littlefs drivers. + lfs_size_t name_max; + + // Optional upper limit on files in bytes. No downside for larger files + // but must be <= LFS_FILE_MAX. Defaults to LFS_FILE_MAX when zero. Stored + // in superblock and must be respected by other littlefs drivers. + lfs_size_t file_max; + + // Optional upper limit on custom attributes in bytes. No downside for + // larger attributes size but must be <= LFS_ATTR_MAX. Defaults to + // LFS_ATTR_MAX when zero. + lfs_size_t attr_max; +}; + +// File info structure +struct lfs_info { + // Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR + uint8_t type; + + // Size of the file, only valid for REG files. Limited to 32-bits. + lfs_size_t size; + + // Name of the file stored as a null-terminated string. Limited to + // LFS_NAME_MAX+1, which can be changed by redefining LFS_NAME_MAX to + // reduce RAM. LFS_NAME_MAX is stored in superblock and must be + // respected by other littlefs drivers. + char name[LFS_NAME_MAX+1]; +}; + +// Custom attribute structure, used to describe custom attributes +// committed atomically during file writes. +struct lfs_attr { + // 8-bit type of attribute, provided by user and used to + // identify the attribute + uint8_t type; + + // Pointer to buffer containing the attribute + void *buffer; + + // Size of attribute in bytes, limited to LFS_ATTR_MAX + lfs_size_t size; +}; + +// Optional configuration provided during lfs_file_opencfg +struct lfs_file_config { + // Optional statically allocated file buffer. Must be cache_size. + // By default lfs_malloc is used to allocate this buffer. + void *buffer; + + // Optional list of custom attributes related to the file. If the file + // is opened with read access, these attributes will be read from disk + // during the open call. If the file is opened with write access, the + // attributes will be written to disk every file sync or close. This + // write occurs atomically with update to the file's contents. + // + // Custom attributes are uniquely identified by an 8-bit type and limited + // to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller + // than the buffer, it will be padded with zeros. If the stored attribute + // is larger, then it will be silently truncated. If the attribute is not + // found, it will be created implicitly. + struct lfs_attr *attrs; + + // Number of custom attributes in the list + lfs_size_t attr_count; +}; + + +/// internal littlefs data structures /// +typedef struct lfs_cache { + lfs_block_t block; + lfs_off_t off; + lfs_size_t size; + uint8_t *buffer; +} lfs_cache_t; + +typedef struct lfs_mdir { + lfs_block_t pair[2]; + uint32_t rev; + lfs_off_t off; + uint32_t etag; + uint16_t count; + bool erased; + bool split; + lfs_block_t tail[2]; +} lfs_mdir_t; + +// littlefs directory type +typedef struct lfs_dir { + struct lfs_dir *next; + uint16_t id; + uint8_t type; + lfs_mdir_t m; + + lfs_off_t pos; + lfs_block_t head[2]; +} lfs_dir_t; + +// littlefs file type +typedef struct lfs_file { + struct lfs_file *next; + uint16_t id; + uint8_t type; + lfs_mdir_t m; + + struct lfs_ctz { + lfs_block_t head; + lfs_size_t size; + } ctz; + + uint32_t flags; + lfs_off_t pos; + lfs_block_t block; + lfs_off_t off; + lfs_cache_t cache; + + const struct lfs_file_config *cfg; +} lfs_file_t; + +typedef struct lfs_superblock { + uint32_t version; + lfs_size_t block_size; + lfs_size_t block_count; + lfs_size_t name_max; + lfs_size_t file_max; + lfs_size_t attr_max; +} lfs_superblock_t; + +typedef struct lfs_gstate { + uint32_t tag; + lfs_block_t pair[2]; +} lfs_gstate_t; + +// The littlefs filesystem type +typedef struct lfs { + lfs_cache_t rcache; + lfs_cache_t pcache; + + lfs_block_t root[2]; + struct lfs_mlist { + struct lfs_mlist *next; + uint16_t id; + uint8_t type; + lfs_mdir_t m; + } *mlist; + uint32_t seed; + + lfs_gstate_t gstate; + lfs_gstate_t gdisk; + lfs_gstate_t gdelta; + + struct lfs_free { + lfs_block_t off; + lfs_block_t size; + lfs_block_t i; + lfs_block_t ack; + uint32_t *buffer; + } free; + + const struct lfs_config *cfg; + lfs_size_t name_max; + lfs_size_t file_max; + lfs_size_t attr_max; + +#ifdef LFS_MIGRATE + struct lfs1 *lfs1; +#endif +} lfs_t; + + +/// Filesystem functions /// + +// Format a block device with the littlefs +// +// Requires a littlefs object and config struct. This clobbers the littlefs +// object, and does not leave the filesystem mounted. The config struct must +// be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_format(lfs_t *lfs, const struct lfs_config *config); + +// Mounts a littlefs +// +// Requires a littlefs object and config struct. Multiple filesystems +// may be mounted simultaneously with multiple littlefs objects. Both +// lfs and config must be allocated while mounted. The config struct must +// be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_mount(lfs_t *lfs, const struct lfs_config *config); + +// Unmounts a littlefs +// +// Does nothing besides releasing any allocated resources. +// Returns a negative error code on failure. +int lfs_unmount(lfs_t *lfs); + +/// General operations /// + +// Removes a file or directory +// +// If removing a directory, the directory must be empty. +// Returns a negative error code on failure. +int lfs_remove(lfs_t *lfs, const char *path); + +// Rename or move a file or directory +// +// If the destination exists, it must match the source in type. +// If the destination is a directory, the directory must be empty. +// +// Returns a negative error code on failure. +int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath); + +// Find info about a file or directory +// +// Fills out the info structure, based on the specified file or directory. +// Returns a negative error code on failure. +int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info); + +// Get a custom attribute +// +// Custom attributes are uniquely identified by an 8-bit type and limited +// to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller than +// the buffer, it will be padded with zeros. If the stored attribute is larger, +// then it will be silently truncated. If no attribute is found, the error +// LFS_ERR_NOATTR is returned and the buffer is filled with zeros. +// +// Returns the size of the attribute, or a negative error code on failure. +// Note, the returned size is the size of the attribute on disk, irrespective +// of the size of the buffer. This can be used to dynamically allocate a buffer +// or check for existance. +lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, + uint8_t type, void *buffer, lfs_size_t size); + +// Set custom attributes +// +// Custom attributes are uniquely identified by an 8-bit type and limited +// to LFS_ATTR_MAX bytes. If an attribute is not found, it will be +// implicitly created. +// +// Returns a negative error code on failure. +int lfs_setattr(lfs_t *lfs, const char *path, + uint8_t type, const void *buffer, lfs_size_t size); + +// Removes a custom attribute +// +// If an attribute is not found, nothing happens. +// +// Returns a negative error code on failure. +int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type); + + +/// File operations /// + +// Open a file +// +// The mode that the file is opened in is determined by the flags, which +// are values from the enum lfs_open_flags that are bitwise-ored together. +// +// Returns a negative error code on failure. +int lfs_file_open(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags); + +// Open a file with extra configuration +// +// The mode that the file is opened in is determined by the flags, which +// are values from the enum lfs_open_flags that are bitwise-ored together. +// +// The config struct provides additional config options per file as described +// above. The config struct must be allocated while the file is open, and the +// config struct must be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags, + const struct lfs_file_config *config); + +// Close a file +// +// Any pending writes are written out to storage as though +// sync had been called and releases any allocated resources. +// +// Returns a negative error code on failure. +int lfs_file_close(lfs_t *lfs, lfs_file_t *file); + +// Synchronize a file on storage +// +// Any pending writes are written out to storage. +// Returns a negative error code on failure. +int lfs_file_sync(lfs_t *lfs, lfs_file_t *file); + +// Read data from file +// +// Takes a buffer and size indicating where to store the read data. +// Returns the number of bytes read, or a negative error code on failure. +lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size); + +// Write data to file +// +// Takes a buffer and size indicating the data to write. The file will not +// actually be updated on the storage until either sync or close is called. +// +// Returns the number of bytes written, or a negative error code on failure. +lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size); + +// Change the position of the file +// +// The change in position is determined by the offset and whence flag. +// Returns the new position of the file, or a negative error code on failure. +lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, + lfs_soff_t off, int whence); + +// Truncates the size of the file to the specified size +// +// Returns a negative error code on failure. +int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size); + +// Return the position of the file +// +// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR) +// Returns the position of the file, or a negative error code on failure. +lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file); + +// Change the position of the file to the beginning of the file +// +// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_SET) +// Returns a negative error code on failure. +int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file); + +// Return the size of the file +// +// Similar to lfs_file_seek(lfs, file, 0, LFS_SEEK_END) +// Returns the size of the file, or a negative error code on failure. +lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file); + + +/// Directory operations /// + +// Create a directory +// +// Returns a negative error code on failure. +int lfs_mkdir(lfs_t *lfs, const char *path); + +// Open a directory +// +// Once open a directory can be used with read to iterate over files. +// Returns a negative error code on failure. +int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path); + +// Close a directory +// +// Releases any allocated resources. +// Returns a negative error code on failure. +int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir); + +// Read an entry in the directory +// +// Fills out the info structure, based on the specified file or directory. +// Returns a positive value on success, 0 at the end of directory, +// or a negative error code on failure. +int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info); + +// Change the position of the directory +// +// The new off must be a value previous returned from tell and specifies +// an absolute offset in the directory seek. +// +// Returns a negative error code on failure. +int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off); + +// Return the position of the directory +// +// The returned offset is only meant to be consumed by seek and may not make +// sense, but does indicate the current position in the directory iteration. +// +// Returns the position of the directory, or a negative error code on failure. +lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir); + +// Change the position of the directory to the beginning of the directory +// +// Returns a negative error code on failure. +int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir); + + +/// Filesystem-level filesystem operations + +// Finds the current size of the filesystem +// +// Note: Result is best effort. If files share COW structures, the returned +// size may be larger than the filesystem actually is. +// +// Returns the number of allocated blocks, or a negative error code on failure. +lfs_ssize_t lfs_fs_size(lfs_t *lfs); + +// Traverse through all blocks in use by the filesystem +// +// The provided callback will be called with each block address that is +// currently in use by the filesystem. This can be used to determine which +// blocks are in use or how much of the storage is available. +// +// Returns a negative error code on failure. +int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); + +#ifdef LFS_MIGRATE +// Attempts to migrate a previous version of littlefs +// +// Behaves similarly to the lfs_format function. Attempts to mount +// the previous version of littlefs and update the filesystem so it can be +// mounted with the current version of littlefs. +// +// Requires a littlefs object and config struct. This clobbers the littlefs +// object, and does not leave the filesystem mounted. The config struct must +// be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg); +#endif + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/Firmware_V3/lib/LittleFS/littlefs/lfs_util.c b/Firmware_V3/lib/LittleFS/littlefs/lfs_util.c new file mode 100644 index 0000000..0b60e3b --- /dev/null +++ b/Firmware_V3/lib/LittleFS/littlefs/lfs_util.c @@ -0,0 +1,33 @@ +/* + * lfs util functions + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "lfs_util.h" + +// Only compile if user does not provide custom config +#ifndef LFS_CONFIG + + +// Software CRC implementation with small lookup table +uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) { + static const uint32_t rtable[16] = { + 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, + 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, + 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c, + }; + + const uint8_t *data = buffer; + + for (size_t i = 0; i < size; i++) { + crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 0)) & 0xf]; + crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf]; + } + + return crc; +} + + +#endif diff --git a/Firmware_V3/lib/LittleFS/littlefs/lfs_util.h b/Firmware_V3/lib/LittleFS/littlefs/lfs_util.h new file mode 100644 index 0000000..a0d2385 --- /dev/null +++ b/Firmware_V3/lib/LittleFS/littlefs/lfs_util.h @@ -0,0 +1,244 @@ +/* + * lfs utility functions + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef LFS_UTIL_H +#define LFS_UTIL_H + + +// Teensy specific use... +#define LFS_NAME_MAX 39 +#define LFS_NO_DEBUG +#define LFS_NO_WARN +#define LFS_NO_ERROR +#define LFS_NO_ASSERT + + + +// Users can override lfs_util.h with their own configuration by defining +// LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h). +// +// If LFS_CONFIG is used, none of the default utils will be emitted and must be +// provided by the config file. To start, I would suggest copying lfs_util.h +// and modifying as needed. +#ifdef LFS_CONFIG +#define LFS_STRINGIZE(x) LFS_STRINGIZE2(x) +#define LFS_STRINGIZE2(x) #x +#include LFS_STRINGIZE(LFS_CONFIG) +#else + +// System includes +#include +#include +#include +#include + +#ifndef LFS_NO_MALLOC +#include +#endif +#ifndef LFS_NO_ASSERT +#include +#endif +#if !defined(LFS_NO_DEBUG) || \ + !defined(LFS_NO_WARN) || \ + !defined(LFS_NO_ERROR) || \ + defined(LFS_YES_TRACE) +#include +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + + +// Macros, may be replaced by system specific wrappers. Arguments to these +// macros must not have side-effects as the macros can be removed for a smaller +// code footprint + +// Logging functions +#ifdef LFS_YES_TRACE +#define LFS_TRACE_(fmt, ...) \ + printf("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "") +#else +#define LFS_TRACE(...) +#endif + +#ifndef LFS_NO_DEBUG +#define LFS_DEBUG_(fmt, ...) \ + printf("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_DEBUG(...) LFS_DEBUG_(__VA_ARGS__, "") +#else +#define LFS_DEBUG(...) +#endif + +#ifndef LFS_NO_WARN +#define LFS_WARN_(fmt, ...) \ + printf("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_WARN(...) LFS_WARN_(__VA_ARGS__, "") +#else +#define LFS_WARN(...) +#endif + +#ifndef LFS_NO_ERROR +#define LFS_ERROR_(fmt, ...) \ + printf("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_ERROR(...) LFS_ERROR_(__VA_ARGS__, "") +#else +#define LFS_ERROR(...) +#endif + +// Runtime assertions +#ifndef LFS_NO_ASSERT +#define LFS_ASSERT(test) assert(test) +#else +#define LFS_ASSERT(test) +#endif + + +// Builtin functions, these may be replaced by more efficient +// toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more +// expensive basic C implementation for debugging purposes + +// Min/max functions for unsigned 32-bit numbers +static inline uint32_t lfs_max(uint32_t a, uint32_t b) { + return (a > b) ? a : b; +} + +static inline uint32_t lfs_min(uint32_t a, uint32_t b) { + return (a < b) ? a : b; +} + +// Align to nearest multiple of a size +static inline uint32_t lfs_aligndown(uint32_t a, uint32_t alignment) { + return a - (a % alignment); +} + +static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) { + return lfs_aligndown(a + alignment-1, alignment); +} + +// Find the smallest power of 2 greater than or equal to a +static inline uint32_t lfs_npw2(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) + return 32 - __builtin_clz(a-1); +#else + uint32_t r = 0; + uint32_t s; + a -= 1; + s = (a > 0xffff) << 4; a >>= s; r |= s; + s = (a > 0xff ) << 3; a >>= s; r |= s; + s = (a > 0xf ) << 2; a >>= s; r |= s; + s = (a > 0x3 ) << 1; a >>= s; r |= s; + return (r | (a >> 1)) + 1; +#endif +} + +// Count the number of trailing binary zeros in a +// lfs_ctz(0) may be undefined +static inline uint32_t lfs_ctz(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__) + return __builtin_ctz(a); +#else + return lfs_npw2((a & -a) + 1) - 1; +#endif +} + +// Count the number of binary ones in a +static inline uint32_t lfs_popc(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) + return __builtin_popcount(a); +#else + a = a - ((a >> 1) & 0x55555555); + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); + return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; +#endif +} + +// Find the sequence comparison of a and b, this is the distance +// between a and b ignoring overflow +static inline int lfs_scmp(uint32_t a, uint32_t b) { + return (int)(unsigned)(a - b); +} + +// Convert between 32-bit little-endian and native order +static inline uint32_t lfs_fromle32(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && ( \ + (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) + return a; +#elif !defined(LFS_NO_INTRINSICS) && ( \ + (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) + return __builtin_bswap32(a); +#else + return (((uint8_t*)&a)[0] << 0) | + (((uint8_t*)&a)[1] << 8) | + (((uint8_t*)&a)[2] << 16) | + (((uint8_t*)&a)[3] << 24); +#endif +} + +static inline uint32_t lfs_tole32(uint32_t a) { + return lfs_fromle32(a); +} + +// Convert between 32-bit big-endian and native order +static inline uint32_t lfs_frombe32(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && ( \ + (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) + return __builtin_bswap32(a); +#elif !defined(LFS_NO_INTRINSICS) && ( \ + (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) + return a; +#else + return (((uint8_t*)&a)[0] << 24) | + (((uint8_t*)&a)[1] << 16) | + (((uint8_t*)&a)[2] << 8) | + (((uint8_t*)&a)[3] << 0); +#endif +} + +static inline uint32_t lfs_tobe32(uint32_t a) { + return lfs_frombe32(a); +} + +// Calculate CRC-32 with polynomial = 0x04c11db7 +uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size); + +// Allocate memory, only used if buffers are not provided to littlefs +// Note, memory must be 64-bit aligned +static inline void *lfs_malloc(size_t size) { +#ifndef LFS_NO_MALLOC + return malloc(size); +#else + (void)size; + return NULL; +#endif +} + +// Deallocate memory, only used if buffers are not provided to littlefs +static inline void lfs_free(void *p) { +#ifndef LFS_NO_MALLOC + free(p); +#else + (void)p; +#endif +} + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif +#endif diff --git a/Firmware_V3/lib/MTP/MTP.cpp b/Firmware_V3/lib/MTP/MTP.cpp new file mode 100644 index 0000000..94427fc --- /dev/null +++ b/Firmware_V3/lib/MTP/MTP.cpp @@ -0,0 +1,1551 @@ +// MTP.cpp - Teensy MTP Responder library +// Copyright (C) 2017 Fredrik Hubinette +// +// With updates from MichaelMC and Yoong Hor Meng +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// modified for SDFS by WMXZ + +#if defined(USB_MTPDISK) || defined(USB_MTPDISK_SERIAL) + +#include "MTP.h" + +#undef USB_DESC_LIST_DEFINE +#include "usb_desc.h" + +#if defined(__IMXRT1062__) + // following only while usb_mtp is not included in cores + #if __has_include("usb_mtp.h") + #include "usb_mtp.h" + #else + #include "usb1_mtp.h" + #endif +#endif + +#include "usb_names.h" +extern struct usb_string_descriptor_struct usb_string_serial_number; + +#define DEBUG 1 +#if DEBUG>0 + #define printf(...) Serial.printf(__VA_ARGS__) +#else + #define printf(...) +#endif + +/***************************************************************************************************/ + // Container Types + #define MTP_CONTAINER_TYPE_UNDEFINED 0 + #define MTP_CONTAINER_TYPE_COMMAND 1 + #define MTP_CONTAINER_TYPE_DATA 2 + #define MTP_CONTAINER_TYPE_RESPONSE 3 + #define MTP_CONTAINER_TYPE_EVENT 4 + + // Container Offsets + #define MTP_CONTAINER_LENGTH_OFFSET 0 + #define MTP_CONTAINER_TYPE_OFFSET 4 + #define MTP_CONTAINER_CODE_OFFSET 6 + #define MTP_CONTAINER_TRANSACTION_ID_OFFSET 8 + #define MTP_CONTAINER_PARAMETER_OFFSET 12 + #define MTP_CONTAINER_HEADER_SIZE 12 + + // MTP Operation Codes + #define MTP_OPERATION_GET_DEVICE_INFO 0x1001 + #define MTP_OPERATION_OPEN_SESSION 0x1002 + #define MTP_OPERATION_CLOSE_SESSION 0x1003 + #define MTP_OPERATION_GET_STORAGE_IDS 0x1004 + #define MTP_OPERATION_GET_STORAGE_INFO 0x1005 + #define MTP_OPERATION_GET_NUM_OBJECTS 0x1006 + #define MTP_OPERATION_GET_OBJECT_HANDLES 0x1007 + #define MTP_OPERATION_GET_OBJECT_INFO 0x1008 + #define MTP_OPERATION_GET_OBJECT 0x1009 + #define MTP_OPERATION_GET_THUMB 0x100A + #define MTP_OPERATION_DELETE_OBJECT 0x100B + #define MTP_OPERATION_SEND_OBJECT_INFO 0x100C + #define MTP_OPERATION_SEND_OBJECT 0x100D + #define MTP_OPERATION_INITIATE_CAPTURE 0x100E + #define MTP_OPERATION_FORMAT_STORE 0x100F + #define MTP_OPERATION_RESET_DEVICE 0x1010 + #define MTP_OPERATION_SELF_TEST 0x1011 + #define MTP_OPERATION_SET_OBJECT_PROTECTION 0x1012 + #define MTP_OPERATION_POWER_DOWN 0x1013 + #define MTP_OPERATION_GET_DEVICE_PROP_DESC 0x1014 + #define MTP_OPERATION_GET_DEVICE_PROP_VALUE 0x1015 + #define MTP_OPERATION_SET_DEVICE_PROP_VALUE 0x1016 + #define MTP_OPERATION_RESET_DEVICE_PROP_VALUE 0x1017 + #define MTP_OPERATION_TERMINATE_OPEN_CAPTURE 0x1018 + #define MTP_OPERATION_MOVE_OBJECT 0x1019 + #define MTP_OPERATION_COPY_OBJECT 0x101A + #define MTP_OPERATION_GET_PARTIAL_OBJECT 0x101B + #define MTP_OPERATION_INITIATE_OPEN_CAPTURE 0x101C + #define MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED 0x9801 + #define MTP_OPERATION_GET_OBJECT_PROP_DESC 0x9802 + #define MTP_OPERATION_GET_OBJECT_PROP_VALUE 0x9803 + #define MTP_OPERATION_SET_OBJECT_PROP_VALUE 0x9804 + #define MTP_OPERATION_GET_OBJECT_PROP_LIST 0x9805 + #define MTP_OPERATION_SET_OBJECT_PROP_LIST 0x9806 + #define MTP_OPERATION_GET_INTERDEPENDENT_PROP_DESC 0x9807 + #define MTP_OPERATION_SEND_OBJECT_PROP_LIST 0x9808 + #define MTP_OPERATION_GET_OBJECT_REFERENCES 0x9810 + #define MTP_OPERATION_SET_OBJECT_REFERENCES 0x9811 + #define MTP_OPERATION_SKIP 0x9820 + + + const unsigned short supported_op[]= + { + MTP_OPERATION_GET_DEVICE_INFO ,//0x1001 + MTP_OPERATION_OPEN_SESSION ,//0x1002 + MTP_OPERATION_CLOSE_SESSION ,//0x1003 + MTP_OPERATION_GET_STORAGE_IDS ,//0x1004 + MTP_OPERATION_GET_STORAGE_INFO ,//0x1005 + //MTP_OPERATION_GET_NUM_OBJECTS ,//0x1006 + MTP_OPERATION_GET_OBJECT_HANDLES ,//0x1007 + MTP_OPERATION_GET_OBJECT_INFO ,//0x1008 + MTP_OPERATION_GET_OBJECT ,//0x1009 + //MTP_OPERATION_GET_THUMB ,//0x100A + MTP_OPERATION_DELETE_OBJECT ,//0x100B + MTP_OPERATION_SEND_OBJECT_INFO ,//0x100C + MTP_OPERATION_SEND_OBJECT ,//0x100D + MTP_OPERATION_GET_DEVICE_PROP_DESC ,//0x1014 + MTP_OPERATION_GET_DEVICE_PROP_VALUE ,//0x1015 + //MTP_OPERATION_SET_DEVICE_PROP_VALUE ,//0x1016 + //MTP_OPERATION_RESET_DEVICE_PROP_VALUE ,//0x1017 + MTP_OPERATION_MOVE_OBJECT ,//0x1019 + MTP_OPERATION_COPY_OBJECT ,//0x101A + MTP_OPERATION_GET_PARTIAL_OBJECT ,//0x101B + + MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED ,//0x9801 + MTP_OPERATION_GET_OBJECT_PROP_DESC ,//0x9802 + MTP_OPERATION_GET_OBJECT_PROP_VALUE ,//0x9803 + MTP_OPERATION_SET_OBJECT_PROP_VALUE //0x9804 + //MTP_OPERATION_GET_OBJECT_PROP_LIST ,//0x9805 + //MTP_OPERATION_GET_OBJECT_REFERENCES ,//0x9810 + //MTP_OPERATION_SET_OBJECT_REFERENCES ,//0x9811 + + //MTP_OPERATION_GET_PARTIAL_OBJECT_64 ,//0x95C1 + //MTP_OPERATION_SEND_PARTIAL_OBJECT ,//0x95C2 + //MTP_OPERATION_TRUNCATE_OBJECT ,//0x95C3 + //MTP_OPERATION_BEGIN_EDIT_OBJECT ,//0x95C4 + //MTP_OPERATION_END_EDIT_OBJECT //0x95C5 + }; + + const int supported_op_size=sizeof(supported_op); + const int supported_op_num = supported_op_size/sizeof(supported_op[0]); + + + #define MTP_PROPERTY_STORAGE_ID 0xDC01 + #define MTP_PROPERTY_OBJECT_FORMAT 0xDC02 + #define MTP_PROPERTY_PROTECTION_STATUS 0xDC03 + #define MTP_PROPERTY_OBJECT_SIZE 0xDC04 + #define MTP_PROPERTY_OBJECT_FILE_NAME 0xDC07 + #define MTP_PROPERTY_DATE_CREATED 0xDC08 + #define MTP_PROPERTY_DATE_MODIFIED 0xDC09 + #define MTP_PROPERTY_PARENT_OBJECT 0xDC0B + #define MTP_PROPERTY_PERSISTENT_UID 0xDC41 + #define MTP_PROPERTY_NAME 0xDC44 + + const uint16_t propertyList[] = + { + MTP_PROPERTY_STORAGE_ID ,//0xDC01 + MTP_PROPERTY_OBJECT_FORMAT ,//0xDC02 + MTP_PROPERTY_PROTECTION_STATUS ,//0xDC03 + MTP_PROPERTY_OBJECT_SIZE ,//0xDC04 + MTP_PROPERTY_OBJECT_FILE_NAME ,//0xDC07 +// MTP_PROPERTY_DATE_CREATED ,//0xDC08 +// MTP_PROPERTY_DATE_MODIFIED ,//0xDC09 + MTP_PROPERTY_PARENT_OBJECT ,//0xDC0B + MTP_PROPERTY_PERSISTENT_UID ,//0xDC41 + MTP_PROPERTY_NAME //0xDC44 + }; + + uint32_t propertyListNum = sizeof(propertyList)/sizeof(propertyList[0]); + + + #define MTP_EVENT_UNDEFINED 0x4000 + #define MTP_EVENT_CANCEL_TRANSACTION 0x4001 + #define MTP_EVENT_OBJECT_ADDED 0x4002 + #define MTP_EVENT_OBJECT_REMOVED 0x4003 + #define MTP_EVENT_STORE_ADDED 0x4004 + #define MTP_EVENT_STORE_REMOVED 0x4005 + #define MTP_EVENT_DEVICE_PROP_CHANGED 0x4006 + #define MTP_EVENT_OBJECT_INFO_CHANGED 0x4007 + #define MTP_EVENT_DEVICE_INFO_CHANGED 0x4008 + #define MTP_EVENT_REQUEST_OBJECT_TRANSFER 0x4009 + #define MTP_EVENT_STORE_FULL 0x400A + #define MTP_EVENT_DEVICE_RESET 0x400B + #define MTP_EVENT_STORAGE_INFO_CHANGED 0x400C + #define MTP_EVENT_CAPTURE_COMPLETE 0x400D + #define MTP_EVENT_UNREPORTED_STATUS 0x400E + #define MTP_EVENT_OBJECT_PROP_CHANGED 0xC801 + #define MTP_EVENT_OBJECT_PROP_DESC_CHANGED 0xC802 + #define MTP_EVENT_OBJECT_REFERENCES_CHANGED 0xC803 + +const uint16_t supported_events[] = + { +// MTP_EVENT_UNDEFINED ,//0x4000 +// MTP_EVENT_CANCEL_TRANSACTION ,//0x4001 +// MTP_EVENT_OBJECT_ADDED ,//0x4002 +// MTP_EVENT_OBJECT_REMOVED ,//0x4003 + MTP_EVENT_STORE_ADDED ,//0x4004 + MTP_EVENT_STORE_REMOVED ,//0x4005 +// MTP_EVENT_DEVICE_PROP_CHANGED ,//0x4006 +// MTP_EVENT_OBJECT_INFO_CHANGED ,//0x4007 +// MTP_EVENT_DEVICE_INFO_CHANGED ,//0x4008 +// MTP_EVENT_REQUEST_OBJECT_TRANSFER ,//0x4009 +// MTP_EVENT_STORE_FULL ,//0x400A + MTP_EVENT_DEVICE_RESET ,//0x400B + MTP_EVENT_STORAGE_INFO_CHANGED ,//0x400C +// MTP_EVENT_CAPTURE_COMPLETE ,//0x400D +// MTP_EVENT_UNREPORTED_STATUS ,//0x400E +// MTP_EVENT_OBJECT_PROP_CHANGED ,//0xC801 +// MTP_EVENT_OBJECT_PROP_DESC_CHANGED ,//0xC802 +// MTP_EVENT_OBJECT_REFERENCES_CHANGED //0xC803 + }; + + const int supported_event_num = sizeof(supported_events)/sizeof(supported_events[0]); + + uint32_t sessionID_; + +// MTP Responder. +/* + struct MTPHeader { + uint32_t len; // 0 + uint16_t type; // 4 + uint16_t op; // 6 + uint32_t transaction_id; // 8 + }; + + struct MTPContainer { + uint32_t len; // 0 + uint16_t type; // 4 + uint16_t op; // 6 + uint32_t transaction_id; // 8 + uint32_t params[5]; // 12 + }; +*/ + + void MTPD::write8 (uint8_t x) { write((char*)&x, sizeof(x)); } + void MTPD::write16(uint16_t x) { write((char*)&x, sizeof(x)); } + void MTPD::write32(uint32_t x) { write((char*)&x, sizeof(x)); } + void MTPD::write64(uint64_t x) { write((char*)&x, sizeof(x)); } + +#define Store2Storage(x) (x+1) +#define Storage2Store(x) (x-1) + + void MTPD::writestring(const char* str) { + if (*str) + { write8(strlen(str) + 1); + while (*str) { write16(*str); ++str; } write16(0); + } else + { write8(0); + } + } + + void MTPD::WriteDescriptor() { + write16(100); // MTP version + write32(6); // MTP extension +// write32(0xFFFFFFFFUL); // MTP extension + write16(100); // MTP version + writestring("microsoft.com: 1.0;"); + write16(0); // functional mode + + // Supported operations (array of uint16) + write32(supported_op_num); + for(int ii=0; iiget_FSCount(); + write32(num); // number of storages (disks) + for(uint32_t ii=0;iireadonly(store) ? 0x0001 : 0x0004); // storage type (removable RAM) + write16(storage_->has_directories(store) ? 0x0002: 0x0001); // filesystem type (generic hierarchical) + write16(0x0000); // access capability (read-write) + + uint64_t ntotal = storage_->totalSize(store) ; + uint64_t nused = storage_->usedSize(store) ; + + write64(ntotal); // max capacity + write64((ntotal-nused)); // free space (100M) + // + write32(0xFFFFFFFFUL); // free space (objects) + const char *name = storage_->get_FSName(store); + writestring(name); // storage descriptor + writestring(""); // volume identifier + + //printf("%d %d ",storage,store); Serial.println(name); Serial.flush(); + } + + uint32_t MTPD::GetNumObjects(uint32_t storage, uint32_t parent) + { uint32_t store = Storage2Store(storage); + storage_->StartGetObjectHandles(store, parent); + int num = 0; + while (storage_->GetNextObjectHandle(store)) num++; + return num; + } + + void MTPD::GetObjectHandles(uint32_t storage, uint32_t parent) + { uint32_t store = Storage2Store(storage); + if (write_get_length_) { + write_length_ = GetNumObjects(storage, parent); + write_length_++; + write_length_ *= 4; + } + else{ + write32(GetNumObjects(storage, parent)); + int handle; + storage_->StartGetObjectHandles(store, parent); + while ((handle = storage_->GetNextObjectHandle(store))) write32(handle); + } + } + + void MTPD::GetObjectInfo(uint32_t handle) + { + char filename[MAX_FILENAME_LEN]; + uint32_t size, parent; + uint16_t store; + storage_->GetObjectInfo(handle, filename, &size, &parent, &store); + + uint32_t storage = Store2Storage(store); + write32(storage); // storage + write16(size == 0xFFFFFFFFUL ? 0x3001 : 0x0000); // format + write16(0); // protection + write32(size); // size + write16(0); // thumb format + write32(0); // thumb size + write32(0); // thumb width + write32(0); // thumb height + write32(0); // pix width + write32(0); // pix height + write32(0); // bit depth + write32(parent); // parent + write16(size == 0xFFFFFFFFUL ? 1 : 0); // association type + write32(0); // association description + write32(0); // sequence number + writestring(filename); + writestring(""); // date created + writestring(""); // date modified + writestring(""); // keywords + } + + uint32_t MTPD::ReadMTPHeader() + { + MTPHeader header; + read((char *)&header, sizeof(MTPHeader)); + // check that the type is data + if(header.type==2) + return header.len - 12; + else + return 0; + } + + uint8_t MTPD::read8() { uint8_t ret; read((char*)&ret, sizeof(ret)); return ret; } + uint16_t MTPD::read16() { uint16_t ret; read((char*)&ret, sizeof(ret)); return ret; } + uint32_t MTPD::read32() { uint32_t ret; read((char*)&ret, sizeof(ret)); return ret; } + + void MTPD::readstring(char* buffer) { + int len = read8(); + if (!buffer) { + read(NULL, len * 2); + } else { + for (int i = 0; i < len; i++) { + int16_t c2; + *(buffer++) = c2 = read16(); + } + } + } + + void MTPD::GetDevicePropValue(uint32_t prop) { + switch (prop) { + case 0xd402: // friendly name + // This is the name we'll actually see in the windows explorer. + // Should probably be configurable. + writestring(MTP_NAME); + break; + } + } + + void MTPD::GetDevicePropDesc(uint32_t prop) { + switch (prop) { + case 0xd402: // friendly name + write16(prop); + write16(0xFFFF); // string type + write8(0); // read-only + GetDevicePropValue(prop); + GetDevicePropValue(prop); + write8(0); // no form + } + } + + void MTPD::getObjectPropsSupported(uint32_t p1) + { + write32(propertyListNum); + for(uint32_t ii=0; iiGetObjectInfo(p1,name,&size,&parent, &store); + dir = size == 0xFFFFFFFFUL; + uint32_t storage = Store2Storage(store); + switch(p2) + { + case MTP_PROPERTY_STORAGE_ID: //0xDC01: + write32(storage); + break; + case MTP_PROPERTY_OBJECT_FORMAT: //0xDC02: + write16(dir?0x3001:0x3000); + break; + case MTP_PROPERTY_PROTECTION_STATUS: //0xDC03: + write16(0); + break; + case MTP_PROPERTY_OBJECT_SIZE: //0xDC04: + write32(size); + write32(0); + break; + case MTP_PROPERTY_OBJECT_FILE_NAME: //0xDC07: + writestring(name); + break; + case MTP_PROPERTY_DATE_CREATED: //0xDC08: + writestring(""); + break; + case MTP_PROPERTY_DATE_MODIFIED: //0xDC09: + writestring(""); + break; + case MTP_PROPERTY_PARENT_OBJECT: //0xDC0B: + write32((store==parent)? 0: parent); + break; + case MTP_PROPERTY_PERSISTENT_UID: //0xDC41: + write32(p1); + write32(parent); + write32(storage); + write32(0); + break; + case MTP_PROPERTY_NAME: //0xDC44: + writestring(name); + break; + default: + break; + } + } + + uint32_t MTPD::deleteObject(uint32_t handle) + { + if (!storage_->DeleteObject(handle)) + { + return 0x2012; // partial deletion + } + return 0x2001; + } + + uint32_t MTPD::moveObject(uint32_t handle, uint32_t newStorage, uint32_t newHandle) + { uint32_t store1=Storage2Store(newStorage); + if(storage_->move(handle,store1,newHandle)) return 0x2001; else return 0x2005; + } + + uint32_t MTPD::copyObject(uint32_t handle, uint32_t newStorage, uint32_t newHandle) + { uint32_t store1=Storage2Store(newStorage); + return storage_->copy(handle,store1,newHandle); + } + + void MTPD::openSession(uint32_t id) + { + sessionID_ = id; + storage_->ResetIndex(); + } + + +#if defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) + +// usb_packet_t *data_buffer_ = NULL; + void MTPD::get_buffer() { + while (!data_buffer_) { + data_buffer_ = usb_malloc(); + if (!data_buffer_) mtp_yield(); + } + } + + void MTPD::receive_buffer() { + while (!data_buffer_) { + data_buffer_ = usb_rx(MTP_RX_ENDPOINT); + if (!data_buffer_) mtp_yield(); + } + } + + void MTPD::write(const char *data, int len) { + if (write_get_length_) { + write_length_ += len; + } else { + int pos = 0; + while (pos < len) { + get_buffer(); + int avail = sizeof(data_buffer_->buf) - data_buffer_->len; + int to_copy = min(len - pos, avail); + memcpy(data_buffer_->buf + data_buffer_->len, + data + pos, + to_copy); + data_buffer_->len += to_copy; + pos += to_copy; + if (data_buffer_->len == sizeof(data_buffer_->buf)) { + usb_tx(MTP_TX_ENDPOINT, data_buffer_); + data_buffer_ = NULL; + } + } + } + } + void MTPD::GetObject(uint32_t object_id) + { + uint32_t size = storage_->GetSize(object_id); + if (write_get_length_) { + write_length_ += size; + } else { + uint32_t pos = 0; + while (pos < size) { + get_buffer(); + uint32_t avail = sizeof(data_buffer_->buf) - data_buffer_->len; + uint32_t to_copy = min(size - pos, avail); + // Read directly from storage into usb buffer. + storage_->read(object_id, pos, + (char*)(data_buffer_->buf + data_buffer_->len), to_copy); + pos += to_copy; + data_buffer_->len += to_copy; + if (data_buffer_->len == sizeof(data_buffer_->buf)) { + usb_tx(MTP_TX_ENDPOINT, data_buffer_); + data_buffer_ = NULL; + } + } + } + } + + #define CONTAINER ((struct MTPContainer*)(receive_buffer->buf)) + + #define TRANSMIT(FUN) do { \ + write_length_ = 0; \ + write_get_length_ = true; \ + FUN; \ + write_get_length_ = false; \ + MTPHeader header; \ + header.len = write_length_ + 12; \ + header.type = 2; \ + header.op = CONTAINER->op; \ + header.transaction_id = CONTAINER->transaction_id; \ + write((char *)&header, sizeof(header)); \ + FUN; \ + get_buffer(); \ + usb_tx(MTP_TX_ENDPOINT, data_buffer_); \ + data_buffer_ = NULL; \ + } while(0) + + #define printContainer() \ + { printf("%x %d %d %d: ", CONTAINER->op, CONTAINER->len, CONTAINER->type, CONTAINER->transaction_id); \ + if(CONTAINER->len>12) printf(" %x", CONTAINER->params[0]); \ + if(CONTAINER->len>16) printf(" %x", CONTAINER->params[1]); \ + if(CONTAINER->len>20) printf(" %x", CONTAINER->params[2]); \ + printf("\n"); \ + } + + void MTPD::read(char* data, uint32_t size) + { + while (size) { + receive_buffer(); + uint32_t to_copy = data_buffer_->len - data_buffer_->index; + to_copy = min(to_copy, size); + if (data) { + memcpy(data, data_buffer_->buf + data_buffer_->index, to_copy); + data += to_copy; + } + size -= to_copy; + data_buffer_->index += to_copy; + if (data_buffer_->index == data_buffer_->len) { + usb_free(data_buffer_); + data_buffer_ = NULL; + } + } + } + + uint32_t MTPD::SendObjectInfo(uint32_t storage, uint32_t parent) { + uint32_t len = ReadMTPHeader(); + char filename[MAX_FILENAME_LEN]; + + uint32_t store = Storage2Store(storage); + + read32(); len-=4; // storage + bool dir = read16() == 0x3001; len-=2; // format + read16(); len-=2; // protection + read32(); len-=4; // size + read16(); len-=2; // thumb format + read32(); len-=4; // thumb size + read32(); len-=4; // thumb width + read32(); len-=4; // thumb height + read32(); len-=4; // pix width + read32(); len-=4; // pix height + read32(); len-=4; // bit depth + read32(); len-=4; // parent + read16(); len-=2; // association type + read32(); len-=4; // association description + read32(); len-=4; // sequence number + + readstring(filename); len -= (2*(strlen(filename)+1)+1); + // ignore rest of ObjectInfo + while(len>=4) { read32(); len-=4;} + while(len) {read8(); len--;} + + return storage_->Create(store, parent, dir, filename); + } + + bool MTPD::SendObject() { + uint32_t len = ReadMTPHeader(); + while (len) + { + receive_buffer(); + uint32_t to_copy = data_buffer_->len - data_buffer_->index; + to_copy = min(to_copy, len); + if(!storage_->write((char*)(data_buffer_->buf + data_buffer_->index), to_copy)) return false; + data_buffer_->index += to_copy; + len -= to_copy; + if (data_buffer_->index == data_buffer_->len) + { + usb_free(data_buffer_); + data_buffer_ = NULL; + } + } + storage_->close(); + return true; + } + + uint32_t MTPD::setObjectPropValue(uint32_t p1, uint32_t p2) + { + receive_buffer(); + if(p2==0xDC07) + { + char filename[MAX_FILENAME_LEN]; + ReadMTPHeader(); + readstring(filename); + + storage_->rename(p1,filename); + + return 0x2001; + } + else + return 0x2005; + } + + void MTPD::loop(void) + { + usb_packet_t *receive_buffer; + if ((receive_buffer = usb_rx(MTP_RX_ENDPOINT))) { + printContainer(); + + int op = CONTAINER->op; + int p1 = CONTAINER->params[0]; + int p2 = CONTAINER->params[1]; + int p3 = CONTAINER->params[2]; + int id = CONTAINER->transaction_id; + int len= CONTAINER->len; + int typ= CONTAINER->type; + TID=id; + + uint32_t return_code = 0; + if (receive_buffer->len >= 12) { + return_code = 0x2001; // Ok + receive_buffer->len = 12; + + if (typ == 1) { // command + switch (op) { + case 0x1001: // GetDescription + TRANSMIT(WriteDescriptor()); + break; + case 0x1002: // OpenSession + openSession(p1); + break; + case 0x1003: // CloseSession + break; + case 0x1004: // GetStorageIDs + TRANSMIT(WriteStorageIDs()); + break; + case 0x1005: // GetStorageInfo + TRANSMIT(GetStorageInfo(p1)); + break; + case 0x1006: // GetNumObjects + if (p2) { + return_code = 0x2014; // spec by format unsupported + } else { + p1 = GetNumObjects(p1, p3); + } + break; + case 0x1007: // GetObjectHandles + if (p2) { + return_code = 0x2014; // spec by format unsupported + } else { + TRANSMIT(GetObjectHandles(p1, p3)); + } + break; + case 0x1008: // GetObjectInfo + TRANSMIT(GetObjectInfo(p1)); + break; + case 0x1009: // GetObject + TRANSMIT(GetObject(p1)); + break; + case 0x100B: // DeleteObject + if (p2) { + return_code = 0x2014; // spec by format unsupported + } else { + if (!storage_->DeleteObject(p1)) { + return_code = 0x2012; // partial deletion + } + } + break; + p3 = SendObjectInfo(p1, // storage + p2); // parent + CONTAINER->params[1]=p2; + CONTAINER->params[2]=p3; + len = receive_buffer->len = 12 + 3 * 4; + break; + case 0x100D: // SendObject + SendObject(); + break; + case 0x1014: // GetDevicePropDesc + TRANSMIT(GetDevicePropDesc(p1)); + break; + case 0x1015: // GetDevicePropvalue + TRANSMIT(GetDevicePropValue(p1)); + break; + + case 0x1010: // Reset + return_code = 0x2005; + break; + + case 0x1019: // MoveObject + return_code = moveObject(p1,p2,p3); + len = receive_buffer->len = 12; + break; + + case 0x101A: // CopyObject + return_code = copyObject(p1,p2,p3); + if(! return_code) { len = receive_buffer->len = 12; return_code = 0x2005; } + else {p1 = return_code; return_code=0x2001;} + break; + + case 0x9801: // getObjectPropsSupported + TRANSMIT(getObjectPropsSupported(p1)); + break; + + case 0x9802: // getObjectPropDesc + TRANSMIT(getObjectPropDesc(p1,p2)); + break; + + case 0x9803: // getObjectPropertyValue + TRANSMIT(getObjectPropValue(p1,p2)); + break; + + case 0x9804: // setObjectPropertyValue + return_code = setObjectPropValue(p1,p2); + break; + + default: + return_code = 0x2005; // operation not supported + break; + } + } else { + return_code = 0x2000; // undefined + } + } + if (return_code) { + CONTAINER->type=3; + CONTAINER->len=len; + CONTAINER->op=return_code; + CONTAINER->transaction_id=id; + CONTAINER->params[0]=p1; + #if DEBUG>1 + printContainer(); + #endif + + usb_tx(MTP_TX_ENDPOINT, receive_buffer); + receive_buffer = 0; + } else { + usb_free(receive_buffer); + } + } + // Maybe put event handling inside mtp_yield()? + if ((receive_buffer = usb_rx(MTP_EVENT_ENDPOINT))) { + printf("Event: "); printContainer(); + usb_free(receive_buffer); + } + } + +#elif defined(__IMXRT1062__) + + int MTPD::push_packet(uint8_t *data_buffer,uint32_t len) + { + while(usb_mtp_send(data_buffer,len,60)<=0) ; + return 1; + } + + int MTPD::pull_packet(uint8_t *data_buffer) + { + while(!usb_mtp_available()); + return usb_mtp_recv(data_buffer,60); + } + + int MTPD::fetch_packet(uint8_t *data_buffer) + { + return usb_mtp_recv(data_buffer,60); + } + + void MTPD::write(const char *data, int len) + { if (write_get_length_) + { + write_length_ += len; + } + else + { + static uint8_t *dst=0; + if(!write_length_) dst=tx_data_buffer; + write_length_ += len; + + const char * src=data; + // + int pos = 0; // into data + while(posGetSize(object_id); + + if (write_get_length_) { + write_length_ += size; + } else + { + uint32_t pos = 0; // into data + uint32_t len = sizeof(MTPHeader); + + disk_pos=DISK_BUFFER_SIZE; + while(posread(object_id,pos,(char *)disk_buffer,nread); + disk_pos=0; + } + + uint32_t to_copy = min(size-pos,MTP_TX_SIZE-len); + to_copy = min (to_copy, DISK_BUFFER_SIZE-disk_pos); + + memcpy(tx_data_buffer+len,disk_buffer+disk_pos,to_copy); + disk_pos += to_copy; + pos += to_copy; + len += to_copy; + + if(len==MTP_TX_SIZE) + { push_packet(tx_data_buffer,MTP_TX_SIZE); + len=0; + } + } + if(len>0) + { push_packet(tx_data_buffer,MTP_TX_SIZE); + len=0; + } + } + } + uint32_t MTPD::GetPartialObject(uint32_t object_id, uint32_t offset, uint32_t NumBytes) + { + uint32_t size = storage_->GetSize(object_id); + + size -= offset; + if(NumBytes == 0xffffffff) NumBytes=size; + if (NumBytesread(object_id,pos,(char *)disk_buffer,nread); + disk_pos=0; + } + + uint32_t to_copy = min(size-pos,MTP_TX_SIZE-len); + to_copy = min (to_copy, DISK_BUFFER_SIZE-disk_pos); + + memcpy(tx_data_buffer+len,disk_buffer+disk_pos,to_copy); + disk_pos += to_copy; + pos += to_copy; + len += to_copy; + + if(len==MTP_TX_SIZE) + { push_packet(tx_data_buffer,MTP_TX_SIZE); + len=0; + } + } + if(len>0) + { push_packet(tx_data_buffer,MTP_TX_SIZE); + len=0; + } + } + return size; + } + + #define CONTAINER ((struct MTPContainer*)(rx_data_buffer)) + + #define TRANSMIT(FUN) do { \ + write_length_ = 0; \ + write_get_length_ = true; \ + FUN; \ + \ + MTPHeader header; \ + header.len = write_length_ + sizeof(header); \ + header.type = 2; \ + header.op = CONTAINER->op; \ + header.transaction_id = CONTAINER->transaction_id; \ + write_length_ = 0; \ + write_get_length_ = false; \ + write((char *)&header, sizeof(header)); \ + FUN; \ + \ + uint32_t rest; \ + rest = (header.len % MTP_TX_SIZE); \ + if(rest>0) \ + { \ + push_packet(tx_data_buffer,rest); \ + } \ + } while(0) + + #define TRANSMIT1(FUN) do { \ + write_length_ = 0; \ + write_get_length_ = true; \ + uint32_t dlen = FUN; \ + \ + MTPContainer header; \ + header.len = write_length_ + sizeof(MTPHeader)+4; \ + header.type = 2; \ + header.op = CONTAINER->op; \ + header.transaction_id = CONTAINER->transaction_id; \ + header.params[0]=dlen; \ + write_length_ = 0; \ + write_get_length_ = false; \ + write((char *)&header, sizeof(header)); \ + FUN; \ + \ + uint32_t rest; \ + rest = (header.len % MTP_TX_SIZE); \ + if(rest>0) \ + { \ + push_packet(tx_data_buffer,rest); \ + } \ + } while(0) + + + #define printContainer() \ + { printf("%x %d %d %d: ", CONTAINER->op, CONTAINER->len, CONTAINER->type, CONTAINER->transaction_id); \ + if(CONTAINER->len>12) printf(" %x", CONTAINER->params[0]); \ + if(CONTAINER->len>16) printf(" %x", CONTAINER->params[1]); \ + if(CONTAINER->len>20) printf(" %x", CONTAINER->params[2]); \ + printf("\r\n"); \ + } + + + void MTPD::read(char* data, uint32_t size) + { + static int index=0; + if(!size) + { + index=0; + return; + } + + while (size) { + uint32_t to_copy = MTP_RX_SIZE - index; + to_copy = min(to_copy, size); + if (data) { + memcpy(data, rx_data_buffer + index, to_copy); + data += to_copy; + } + size -= to_copy; + index += to_copy; + if (index == MTP_RX_SIZE) { + pull_packet(rx_data_buffer); + index=0; + } + } + } + + uint32_t MTPD::SendObjectInfo(uint32_t storage, uint32_t parent) { + pull_packet(rx_data_buffer); + read(0,0); // resync read +// printContainer(); + uint32_t store = Storage2Store(storage); + + int len=ReadMTPHeader(); + char filename[MAX_FILENAME_LEN]; + + read32(); len -=4; // storage + bool dir = (read16() == 0x3001); len -=2; // format + read16(); len -=2; // protection + read32(); len -=4; // size + read16(); len -=2; // thumb format + read32(); len -=4; // thumb size + read32(); len -=4; // thumb width + read32(); len -=4; // thumb height + read32(); len -=4; // pix width + read32(); len -=4; // pix height + read32(); len -=4; // bit depth + read32(); len -=4; // parent + read16(); len -=2; // association type + read32(); len -=4; // association description + read32(); len -=4; // sequence number + readstring(filename); len -= (2*(strlen(filename)+1)+1); + // ignore rest of ObjectInfo + while(len>=4) { read32(); len-=4;} + while(len) {read8(); len--;} + + return storage_->Create(store, parent, dir, filename); + } + + bool MTPD::SendObject() + { + pull_packet(rx_data_buffer); + read(0,0); +// printContainer(); + + uint32_t len = ReadMTPHeader(); + uint32_t index = sizeof(MTPHeader); + disk_pos=0; + + while((int)len>0) + { uint32_t bytes = MTP_RX_SIZE - index; // how many data in usb-packet + bytes = min(bytes,len); // loimit at end + uint32_t to_copy=min(bytes, DISK_BUFFER_SIZE-disk_pos); // how many data to copy to disk buffer + memcpy(disk_buffer+disk_pos, rx_data_buffer + index,to_copy); + disk_pos += to_copy; + bytes -= to_copy; + len -= to_copy; + //printf("a %d %d %d %d %d\n", len,disk_pos,bytes,index,to_copy); + // + if(disk_pos==DISK_BUFFER_SIZE) + { + if(storage_->write((const char *)disk_buffer, DISK_BUFFER_SIZE)0) // we have still data to be transfered + { pull_packet(rx_data_buffer); + index=0; + } + } + //printf("len %d\n",disk_pos); + if(disk_pos) + { + if(storage_->write((const char *)disk_buffer, disk_pos)close(); + return true; + } + + uint32_t MTPD::setObjectPropValue(uint32_t handle, uint32_t p2) + { pull_packet(rx_data_buffer); + read(0,0); + //printContainer(); + + if(p2==0xDC07) + { + char filename[MAX_FILENAME_LEN]; + ReadMTPHeader(); + readstring(filename); + if(storage_->rename(handle,filename)) return 0x2001; else return 0x2005; + } + else + return 0x2005; + } + + void MTPD::loop(void) + { if(!usb_mtp_available()) return; + if(fetch_packet(rx_data_buffer)) + { printContainer(); // to switch on set debug to 1 at beginning of file + + int op = CONTAINER->op; + int p1 = CONTAINER->params[0]; + int p2 = CONTAINER->params[1]; + int p3 = CONTAINER->params[2]; + int id = CONTAINER->transaction_id; + int len= CONTAINER->len; + int typ= CONTAINER->type; + TID=id; + + int return_code =0x2001; //OK use as default value + + if(typ==2) return_code=0x2005; // we should only get cmds + + switch (op) + { + case 0x1001: + TRANSMIT(WriteDescriptor()); + break; + + case 0x1002: //open session + openSession(p1); + break; + + case 0x1003: // CloseSession + break; + + case 0x1004: // GetStorageIDs + TRANSMIT(WriteStorageIDs()); + break; + + case 0x1005: // GetStorageInfo + TRANSMIT(GetStorageInfo(p1)); + break; + + case 0x1006: // GetNumObjects + if (p2) + { + return_code = 0x2014; // spec by format unsupported + } else + { + p1 = GetNumObjects(p1, p3); + } + break; + + case 0x1007: // GetObjectHandles + if (p2) + { return_code = 0x2014; // spec by format unsupported + } else + { + TRANSMIT(GetObjectHandles(p1, p3)); + } + break; + + case 0x1008: // GetObjectInfo + TRANSMIT(GetObjectInfo(p1)); + break; + + case 0x1009: // GetObject + TRANSMIT(GetObject(p1)); + break; + + case 0x100B: // DeleteObject + if (p2) { + return_code = 0x2014; // spec by format unsupported + } else { + if (!storage_->DeleteObject(p1)) { + return_code = 0x2012; // partial deletion + } + } + break; + + case 0x100C: // SendObjectInfo + p3 = SendObjectInfo(p1, // storage + p2); // parent + + CONTAINER->params[1]=p2; + CONTAINER->params[2]=p3; + len = 12 + 3 * 4; + break; + + case 0x100D: // SendObject + if(!SendObject()) return_code = 0x2005; + len = 12; + break; + + case 0x1014: // GetDevicePropDesc + TRANSMIT(GetDevicePropDesc(p1)); + break; + + case 0x1015: // GetDevicePropvalue + TRANSMIT(GetDevicePropValue(p1)); + break; + + case 0x1010: // Reset + return_code = 0x2005; + break; + + case 0x1019: // MoveObject + return_code = moveObject(p1,p2,p3); + len = 12; + break; + + case 0x101A: // CopyObject + return_code = copyObject(p1,p2,p3); + if(!return_code) + { return_code=0x2005; len = 12; } + else + { p1 = return_code; return_code=0x2001; len = 16; } + break; + + case 0x101B: // GetPartialObject + TRANSMIT1(GetPartialObject(p1,p2,p3)); + break; + + case 0x9801: // getObjectPropsSupported + TRANSMIT(getObjectPropsSupported(p1)); + break; + + case 0x9802: // getObjectPropDesc + TRANSMIT(getObjectPropDesc(p1,p2)); + break; + + case 0x9803: // getObjectPropertyValue + TRANSMIT(getObjectPropValue(p1,p2)); + break; + + case 0x9804: // setObjectPropertyValue + return_code = setObjectPropValue(p1,p2); + break; + + default: + return_code = 0x2005; // operation not supported + break; + } + if(return_code) + { + CONTAINER->type=3; + CONTAINER->len=len; + CONTAINER->op=return_code; + CONTAINER->transaction_id=id; + CONTAINER->params[0]=p1; + #if DEBUG >1 + printContainer(); // to switch on set debug to 2 at beginning of file + #endif + + memcpy(tx_data_buffer,rx_data_buffer,len); + push_packet(tx_data_buffer,len); // for acknowledge use rx_data_buffer + } + } + } + + +#endif +#if USE_EVENTS==1 + + #if defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) + + #include "usb_mtp.h" + extern "C" + { + usb_packet_t *tx_event_packet=NULL; + + int usb_init_events(void) + { + tx_event_packet = usb_malloc(); + if(tx_event_packet) return 1; else return 0; + } + + + int usb_mtp_sendEvent(const void *buffer, uint32_t len, uint32_t timeout) + { + if (!usb_configuration) return -1; + memcpy(tx_event_packet->buf, buffer, len); + tx_event_packet->len = len; + usb_tx(MTP_EVENT_ENDPOINT, tx_event_packet); + return len; + } + } + + #elif defined(__IMXRT1062__) + // keep this here until cores is upgraded + + #include "usb_mtp.h" + extern "C" + { + static transfer_t tx_event_transfer[1] __attribute__ ((used, aligned(32))); + static uint8_t tx_event_buffer[MTP_EVENT_SIZE] __attribute__ ((used, aligned(32))); + + static transfer_t rx_event_transfer[1] __attribute__ ((used, aligned(32))); + static uint8_t rx_event_buffer[MTP_EVENT_SIZE] __attribute__ ((used, aligned(32))); + + static uint32_t mtp_txEventcount=0; + static uint32_t mtp_rxEventcount=0; + + uint32_t get_mtp_txEventcount() {return mtp_txEventcount; } + uint32_t get_mtp_rxEventcount() {return mtp_rxEventcount; } + + static void txEvent_event(transfer_t *t) { mtp_txEventcount++;} + static void rxEvent_event(transfer_t *t) { mtp_rxEventcount++;} + + int usb_init_events(void) + { + usb_config_tx(MTP_EVENT_ENDPOINT, MTP_EVENT_SIZE, 0, txEvent_event); + // + usb_config_rx(MTP_EVENT_ENDPOINT, MTP_EVENT_SIZE, 0, rxEvent_event); + usb_prepare_transfer(rx_event_transfer + 0, rx_event_buffer, MTP_EVENT_SIZE, 0); + usb_receive(MTP_EVENT_ENDPOINT, rx_event_transfer + 0); + return 1; + } + + static int usb_mtp_wait(transfer_t *xfer, uint32_t timeout) + { + uint32_t wait_begin_at = systick_millis_count; + while (1) { + if (!usb_configuration) return -1; // usb not enumerated by host + uint32_t status = usb_transfer_status(xfer); + if (!(status & 0x80)) break; // transfer descriptor ready + if (systick_millis_count - wait_begin_at > timeout) return 0; + yield(); + } + return 1; + } + + int usb_mtp_recvEvent(void *buffer, uint32_t len, uint32_t timeout) + { + int ret= usb_mtp_wait(rx_event_transfer, timeout); if(ret<=0) return ret; + + memcpy(buffer, rx_event_buffer, len); + memset(rx_event_transfer, 0, sizeof(rx_event_transfer)); + + NVIC_DISABLE_IRQ(IRQ_USB1); + usb_prepare_transfer(rx_event_transfer + 0, rx_event_buffer, MTP_EVENT_SIZE, 0); + usb_receive(MTP_EVENT_ENDPOINT, rx_event_transfer + 0); + NVIC_ENABLE_IRQ(IRQ_USB1); + return MTP_EVENT_SIZE; + } + + int usb_mtp_sendEvent(const void *buffer, uint32_t len, uint32_t timeout) + { + transfer_t *xfer = tx_event_transfer; + int ret= usb_mtp_wait(xfer, timeout); if(ret<=0) return ret; + + uint8_t *eventdata = tx_event_buffer; + memcpy(eventdata, buffer, len); + usb_prepare_transfer(xfer, eventdata, len, 0); + usb_transmit(MTP_EVENT_ENDPOINT, xfer); + return len; + } + } + + #endif + const uint32_t EVENT_TIMEOUT=60; + + int MTPD::send_Event(uint16_t eventCode) + { + MTPContainer event; + event.len = 12; + event.op =eventCode ; + event.type = MTP_CONTAINER_TYPE_EVENT; + event.transaction_id=TID; + event.params[0]=0; + event.params[1]=0; + event.params[2]=0; + return usb_mtp_sendEvent((const void *) &event, event.len, EVENT_TIMEOUT); + } + int MTPD::send_Event(uint16_t eventCode, uint32_t p1) + { + MTPContainer event; + event.len = 16; + event.op =eventCode ; + event.type = MTP_CONTAINER_TYPE_EVENT; + event.transaction_id=TID; + event.params[0]=p1; + event.params[1]=0; + event.params[2]=0; + return usb_mtp_sendEvent((const void *) &event, event.len, EVENT_TIMEOUT); + } + int MTPD::send_Event(uint16_t eventCode, uint32_t p1, uint32_t p2) + { + MTPContainer event; + event.len = 20; + event.op =eventCode ; + event.type = MTP_CONTAINER_TYPE_EVENT; + event.transaction_id=TID; + event.params[0]=p1; + event.params[1]=p2; + event.params[2]=0; + return usb_mtp_sendEvent((const void *) &event, event.len, EVENT_TIMEOUT); + } + int MTPD::send_Event(uint16_t eventCode, uint32_t p1, uint32_t p2, uint32_t p3) + { + MTPContainer event; + event.len = 24; + event.op =eventCode ; + event.type = MTP_CONTAINER_TYPE_EVENT; + event.transaction_id=TID; + event.params[0]=p1; + event.params[1]=p2; + event.params[2]=p3; + return usb_mtp_sendEvent((const void *) &event, event.len, EVENT_TIMEOUT); + } + + int MTPD::send_DeviceResetEvent(void) + { return send_Event(MTP_EVENT_DEVICE_RESET); } + // following WIP + int MTPD::send_StorageInfoChangedEvent(uint32_t p1) + { return send_Event(MTP_EVENT_STORAGE_INFO_CHANGED, Store2Storage(p1));} + + // following not tested + int MTPD::send_addObjectEvent(uint32_t p1) + { return send_Event(MTP_EVENT_OBJECT_ADDED, p1); } + int MTPD::send_removeObjectEvent(uint32_t p1) + { return send_Event(MTP_EVENT_OBJECT_REMOVED, p1); } + +#endif +#endif diff --git a/Firmware_V3/lib/MTP/MTP.h b/Firmware_V3/lib/MTP/MTP.h new file mode 100644 index 0000000..db7edff --- /dev/null +++ b/Firmware_V3/lib/MTP/MTP.h @@ -0,0 +1,168 @@ +// MTP.h - Teensy MTP Responder library +// Copyright (C) 2017 Fredrik Hubinette +// +// With updates from MichaelMC and Yoong Hor Meng +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// modified for SDFS by WMXZ + +#ifndef MTP_H +#define MTP_H + +#if !defined(USB_MTPDISK) && !defined(USB_MTPDISK_SERIAL) + #error "You need to select USB Type: 'MTP Disk (Experimental)'" +#endif + +#include "core_pins.h" +#include "usb_dev.h" +extern "C" int usb_mtp_sendEvent(const void *buffer, uint32_t len, uint32_t timeout); + +#include "Storage.h" +// modify strings if needed (see MTP.cpp how they are used) +#define MTP_MANUF "PJRC" +#define MTP_MODEL "Teensy" +#define MTP_VERS "1.0" +#define MTP_SERNR "1234" +#define MTP_NAME "Teensy" + +#define USE_EVENTS 1 + +// MTP Responder. +class MTPD { +public: + + explicit MTPD(MTPStorageInterface* storage): storage_(storage) {} + +private: + MTPStorageInterface* storage_; + + struct MTPHeader { + uint32_t len; // 0 + uint16_t type; // 4 + uint16_t op; // 6 + uint32_t transaction_id; // 8 + }; + + struct MTPContainer { + uint32_t len; // 0 + uint16_t type; // 4 + uint16_t op; // 6 + uint32_t transaction_id; // 8 + uint32_t params[5]; // 12 + } __attribute__((__may_alias__)) ; + +#if defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) + usb_packet_t *data_buffer_ = NULL; + void get_buffer() ; + void receive_buffer() ; +// inline MTPContainer *contains (usb_packet_t *receive_buffer) { return (MTPContainer*)(receive_buffer->buf); } +// possible events for T3.xx ? + +#elif defined(__IMXRT1062__) + #define MTP_RX_SIZE MTP_RX_SIZE_480 + #define MTP_TX_SIZE MTP_TX_SIZE_480 + + uint8_t rx_data_buffer[MTP_RX_SIZE] __attribute__ ((aligned(32))); + uint8_t tx_data_buffer[MTP_TX_SIZE] __attribute__ ((aligned(32))); + + #define DISK_BUFFER_SIZE 8*1024 + uint8_t disk_buffer[DISK_BUFFER_SIZE] __attribute__ ((aligned(32))); + uint32_t disk_pos=0; + + int push_packet(uint8_t *data_buffer, uint32_t len); + int fetch_packet(uint8_t *data_buffer); + int pull_packet(uint8_t *data_buffer); + +#endif + + bool write_get_length_ = false; + uint32_t write_length_ = 0; + void write(const char *data, int len) ; + + void write8 (uint8_t x) ; + void write16(uint16_t x) ; + void write32(uint32_t x) ; + void write64(uint64_t x) ; + + void writestring(const char* str) ; + + void WriteDescriptor() ; + void WriteStorageIDs() ; + + void GetStorageInfo(uint32_t storage) ; + + uint32_t GetNumObjects(uint32_t storage, uint32_t parent) ; + + void GetObjectHandles(uint32_t storage, uint32_t parent) ; + + void GetObjectInfo(uint32_t handle) ; + void GetObject(uint32_t object_id) ; + uint32_t GetPartialObject(uint32_t object_id, uint32_t offset, uint32_t NumBytes) ; + + void read(char* data, uint32_t size) ; + + uint32_t ReadMTPHeader() ; + + uint8_t read8() ; + uint16_t read16() ; + uint32_t read32() ; + void readstring(char* buffer) ; + +// void read_until_short_packet() ; + + uint32_t SendObjectInfo(uint32_t storage, uint32_t parent) ; + bool SendObject() ; + + void GetDevicePropValue(uint32_t prop) ; + void GetDevicePropDesc(uint32_t prop) ; + void getObjectPropsSupported(uint32_t p1) ; + + void getObjectPropDesc(uint32_t p1, uint32_t p2) ; + void getObjectPropValue(uint32_t p1, uint32_t p2) ; + + uint32_t setObjectPropValue(uint32_t p1, uint32_t p2) ; + + uint32_t deleteObject(uint32_t p1) ; + uint32_t copyObject(uint32_t p1,uint32_t p2, uint32_t p3) ; + uint32_t moveObject(uint32_t p1,uint32_t p2, uint32_t p3) ; + void openSession(uint32_t id) ; + + uint32_t TID; +#if USE_EVENTS==1 + int send_Event(uint16_t eventCode); + int send_Event(uint16_t eventCode, uint32_t p1); + int send_Event(uint16_t eventCode, uint32_t p1, uint32_t p2); + int send_Event(uint16_t eventCode, uint32_t p1, uint32_t p2, uint32_t p3); +#endif + +public: + void loop(void) ; + void test(void) ; + +#if USE_EVENTS==1 + int send_addObjectEvent(uint32_t p1); + int send_removeObjectEvent(uint32_t p1); + int send_StorageInfoChangedEvent(uint32_t p1); + int send_StorageRemovedEvent(uint32_t p1); + int send_DeviceResetEvent(void); +#endif +}; + +#endif diff --git a/Firmware_V3/lib/MTP/Storage.cpp b/Firmware_V3/lib/MTP/Storage.cpp new file mode 100644 index 0000000..d02624b --- /dev/null +++ b/Firmware_V3/lib/MTP/Storage.cpp @@ -0,0 +1,709 @@ +// Storage.cpp - Teensy MTP Responder library +// Copyright (C) 2017 Fredrik Hubinette +// +// With updates from MichaelMC and Yoong Hor Meng +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// modified for SDFS by WMXZ +// Nov 2020 adapted to SdFat-beta / SD combo + +#include "core_pins.h" +#include "usb_dev.h" +#include "usb_serial.h" + +#include "Storage.h" + +#define DEBUG 0 + +#if DEBUG>0 + #define USE_DBG_MACROS 1 +#else + #define USE_DBG_MACROS 0 +#endif + +#define DBG_FILE "Storage.cpp" + +#if USE_DBG_MACROS==1 + static void dbgPrint(uint16_t line) { + Serial.print(F("DBG_FAIL: ")); + Serial.print(F(DBG_FILE)); + Serial.write('.'); + Serial.println(line); + } + + #define DBG_PRINT_IF(b) if (b) {Serial.print(F(__FILE__));\ + Serial.println(__LINE__);} + #define DBG_HALT_IF(b) if (b) { Serial.print(F("DBG_HALT "));\ + Serial.print(F(__FILE__)); Serial.println(__LINE__);\ + while (true) {}} + #define DBG_FAIL_MACRO dbgPrint(__LINE__); +#else // USE_DBG_MACROS + #define DBG_FAIL_MACRO + #define DBG_PRINT_IF(b) + #define DBG_HALT_IF(b) +#endif // USE_DBG_MACROS + + #define sd_isOpen(x) (x) + #define sd_getName(x,y,n) strlcpy(y,x.name(),n) + + #define indexFile "/mtpindex.dat" +// TODO: +// support serialflash +// partial object fetch/receive +// events (notify usb host when local storage changes) (But, this seems too difficult) + +// These should probably be weak. +void mtp_yield() {} +void mtp_lock_storage(bool lock) {} + + bool MTPStorage_SD::readonly(uint32_t store) { return false; } + bool MTPStorage_SD::has_directories(uint32_t store) { return true; } + + uint64_t MTPStorage_SD::totalSize(uint32_t store) { return sd_totalSize(store); } + uint64_t MTPStorage_SD::usedSize(uint32_t store) { return sd_usedSize(store); } + + void MTPStorage_SD::CloseIndex() + { + mtp_lock_storage(true); + if(sd_isOpen(index_)) index_.close(); + mtp_lock_storage(false); + index_generated = false; + index_entries_ = 0; + } + + void MTPStorage_SD::OpenIndex() + { if(sd_isOpen(index_)) return; // only once + mtp_lock_storage(true); + index_=sd_open(0,indexFile, FILE_WRITE_BEGIN); + if(!index_) Serial.println("cannot open Index file"); + mtp_lock_storage(false); + } + + void MTPStorage_SD::ResetIndex() { + if(!sd_isOpen(index_)) return; + CloseIndex(); +// OpenIndex(); + + all_scanned_ = false; + open_file_ = 0xFFFFFFFEUL; + } + + void MTPStorage_SD::WriteIndexRecord(uint32_t i, const Record& r) + { OpenIndex(); + mtp_lock_storage(true); + index_.seek((sizeof(r) * i)); + index_.write((char*)&r, sizeof(r)); + mtp_lock_storage(false); + } + + uint32_t MTPStorage_SD::AppendIndexRecord(const Record& r) + { uint32_t new_record = index_entries_++; + WriteIndexRecord(new_record, r); + return new_record; + } + + // TODO(hubbe): Cache a few records for speed. + Record MTPStorage_SD::ReadIndexRecord(uint32_t i) + { + Record ret; + memset(&ret, 0, sizeof(ret)); + if (i > index_entries_) + { memset(&ret, 0, sizeof(ret)); + return ret; + } + OpenIndex(); + mtp_lock_storage(true); + index_.seek(sizeof(ret) * i); + index_.read((char *)&ret, sizeof(ret)); + mtp_lock_storage(false); + + return ret; + } + + uint16_t MTPStorage_SD::ConstructFilename(int i, char* out, int len) // construct filename rexursively + { + Record tmp = ReadIndexRecord(i); + + if (tmp.parent==0xFFFFFFFFUL) //flags the root object + { strcpy(out, "/"); + return tmp.store; + } + else + { ConstructFilename(tmp.parent, out, len); + if (out[strlen(out)-1] != '/') strlcat(out, "/",len); + strlcat(out, tmp.name,len); + return tmp.store; + } + } + + void MTPStorage_SD::OpenFileByIndex(uint32_t i, uint32_t mode) + { + if (open_file_ == i && mode_ == mode) return; + char filename[MAX_FILENAME_LEN]; + uint16_t store = ConstructFilename(i, filename, MAX_FILENAME_LEN); + + mtp_lock_storage(true); + if(sd_isOpen(file_)) file_.close(); + file_=sd_open(store,filename,mode); + open_file_ = i; + mode_ = mode; + mtp_lock_storage(false); + } + + // MTP object handles should not change or be re-used during a session. + // This would be easy if we could just have a list of all files in memory. + // Since our RAM is limited, we'll keep the index in a file instead. + void MTPStorage_SD::GenerateIndex(uint32_t store) + { if (index_generated) return; + index_generated = true; + // first remove old index file + mtp_lock_storage(true); + sd_remove(0,indexFile); + mtp_lock_storage(false); + + num_storage = sd_getFSCount(); + + index_entries_ = 0; + Record r; + for(int ii=0; ii= index_entries_) next_ = 0; + } + if (r.name[0]) return ret; + } + } + + void MTPStorage_SD::GetObjectInfo(uint32_t handle, char* name, uint32_t* size, uint32_t* parent, uint16_t *store) + { + Record r = ReadIndexRecord(handle); + strcpy(name, r.name); + *parent = r.parent; + *size = r.isdir ? 0xFFFFFFFFUL : r.child; + *store = r.store; + } + + uint32_t MTPStorage_SD::GetSize(uint32_t handle) + { + return ReadIndexRecord(handle).child; + } + + void MTPStorage_SD::read(uint32_t handle, uint32_t pos, char* out, uint32_t bytes) + { + OpenFileByIndex(handle); + mtp_lock_storage(true); + file_.seek(pos); + file_.read(out,bytes); + mtp_lock_storage(false); + } + +void MTPStorage_SD::removeFile(uint32_t store, char *file) +{ + char tname[MAX_FILENAME_LEN]; + + File f1=sd_open(store,file,0); + File f2; + while(f2=f1.openNextFile()) + { sprintf(tname,"%s/%s",file,f2.name()); + if(f2.isDirectory()) removeFile(store,tname); else sd_remove(store,tname); + } + sd_rmdir(store,file); +} + + bool MTPStorage_SD::DeleteObject(uint32_t object) + { + if(object==0xFFFFFFFFUL) return true; // don't do anything if trying to delete a root directory see below + + // first create full filename + char filename[MAX_FILENAME_LEN]; + ConstructFilename(object, filename, MAX_FILENAME_LEN); + + Record r = ReadIndexRecord(object); + + // remove file from storage (assume it is always working) + mtp_lock_storage(true); + removeFile(r.store,filename); + mtp_lock_storage(false); + + // mark object as deleted + r.name[0]=0; + WriteIndexRecord(object, r); + + // update index file + Record t = ReadIndexRecord(r.parent); + if(t.child==object) + { // we are the jungest, simply relink parent to older sibling + t.child = r.sibling; + WriteIndexRecord(r.parent, t); + } + else + { // link junger to older sibling + // find junger sibling + uint32_t is = t.child; + Record x = ReadIndexRecord(is); + while((x.sibling != object)) { is=x.sibling; x=ReadIndexRecord(is);} + // is points now to junder sibling + x.sibling = r.sibling; + WriteIndexRecord(is, x); + } + return 1; + } + + uint32_t MTPStorage_SD::Create(uint32_t store, uint32_t parent, bool folder, const char* filename) + { + uint32_t ret; + if (parent == 0xFFFFFFFFUL) parent = store; + Record p = ReadIndexRecord(parent); + Record r; + strlcpy(r.name, filename,MAX_FILENAME_LEN); + r.store = p.store; + r.parent = parent; + r.child = 0; + r.sibling = p.child; + r.isdir = folder; + // New folder is empty, scanned = true. + r.scanned = 1; + ret = p.child = AppendIndexRecord(r); + WriteIndexRecord(parent, p); + if (folder) + { + char filename[MAX_FILENAME_LEN]; + ConstructFilename(ret, filename, MAX_FILENAME_LEN); + mtp_lock_storage(true); + sd_mkdir(store,filename); + mtp_lock_storage(false); + } + else + { + OpenFileByIndex(ret, FILE_WRITE_BEGIN); + } + #if DEBUG>1 + Serial.print("Create "); + Serial.print(ret); Serial.print(" "); + Serial.print(store); Serial.print(" "); + Serial.print(parent); Serial.print(" "); + Serial.println(filename); + #endif + return ret; + } + + size_t MTPStorage_SD::write(const char* data, uint32_t bytes) + { + mtp_lock_storage(true); + size_t ret = file_.write(data,bytes); + mtp_lock_storage(false); + return ret; + } + + void MTPStorage_SD::close() + { + mtp_lock_storage(true); + uint32_t size = (uint32_t) file_.size(); + file_.close(); + mtp_lock_storage(false); + // + // update record with file size + Record r = ReadIndexRecord(open_file_); + r.child = size; + WriteIndexRecord(open_file_, r); + open_file_ = 0xFFFFFFFEUL; + } + + bool MTPStorage_SD::rename(uint32_t handle, const char* name) + { char oldName[MAX_FILENAME_LEN]; + char newName[MAX_FILENAME_LEN]; + char temp[MAX_FILENAME_LEN]; + + uint16_t store = ConstructFilename(handle, oldName, MAX_FILENAME_LEN); + Serial.println(oldName); + + Record p1 = ReadIndexRecord(handle); + strlcpy(temp,p1.name,MAX_FILENAME_LEN); + strlcpy(p1.name,name,MAX_FILENAME_LEN); + + WriteIndexRecord(handle, p1); + ConstructFilename(handle, newName, MAX_FILENAME_LEN); + Serial.println(newName); + + if (sd_rename(store,oldName,newName)) return true; + + // rename failed; undo index update + strlcpy(p1.name,temp,MAX_FILENAME_LEN); + WriteIndexRecord(handle, p1); + return false; + } + + void MTPStorage_SD::dumpIndexList(void) + { for(uint32_t ii=0; iistore,p->isdir,p->parent,p->sibling,p->child); } + + +/* + * //index list management for moving object around + * p1 is record of handle + * p2 is record of new dir + * p3 is record of old dir + * + * // remove from old direcory + * if p3.child == handle / handle is last in old dir + * p3.child = p1.sibling / simply relink old dir + * save p3 + * else + * px record of p3.child + * while( px.sibling != handle ) update px = record of px.sibling + * px.sibling = p1.sibling + * save px + * + * // add to new directory + * p1.parent = new + * p1.sibling = p2.child + * p2.child = handle + * save p1 + * save p2 + * +*/ + + bool MTPStorage_SD::move(uint32_t handle, uint32_t newStore, uint32_t newParent ) + { + #if DEBUG>1 + Serial.printf("%d -> %d %d\n",handle,newStorage,newParent); + #endif + if(newParent==0xFFFFFFFFUL) newParent=newStore; //storage runs from 1, while record.store runs from 0 + + Record p1 = ReadIndexRecord(handle); + Record p2 = ReadIndexRecord(newParent); + Record p3 = ReadIndexRecord(p1.parent); + + if(p1.isdir) + { if(!p1.scanned) + { ScanDir(p1.store, handle) ; // in case scan directory + WriteIndexRecord(handle, p1); + } + } + + Record p1o = p1; + Record p2o = p2; + Record p3o = p3; + + char oldName[MAX_FILENAME_LEN]; + ConstructFilename(handle, oldName, MAX_FILENAME_LEN); + + #if DEBUG>1 + Serial.print(p1.store); Serial.print(": "); Serial.println(oldName); + dumpIndexList(); + #endif + + uint32_t jx=-1; + Record pxo; + + // remove index from old parent + Record px; + if(p3.child==handle) + { + p3.child = p1.sibling; + WriteIndexRecord(p1.parent, p3); + } + else + { jx = p3.child; + px = ReadIndexRecord(jx); + pxo = px; + while(handle != px.sibling) + { + jx = px.sibling; + px = ReadIndexRecord(jx); + pxo = px; + } + px.sibling = p1.sibling; + WriteIndexRecord(jx, px); + } + + // add to new parent + p1.parent = newParent; + p1.store = p2.store; + p1.sibling = p2.child; + p2.child = handle; + WriteIndexRecord(handle, p1); + WriteIndexRecord(newParent,p2); + + // now working on disk storage + char newName[MAX_FILENAME_LEN]; + ConstructFilename(handle, newName, MAX_FILENAME_LEN); + + #if DEBUG>1 + Serial.print(p1.store); Serial.print(": ");Serial.println(newName); + dumpIndexList(); + #endif + + + if(p1o.store == p2o.store) + { // do a simple rename (works for files and directories) + if(sd_rename(p1o.store,oldName,newName)) return true; else {DBG_FAIL_MACRO; goto fail;} + } + else if(!p1o.isdir) + { if(sd_copy(p1o.store,oldName, p2o.store, newName)) + { sd_remove(p2o.store,oldName); return true; } else { DBG_FAIL_MACRO; goto fail;} + } + else + { // move directory cross mtp-disks + if(sd_moveDir(p1o.store,oldName,p2o.store,newName)) return true; else {DBG_FAIL_MACRO; goto fail;} + } + + fail: + // undo changes in index list + if(jx<0) WriteIndexRecord(p1.parent, p3o); else WriteIndexRecord(jx, pxo); + WriteIndexRecord(handle, p1o); + WriteIndexRecord(newParent,p2o); + return false; + } + + uint32_t MTPStorage_SD::copy(uint32_t handle, uint32_t newStore, uint32_t newParent ) + { + if(newParent==0xFFFFFFFFUL) newParent=newStore; + + Record p1 = ReadIndexRecord(handle); + Record p2 = ReadIndexRecord(newParent); + + uint32_t newHandle; + if(p1.isdir) + { + ScanDir(p1.store+1,handle); + newHandle = Create(p2.store,newParent,p1.isdir,p1.name); + CopyFiles(handle, p2.store, newHandle); + } + else + { + Record r; + strlcpy(r.name, p1.name,MAX_FILENAME_LEN); + r.store = p2.store; + r.parent = newParent; + r.child = 0; + r.sibling = p2.child; + r.isdir = 0; + r.scanned = 0; + newHandle = p2.child = AppendIndexRecord(r); + WriteIndexRecord(newParent, p2); + + char oldfilename[MAX_FILENAME_LEN]; + char newfilename[MAX_FILENAME_LEN]; + uint32_t store0 = ConstructFilename(handle,oldfilename,MAX_FILENAME_LEN); + uint32_t store1 = ConstructFilename(newHandle,newfilename,MAX_FILENAME_LEN); + + sd_copy(store0,oldfilename,store1,newfilename); + } + + return newHandle; + } + +bool MTPStorage_SD::CopyFiles(uint32_t handle, uint32_t store, uint32_t newHandle) +{ // assume handle and newHandle point to existing directories + if(newHandle==0xFFFFFFFFUL) newHandle=store; + #if DEBUG>1 + Serial.printf("%d -> %d\n",handle,newHandle); + #endif + + Record p1=ReadIndexRecord(handle); + Record p2=ReadIndexRecord(newHandle); + uint32_t ix= p1.child; + uint32_t iy= 0; + while(ix) + { // get child + Record px = ReadIndexRecord(ix) ; + Record py = px; + py.store = p2.store; + py.parent = newHandle; + py.sibling = iy; + iy = AppendIndexRecord(py); + + char oldfilename[MAX_FILENAME_LEN]; + char newfilename[MAX_FILENAME_LEN]; + ConstructFilename(ix,oldfilename,MAX_FILENAME_LEN); + ConstructFilename(iy,newfilename,MAX_FILENAME_LEN); + + if(py.isdir) + { + sd_mkdir(py.store,newfilename); + + ScanDir(p1.store,ix); + CopyFiles(ix,p2.store,iy); + } + else + { sd_copy(p1.store,oldfilename,py.store,newfilename); + } + ix = px.sibling; + } + p2.child=iy; + WriteIndexRecord(newHandle,p2); + return true; +} +/************************************** mSD_Base *******************************/ +bool mSD_Base::sd_copy(uint32_t store0, char *oldfilename, uint32_t store1, char *newfilename) +{ + const int nbuf = 2048; + char buffer[nbuf]; + int nd=-1; + + #if DEBUG>1 + Serial.print("From "); Serial.print(store0); Serial.print(": ");Serial.println(oldfilename); + Serial.print("To "); Serial.print(store1); Serial.print(": ");Serial.println(newfilename); + #endif + + File f1 = sd_open(store0,oldfilename,FILE_READ); if(!f1) {DBG_FAIL_MACRO; return false;} + File f2 = sd_open(store1,newfilename,FILE_WRITE_BEGIN); + if(!f2) { f1.close(); {DBG_FAIL_MACRO; return false;}} + + while(f1.available()>0) + { + nd=f1.read(buffer,nbuf); + if(nd<0) break; // read error + f2.write(buffer,nd); + if(nd +// +// With updates from MichaelMC and Yoong Hor Meng +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// modified for SDFS by WMXZ +// Nov 2020 adapted to SdFat-beta / SD combo +// 19-nov-2020 adapted to FS + +#ifndef Storage_H +#define Storage_H + +#include "core_pins.h" + +#include "FS.h" +#ifndef FILE_WRITE_BEGIN + #define FILE_WRITE_BEGIN 2 +#endif + + +#define MTPD_MAX_FILESYSTEMS 20 +#ifndef MAX_FILENAME_LEN + #define MAX_FILENAME_LEN 256 +#endif + +class mSD_Base +{ + public: + mSD_Base() { + fsCount = 0; + } + + void sd_addFilesystem(FS &fs, const char *name) { + if (fsCount < MTPD_MAX_FILESYSTEMS) { + sd_name[fsCount] = name; + sdx[fsCount++] = &fs; + } + } + + uint32_t sd_getStoreID( const char *name) + { + for(int ii=0; iiopen(filename,mode); } + bool sd_mkdir(uint32_t store, char *filename) { return sdx[store]->mkdir(filename); } + bool sd_rename(uint32_t store, char *oldfilename, char *newfilename) { return sdx[store]->rename(oldfilename,newfilename); } + bool sd_remove(uint32_t store, const char *filename) { return sdx[store]->remove(filename); } + bool sd_rmdir(uint32_t store, char *filename) { return sdx[store]->rmdir(filename); } + + uint64_t sd_totalSize(uint32_t store) { return sdx[store]->totalSize(); } + uint64_t sd_usedSize(uint32_t store) { return sdx[store]->usedSize(); } + + bool sd_copy(uint32_t store0, char *oldfilename, uint32_t store1, char *newfilename); + bool sd_moveDir(uint32_t store0, char *oldfilename, uint32_t store1, char *newfilename); + + private: + int fsCount; + const char *sd_name[MTPD_MAX_FILESYSTEMS]; + FS *sdx[MTPD_MAX_FILESYSTEMS]; +}; + +// This interface lets the MTP responder interface any storage. +// We'll need to give the MTP responder a pointer to one of these. +class MTPStorageInterface { +public: + virtual void addFilesystem(FS &filesystem, const char *name)=0; + virtual uint32_t get_FSCount(void) = 0; + virtual const char *get_FSName(uint32_t storage) = 0; + + virtual uint64_t totalSize(uint32_t storage) = 0; + virtual uint64_t usedSize(uint32_t storage) = 0; + + // Return true if this storage is read-only + virtual bool readonly(uint32_t storage) = 0; + + // Does it have directories? + virtual bool has_directories(uint32_t storage) = 0; + + virtual void StartGetObjectHandles(uint32_t storage, uint32_t parent) = 0; + virtual uint32_t GetNextObjectHandle(uint32_t storage) = 0; + + virtual void GetObjectInfo(uint32_t handle, char* name, uint32_t* size, uint32_t* parent, uint16_t *store) = 0; + virtual uint32_t GetSize(uint32_t handle) = 0; + + virtual uint32_t Create(uint32_t storage, uint32_t parent, bool folder, const char* filename) = 0; + virtual void read(uint32_t handle, uint32_t pos, char* buffer, uint32_t bytes) = 0; + virtual size_t write(const char* data, uint32_t size); + virtual void close() = 0; + virtual bool DeleteObject(uint32_t object) = 0; + virtual void CloseIndex() = 0; + + virtual void ResetIndex() = 0; + virtual bool rename(uint32_t handle, const char* name) = 0 ; + virtual bool move(uint32_t handle, uint32_t newStorage, uint32_t newParent) = 0 ; + virtual uint32_t copy(uint32_t handle, uint32_t newStorage, uint32_t newParent) = 0 ; + + virtual bool CopyFiles(uint32_t storage, uint32_t handle, uint32_t newHandle) = 0; +}; + + struct Record + { uint32_t parent; + uint32_t child; // size stored here for files + uint32_t sibling; + uint8_t isdir; + uint8_t scanned; + uint16_t store; // index int physical storage (0 ... num_storages-1) + char name[MAX_FILENAME_LEN]; + + + }; + + void mtp_yield(void); + + +// Storage implementation for SD. SD needs to be already initialized. +class MTPStorage_SD : public MTPStorageInterface, mSD_Base +{ +public: + void addFilesystem(FS &fs, const char *name) { sd_addFilesystem(fs, name);} + void dumpIndexList(void); + uint32_t getStoreID(const char *name) {return sd_getStoreID(name);} + void close() override ; + +private: + File index_; + File file_; + File child_; + + int num_storage = 0; + const char **sd_str = 0; + + uint32_t mode_ = 0; + uint32_t open_file_ = 0xFFFFFFFEUL; + + bool readonly(uint32_t storage); + bool has_directories(uint32_t storage) ; + + uint64_t totalSize(uint32_t storage) ; + uint64_t usedSize(uint32_t storage) ; + + void CloseIndex() ; + void OpenIndex() ; + void GenerateIndex(uint32_t storage) ; + void ScanDir(uint32_t storage, uint32_t i) ; + void ScanAll(uint32_t storage) ; + + void removeFile(uint32_t store, char *filename); + + uint32_t index_entries_ = 0; + bool index_generated = false; + + bool all_scanned_ = false; + uint32_t next_; + bool follow_sibling_; + + void WriteIndexRecord(uint32_t i, const Record& r) ; + uint32_t AppendIndexRecord(const Record& r) ; + Record ReadIndexRecord(uint32_t i) ; + uint16_t ConstructFilename(int i, char* out, int len) ; + void OpenFileByIndex(uint32_t i, uint32_t mode = FILE_READ) ; + void printRecord(int h, Record *p); + + uint32_t get_FSCount(void) {return sd_getFSCount();} + const char *get_FSName(uint32_t storage) { return sd_getFSName(storage);} + + void StartGetObjectHandles(uint32_t storage, uint32_t parent) override ; + uint32_t GetNextObjectHandle(uint32_t storage) override ; + void GetObjectInfo(uint32_t handle, char* name, uint32_t* size, uint32_t* parent, uint16_t *store) override ; + uint32_t GetSize(uint32_t handle) override; + void read(uint32_t handle, uint32_t pos, char* out, uint32_t bytes) override ; + bool DeleteObject(uint32_t object) override ; + + uint32_t Create(uint32_t storage, uint32_t parent, bool folder, const char* filename) override ; + + size_t write(const char* data, uint32_t bytes) override ; + + bool rename(uint32_t handle, const char* name) override ; + bool move(uint32_t handle, uint32_t newStorage, uint32_t newParent) override ; + uint32_t copy(uint32_t handle, uint32_t newStorage, uint32_t newParent) override ; + + bool CopyFiles(uint32_t storage, uint32_t handle, uint32_t newHandle) override ; + void ResetIndex() override ; +}; + +#endif diff --git a/Firmware_V3/lib/Metro/Metro.cpp b/Firmware_V3/lib/Metro/Metro.cpp new file mode 100644 index 0000000..f8bd4a6 --- /dev/null +++ b/Firmware_V3/lib/Metro/Metro.cpp @@ -0,0 +1,73 @@ + +#if defined(ARDUINO) && ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif +#include "Metro.h" + + +void Metro::begin(unsigned long interval_millis) +{ + this->autoreset = 0; + interval(interval_millis); + reset(); +} + +// New creator so I can use either the original check behavior or benjamin.soelberg's +// suggested one (see below). +// autoreset = 0 is benjamin.soelberg's check behavior +// autoreset != 0 is the original behavior + +void Metro::begin(unsigned long interval_millis, uint8_t autoreset) +{ + this->autoreset = autoreset; // Fix by Paul Bouchier + interval(interval_millis); + reset(); +} + +void Metro::interval(unsigned long interval_millis) +{ + this->interval_millis = interval_millis; +} + +// Benjamin.soelberg's check behavior: +// When a check is true, add the interval to the internal counter. +// This should guarantee a better overall stability. + +// Original check behavior: +// When a check is true, add the interval to the current millis() counter. +// This method can add a certain offset over time. + +char Metro::check() +{ + if (millis() - this->previous_millis >= this->interval_millis) { + // As suggested by benjamin.soelberg@gmail.com, the following line + // this->previous_millis = millis(); + // was changed to + // this->previous_millis += this->interval_millis; + + // If the interval is set to 0 we revert to the original behavior + if (this->interval_millis <= 0 || this->autoreset ) { + this->previous_millis = millis(); + } else { + this->previous_millis += this->interval_millis; + } + + return 1; + } + + + + return 0; + +} + +void Metro::reset() +{ + + this->previous_millis = millis(); + +} + + diff --git a/Firmware_V3/lib/Metro/Metro.h b/Firmware_V3/lib/Metro/Metro.h new file mode 100644 index 0000000..f8117a2 --- /dev/null +++ b/Firmware_V3/lib/Metro/Metro.h @@ -0,0 +1,26 @@ + + +#ifndef Metro_h +#define Metro_h + +#include + +class Metro +{ + +public: + void begin(unsigned long interval_millis); + void begin(unsigned long interval_millis, uint8_t autoreset); + void interval(unsigned long interval_millis); + char check(); + void reset(); + +private: + uint8_t autoreset; + unsigned long previous_millis, interval_millis; + +}; + +#endif + + diff --git a/Firmware_V3/lib/SD/examples/CardInfo/CardInfo.ino b/Firmware_V3/lib/SD/examples/CardInfo/CardInfo.ino new file mode 100644 index 0000000..44ea4e4 --- /dev/null +++ b/Firmware_V3/lib/SD/examples/CardInfo/CardInfo.ino @@ -0,0 +1,123 @@ +/* + SD card test + + This example shows how use the utility libraries on which the' + SD library is based in order to get info about your SD card. + Very useful for testing a card when you're not sure whether its working or not. + + The circuit: + * SD card attached to SPI bus as follows: + ** MOSI - pin 11 on Arduino Uno/Duemilanove/Diecimila, pin 7 on Teensy with audio board + ** MISO - pin 12 on Arduino Uno/Duemilanove/Diecimila + ** CLK - pin 13 on Arduino Uno/Duemilanove/Diecimila, pin 14 on Teensy with audio board + ** CS - depends on your SD card shield or module - pin 10 on Teensy with audio board + Pin 4 used here for consistency with other Arduino examples + + + created 28 Mar 2011 + by Limor Fried + modified 9 Apr 2012 + by Tom Igoe + */ + // include the SD library: +#include +#include + +// set up variables using the SD utility library functions: +Sd2Card card; +SdVolume volume; +SdFile root; + +// change this to match your SD shield or module; +// Arduino Ethernet shield: pin 4 +// Adafruit SD shields and modules: pin 10 +// Sparkfun SD shield: pin 8 +// Teensy audio board: pin 10 +// Teensy 3.5 & 3.6 & 4.1 on-board: BUILTIN_SDCARD +// Wiz820+SD board: pin 4 +// Teensy 2.0: pin 0 +// Teensy++ 2.0: pin 20 +const int chipSelect = 4; + +void setup() +{ + //UNCOMMENT THESE TWO LINES FOR TEENSY AUDIO BOARD: + //SPI.setMOSI(7); // Audio shield has MOSI on pin 7 + //SPI.setSCK(14); // Audio shield has SCK on pin 14 + + // Open serial communications and wait for port to open: + Serial.begin(9600); + while (!Serial) { + ; // wait for serial port to connect. + } + + + Serial.print("\nInitializing SD card..."); + + + // we'll use the initialization code from the utility libraries + // since we're just testing if the card is working! + if (!card.init(SPI_HALF_SPEED, chipSelect)) { + Serial.println("initialization failed. Things to check:"); + Serial.println("* is a card inserted?"); + Serial.println("* is your wiring correct?"); + Serial.println("* did you change the chipSelect pin to match your shield or module?"); + return; + } else { + Serial.println("Wiring is correct and a card is present."); + } + + // print the type of card + Serial.print("\nCard type: "); + switch(card.type()) { + case SD_CARD_TYPE_SD1: + Serial.println("SD1"); + break; + case SD_CARD_TYPE_SD2: + Serial.println("SD2"); + break; + case SD_CARD_TYPE_SDHC: + Serial.println("SDHC"); + break; + default: + Serial.println("Unknown"); + } + + // Now we will try to open the 'volume'/'partition' - it should be FAT16 or FAT32 + if (!volume.init(card)) { + Serial.println("Could not find FAT16/FAT32 partition.\nMake sure you've formatted the card"); + return; + } + + + // print the type and size of the first FAT-type volume + uint32_t volumesize; + Serial.print("\nVolume type is FAT"); + Serial.println(volume.fatType(), DEC); + Serial.println(); + + volumesize = volume.blocksPerCluster(); // clusters are collections of blocks + volumesize *= volume.clusterCount(); // we'll have a lot of clusters + if (volumesize < 8388608ul) { + Serial.print("Volume size (bytes): "); + Serial.println(volumesize * 512); // SD card blocks are always 512 bytes + } + Serial.print("Volume size (Kbytes): "); + volumesize /= 2; + Serial.println(volumesize); + Serial.print("Volume size (Mbytes): "); + volumesize /= 1024; + Serial.println(volumesize); + + + //Serial.println("\nFiles found on the card (name, date and size in bytes): "); + //root.openRoot(volume); + + // list all files in the card with date and size + //root.ls(LS_R | LS_DATE | LS_SIZE); +} + + +void loop(void) { + +} diff --git a/Firmware_V3/lib/SD/examples/Datalogger/Datalogger.ino b/Firmware_V3/lib/SD/examples/Datalogger/Datalogger.ino new file mode 100644 index 0000000..3373417 --- /dev/null +++ b/Firmware_V3/lib/SD/examples/Datalogger/Datalogger.ino @@ -0,0 +1,99 @@ +/* + SD card datalogger + + This example shows how to log data from three analog sensors + to an SD card using the SD library. + + The circuit: + * analog sensors on analog ins 0, 1, and 2 + * SD card attached to SPI bus as follows: + ** MOSI - pin 11, pin 7 on Teensy with audio board + ** MISO - pin 12 + ** CLK - pin 13, pin 14 on Teensy with audio board + ** CS - pin 4, pin 10 on Teensy with audio board + + created 24 Nov 2010 + modified 9 Apr 2012 + by Tom Igoe + + This example code is in the public domain. + */ + +#include +#include + +// On the Ethernet Shield, CS is pin 4. Note that even if it's not +// used as the CS pin, the hardware CS pin (10 on most Arduino boards, +// 53 on the Mega) must be left as an output or the SD library +// functions will not work. + +// change this to match your SD shield or module; +// Arduino Ethernet shield: pin 4 +// Adafruit SD shields and modules: pin 10 +// Sparkfun SD shield: pin 8 +// Teensy audio board: pin 10 +// Teensy 3.5 & 3.6 & 4.1 on-board: BUILTIN_SDCARD +// Wiz820+SD board: pin 4 +// Teensy 2.0: pin 0 +// Teensy++ 2.0: pin 20 +const int chipSelect = 4; + +void setup() +{ + //UNCOMMENT THESE TWO LINES FOR TEENSY AUDIO BOARD: + //SPI.setMOSI(7); // Audio shield has MOSI on pin 7 + //SPI.setSCK(14); // Audio shield has SCK on pin 14 + + // Open serial communications and wait for port to open: + Serial.begin(9600); + while (!Serial) { + ; // wait for serial port to connect. + } + + + Serial.print("Initializing SD card..."); + + // see if the card is present and can be initialized: + if (!SD.begin(chipSelect)) { + Serial.println("Card failed, or not present"); + while (1) { + // No SD card, so don't do anything more - stay stuck here + } + } + Serial.println("card initialized."); +} + +void loop() +{ + // make a string for assembling the data to log: + String dataString = ""; + + // read three sensors and append to the string: + for (int analogPin = 0; analogPin < 3; analogPin++) { + int sensor = analogRead(analogPin); + dataString += String(sensor); + if (analogPin < 2) { + dataString += ","; + } + } + + // open the file. + File dataFile = SD.open("datalog.txt", FILE_WRITE); + + // if the file is available, write to it: + if (dataFile) { + dataFile.println(dataString); + dataFile.close(); + // print to the serial port too: + Serial.println(dataString); + } else { + // if the file isn't open, pop up an error: + Serial.println("error opening datalog.txt"); + } + delay(100); // run at a reasonable not-too-fast speed +} + + + + + diff --git a/Firmware_V3/lib/SD/examples/DumpFile/DumpFile.ino b/Firmware_V3/lib/SD/examples/DumpFile/DumpFile.ino new file mode 100644 index 0000000..5aff760 --- /dev/null +++ b/Firmware_V3/lib/SD/examples/DumpFile/DumpFile.ino @@ -0,0 +1,84 @@ +/* + SD card file dump + + This example shows how to read a file from the SD card using the + SD library and send it over the serial port. + + The circuit: + * SD card attached to SPI bus as follows: + ** MOSI - pin 11, pin 7 on Teensy with audio board + ** MISO - pin 12 + ** CLK - pin 13, pin 14 on Teensy with audio board + ** CS - pin 4, pin 10 on Teensy with audio board + + created 22 December 2010 + by Limor Fried + modified 9 Apr 2012 + by Tom Igoe + + This example code is in the public domain. + + */ + +#include +#include + +// On the Ethernet Shield, CS is pin 4. Note that even if it's not +// used as the CS pin, the hardware CS pin (10 on most Arduino boards, +// 53 on the Mega) must be left as an output or the SD library +// functions will not work. + +// change this to match your SD shield or module; +// Arduino Ethernet shield: pin 4 +// Adafruit SD shields and modules: pin 10 +// Sparkfun SD shield: pin 8 +// Teensy audio board: pin 10 +// Teensy 3.5 & 3.6 & 4.1 on-board: BUILTIN_SDCARD +// Wiz820+SD board: pin 4 +// Teensy 2.0: pin 0 +// Teensy++ 2.0: pin 20 +const int chipSelect = 4; + +void setup() +{ + //UNCOMMENT THESE TWO LINES FOR TEENSY AUDIO BOARD: + //SPI.setMOSI(7); // Audio shield has MOSI on pin 7 + //SPI.setSCK(14); // Audio shield has SCK on pin 14 + + // Open serial communications and wait for port to open: + Serial.begin(9600); + while (!Serial) { + ; // wait for serial port to connect. + } + + + Serial.print("Initializing SD card..."); + + // see if the card is present and can be initialized: + if (!SD.begin(chipSelect)) { + Serial.println("Card failed, or not present"); + // don't do anything more: + return; + } + Serial.println("card initialized."); + + // open the file. + File dataFile = SD.open("datalog.txt"); + + // if the file is available, write to it: + if (dataFile) { + while (dataFile.available()) { + Serial.write(dataFile.read()); + } + dataFile.close(); + } + // if the file isn't open, pop up an error: + else { + Serial.println("error opening datalog.txt"); + } +} + +void loop() +{ +} + diff --git a/Firmware_V3/lib/SD/examples/Files/Files.ino b/Firmware_V3/lib/SD/examples/Files/Files.ino new file mode 100644 index 0000000..5e3db95 --- /dev/null +++ b/Firmware_V3/lib/SD/examples/Files/Files.ino @@ -0,0 +1,95 @@ +/* + SD card basic file example + + This example shows how to create and destroy an SD card file + The circuit: + * SD card attached to SPI bus as follows: + ** MOSI - pin 11, pin 7 on Teensy with audio board + ** MISO - pin 12 + ** CLK - pin 13, pin 14 on Teensy with audio board + ** CS - pin 4, pin 10 on Teensy with audio board + + created Nov 2010 + by David A. Mellis + modified 9 Apr 2012 + by Tom Igoe + + This example code is in the public domain. + + */ +#include +#include + +File myFile; + +// change this to match your SD shield or module; +// Arduino Ethernet shield: pin 4 +// Adafruit SD shields and modules: pin 10 +// Sparkfun SD shield: pin 8 +// Teensy audio board: pin 10 +// Teensy 3.5 & 3.6 & 4.1 on-board: BUILTIN_SDCARD +// Wiz820+SD board: pin 4 +// Teensy 2.0: pin 0 +// Teensy++ 2.0: pin 20 +const int chipSelect = 4; + +void setup() +{ + //UNCOMMENT THESE TWO LINES FOR TEENSY AUDIO BOARD: + //SPI.setMOSI(7); // Audio shield has MOSI on pin 7 + //SPI.setSCK(14); // Audio shield has SCK on pin 14 + + // Open serial communications and wait for port to open: + Serial.begin(9600); + while (!Serial) { + ; // wait for serial port to connect. + } + + + Serial.print("Initializing SD card..."); + + if (!SD.begin(chipSelect)) { + Serial.println("initialization failed!"); + return; + } + Serial.println("initialization done."); + + if (SD.exists("example.txt")) { + Serial.println("example.txt exists."); + } + else { + Serial.println("example.txt doesn't exist."); + } + + // open a new file and immediately close it: + Serial.println("Creating example.txt..."); + myFile = SD.open("example.txt", FILE_WRITE); + myFile.close(); + + // Check to see if the file exists: + if (SD.exists("example.txt")) { + Serial.println("example.txt exists."); + } + else { + Serial.println("example.txt doesn't exist."); + } + + // delete the file: + Serial.println("Removing example.txt..."); + SD.remove("example.txt"); + + if (SD.exists("example.txt")){ + Serial.println("example.txt exists."); + } + else { + Serial.println("example.txt doesn't exist."); + } +} + +void loop() +{ + // nothing happens after setup finishes. +} + + + diff --git a/Firmware_V3/lib/SD/examples/ReadWrite/ReadWrite.ino b/Firmware_V3/lib/SD/examples/ReadWrite/ReadWrite.ino new file mode 100644 index 0000000..805895f --- /dev/null +++ b/Firmware_V3/lib/SD/examples/ReadWrite/ReadWrite.ino @@ -0,0 +1,95 @@ +/* + SD card read/write + + This example shows how to read and write data to and from an SD card file + The circuit: + * SD card attached to SPI bus as follows: + ** MOSI - pin 11, pin 7 on Teensy with audio board + ** MISO - pin 12 + ** CLK - pin 13, pin 14 on Teensy with audio board + ** CS - pin 4, pin 10 on Teensy with audio board + + created Nov 2010 + by David A. Mellis + modified 9 Apr 2012 + by Tom Igoe + + This example code is in the public domain. + + */ + +#include +#include + +File myFile; + +// change this to match your SD shield or module; +// Arduino Ethernet shield: pin 4 +// Adafruit SD shields and modules: pin 10 +// Sparkfun SD shield: pin 8 +// Teensy audio board: pin 10 +// Teensy 3.5 & 3.6 & 4.1 on-board: BUILTIN_SDCARD +// Wiz820+SD board: pin 4 +// Teensy 2.0: pin 0 +// Teensy++ 2.0: pin 20 +const int chipSelect = 4; + +void setup() +{ + //UNCOMMENT THESE TWO LINES FOR TEENSY AUDIO BOARD: + //SPI.setMOSI(7); // Audio shield has MOSI on pin 7 + //SPI.setSCK(14); // Audio shield has SCK on pin 14 + + // Open serial communications and wait for port to open: + Serial.begin(9600); + while (!Serial) { + ; // wait for serial port to connect. + } + + + Serial.print("Initializing SD card..."); + + if (!SD.begin(chipSelect)) { + Serial.println("initialization failed!"); + return; + } + Serial.println("initialization done."); + + // open the file. + myFile = SD.open("test.txt", FILE_WRITE); + + // if the file opened okay, write to it: + if (myFile) { + Serial.print("Writing to test.txt..."); + myFile.println("testing 1, 2, 3."); + // close the file: + myFile.close(); + Serial.println("done."); + } else { + // if the file didn't open, print an error: + Serial.println("error opening test.txt"); + } + + // re-open the file for reading: + myFile = SD.open("test.txt"); + if (myFile) { + Serial.println("test.txt:"); + + // read from the file until there's nothing else in it: + while (myFile.available()) { + Serial.write(myFile.read()); + } + // close the file: + myFile.close(); + } else { + // if the file didn't open, print an error: + Serial.println("error opening test.txt"); + } +} + +void loop() +{ + // nothing happens after setup +} + + diff --git a/Firmware_V3/lib/SD/examples/listfiles/listfiles.ino b/Firmware_V3/lib/SD/examples/listfiles/listfiles.ino new file mode 100644 index 0000000..5bdb3e9 --- /dev/null +++ b/Firmware_V3/lib/SD/examples/listfiles/listfiles.ino @@ -0,0 +1,89 @@ +/* + SD card basic directory list example + + This example shows how to create and destroy an SD card file + The circuit: + * SD card attached to SPI bus as follows: + ** MOSI - pin 11, pin 7 on Teensy with audio board + ** MISO - pin 12 + ** CLK - pin 13, pin 14 on Teensy with audio board + ** CS - pin 4, pin 10 on Teensy with audio board + + created Nov 2010 + by David A. Mellis + modified 9 Apr 2012 + by Tom Igoe + + This example code is in the public domain. + + */ +#include + +// change this to match your SD shield or module; +// Teensy 2.0: pin 0 +// Teensy++ 2.0: pin 20 +// Wiz820+SD board: pin 4 +// Teensy audio board: pin 10 +// Teensy 3.5 & 3.6 & 4.1 on-board: BUILTIN_SDCARD +const int chipSelect = 10; + +void setup() +{ + //Uncomment these lines for Teensy 3.x Audio Shield (Rev C) + //SPI.setMOSI(7); // Audio shield has MOSI on pin 7 + //SPI.setSCK(14); // Audio shield has SCK on pin 14 + + // Open serial communications and wait for port to open: + Serial.begin(9600); + while (!Serial) { + ; // wait for serial port to connect. + } + + Serial.print("Initializing SD card..."); + + if (!SD.begin(chipSelect)) { + Serial.println("initialization failed!"); + return; + } + Serial.println("initialization done."); + + File root = SD.open("/"); + + printDirectory(root, 0); + + Serial.println("done!"); +} + +void loop() +{ + // nothing happens after setup finishes. +} + +void printDirectory(File dir, int numSpaces) { + while(true) { + File entry = dir.openNextFile(); + if (! entry) { + //Serial.println("** no more files **"); + break; + } + printSpaces(numSpaces); + Serial.print(entry.name()); + if (entry.isDirectory()) { + Serial.println("/"); + printDirectory(entry, numSpaces+2); + } else { + // files have sizes, directories do not + printSpaces(48 - numSpaces - strlen(entry.name())); + Serial.print(" "); + Serial.println(entry.size(), DEC); + } + entry.close(); + } +} + +void printSpaces(int num) { + for (int i=0; i < num; i++) { + Serial.print(" "); + } +} + diff --git a/Firmware_V3/lib/SD/keywords.txt b/Firmware_V3/lib/SD/keywords.txt new file mode 100644 index 0000000..7f3b4ce --- /dev/null +++ b/Firmware_V3/lib/SD/keywords.txt @@ -0,0 +1,31 @@ +####################################### +# Syntax Coloring Map SD +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +SD KEYWORD1 +File KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### +begin KEYWORD2 +exists KEYWORD2 +mkdir KEYWORD2 +remove KEYWORD2 +rmdir KEYWORD2 +open KEYWORD2 +close KEYWORD2 +seek KEYWORD2 +position KEYWORD2 +size KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### +FILE_READ LITERAL1 +FILE_WRITE LITERAL1 +BUILTIN_SDCARD LITERAL1 diff --git a/Firmware_V3/lib/SD/library.properties b/Firmware_V3/lib/SD/library.properties new file mode 100644 index 0000000..cc6fd75 --- /dev/null +++ b/Firmware_V3/lib/SD/library.properties @@ -0,0 +1,9 @@ +name=SD +version=2.0.0 +author=Paul Stoffregen +maintainer=Paul Stoffregen +sentence=Arduino SD compatibility layer for SdFat. +paragraph=To access SD cards, we now use Bill Greiman's SdFat library. This library just provides a thin wrapper so programs written for Arduino's SD library can use SdFat. None of the original Arduino SD library code is present in this compatibility library. +category=Data Storage +url=https://github.com/PaulStoffregen/SD +architectures=* diff --git a/Firmware_V3/lib/SD/src/SD.cpp b/Firmware_V3/lib/SD/src/SD.cpp new file mode 100644 index 0000000..f4c380c --- /dev/null +++ b/Firmware_V3/lib/SD/src/SD.cpp @@ -0,0 +1,26 @@ +/* SD library compatibility wrapper for use of SdFat on Teensy + * Copyright (c) 2020, Paul Stoffregen, paul@pjrc.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice, development funding notice, and this permission + * notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +SDClass SD; diff --git a/Firmware_V3/lib/SD/src/SD.h b/Firmware_V3/lib/SD/src/SD.h new file mode 100644 index 0000000..c1deb92 --- /dev/null +++ b/Firmware_V3/lib/SD/src/SD.h @@ -0,0 +1,232 @@ +/* SD library compatibility wrapper for use of SdFat on Teensy + * Copyright (c) 2020, Paul Stoffregen, paul@pjrc.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice, development funding notice, and this permission + * notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __SD_H__ +#define __SD_H__ + +#include +#include +// Use FILE_READ & FILE_WRITE as defined by FS.h +#if defined(FILE_READ) && !defined(FS_H) +#undef FILE_READ +#endif +#if defined(FILE_WRITE) && !defined(FS_H) +#undef FILE_WRITE +#endif +#include + +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) || defined(__IMXRT1062__) +#define BUILTIN_SDCARD 254 +#endif + +#if defined(__arm__) + // Support everything on 32 bit boards with enough memory + #define SDFAT_FILE FsFile + #define SDFAT_BASE SdFs + #define MAX_FILENAME_LEN 256 +#elif defined(__AVR__) + // Limit to 32GB cards on 8 bit Teensy with only limited memory + #define SDFAT_FILE File32 + #define SDFAT_BASE SdFat32 + #define MAX_FILENAME_LEN 64 +#endif + +class SDFile : public File +{ +private: + // Classes derived from File are never meant to be constructed + // anywhere other than open() in the parent FS class and + // openNextFile() while traversing a directory. + // Only the abstract File class which references these derived + // classes is meant to have a public constructor! + SDFile(const SDFAT_FILE &file) : sdfatfile(file), filename(nullptr) { } + friend class SDClass; +public: + virtual ~SDFile(void) { + if (sdfatfile) sdfatfile.close(); + if (filename) free(filename); + } +#ifdef FILE_WHOAMI + virtual void whoami() { + Serial.printf(" SDFile this=%x, refcount=%u\n", + (int)this, getRefcount()); + } +#endif + virtual size_t write(const void *buf, size_t size) { + return sdfatfile.write(buf, size); + } + virtual int peek() { + return sdfatfile.peek(); + } + virtual int available() { + return sdfatfile.available(); + } + virtual void flush() { + sdfatfile.flush(); + } + virtual size_t read(void *buf, size_t nbyte) { + return sdfatfile.read(buf, nbyte); + } + virtual bool truncate(uint64_t size=0) { + return sdfatfile.truncate(size); + } + virtual bool seek(uint64_t pos, int mode = SeekSet) { + if (mode == SeekSet) return sdfatfile.seekSet(pos); + if (mode == SeekCur) return sdfatfile.seekCur(pos); + if (mode == SeekEnd) return sdfatfile.seekEnd(pos); + return false; + } + virtual uint64_t position() { + return sdfatfile.curPosition(); + } + virtual uint64_t size() { + return sdfatfile.size(); + } + virtual void close() { + if (filename) { + free(filename); + filename = nullptr; + } + sdfatfile.close(); + } + virtual operator bool() { + return sdfatfile.isOpen(); + } + virtual const char * name() { + if (!filename) { + filename = (char *)malloc(MAX_FILENAME_LEN); + if (filename) { + sdfatfile.getName(filename, MAX_FILENAME_LEN); + } else { + static char zeroterm = 0; + filename = &zeroterm; + } + } + return filename; + } + virtual boolean isDirectory(void) { + return sdfatfile.isDirectory(); + } + virtual File openNextFile(uint8_t mode=0) { + SDFAT_FILE file = sdfatfile.openNextFile(); + if (file) return File(new SDFile(file)); + return File(); + } + virtual void rewindDirectory(void) { + sdfatfile.rewindDirectory(); + } + using Print::write; +private: + SDFAT_FILE sdfatfile; + char *filename; +}; + + + +class SDClass : public FS +{ +public: + SDClass() { } + bool begin(uint8_t csPin = 10) { +#ifdef BUILTIN_SDCARD + if (csPin == BUILTIN_SDCARD) { + return sdfs.begin(SdioConfig(FIFO_SDIO)); + //return sdfs.begin(SdioConfig(DMA_SDIO)); + } +#endif + return sdfs.begin(SdSpiConfig(csPin, SHARED_SPI, SD_SCK_MHZ(16))); + //return sdfs.begin(csPin, SD_SCK_MHZ(24)); + } + File open(const char *filepath, uint8_t mode = FILE_READ) { + oflag_t flags = O_READ; + if (mode == FILE_WRITE) flags = O_RDWR | O_CREAT | O_AT_END; + else if (mode == FILE_WRITE_BEGIN) flags = O_RDWR | O_CREAT; + SDFAT_FILE file = sdfs.open(filepath, flags); + if (file) return File(new SDFile(file)); + return File(); + } + bool exists(const char *filepath) { + return sdfs.exists(filepath); + } + bool mkdir(const char *filepath) { + return sdfs.mkdir(filepath); + } + bool rename(const char *oldfilepath, const char *newfilepath) { + return sdfs.rename(oldfilepath, newfilepath); + } + bool remove(const char *filepath) { + return sdfs.remove(filepath); + } + bool rmdir(const char *filepath) { + return sdfs.rmdir(filepath); + } + uint64_t usedSize() { + return (uint64_t)(sdfs.clusterCount() - sdfs.freeClusterCount()) + * (uint64_t)sdfs.bytesPerCluster(); + } + uint64_t totalSize() { + return (uint64_t)sdfs.clusterCount() * (uint64_t)sdfs.bytesPerCluster(); + } +public: // allow access, so users can mix SD & SdFat APIs + SDFAT_BASE sdfs; + operator SDFAT_BASE & () { return sdfs; } +}; + +extern SDClass SD; + +// do not expose these defines in Arduino sketches or other libraries +#undef SDFAT_FILE +#undef SDFAT_BASE +#undef MAX_FILENAME_LEN + + +#define SD_CARD_TYPE_SD1 0 +#define SD_CARD_TYPE_SD2 1 +#define SD_CARD_TYPE_SDHC 3 +class Sd2Card +{ +public: + bool init(uint32_t speed, uint8_t csPin) { + return SD.begin(csPin); + } + uint8_t type() { + return SD.sdfs.card()->type(); + } +}; +class SdVolume +{ +public: + bool init(Sd2Card &card) { + return SD.sdfs.vol() != nullptr; + } + uint8_t fatType() { + return SD.sdfs.vol()->fatType(); + } + uint32_t blocksPerCluster() { + return SD.sdfs.vol()->sectorsPerCluster(); + } + uint32_t clusterCount() { + return SD.sdfs.vol()->clusterCount(); + } +}; + +#endif diff --git a/Firmware_V3/lib/SPI/SPI.cpp b/Firmware_V3/lib/SPI/SPI.cpp new file mode 100644 index 0000000..6258a8d --- /dev/null +++ b/Firmware_V3/lib/SPI/SPI.cpp @@ -0,0 +1,1927 @@ +/* + * Copyright (c) 2010 by Cristian Maglie + * SPI Master library for arduino. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#include "SPI.h" +#include "pins_arduino.h" + +//#define DEBUG_DMA_TRANSFERS + +/**********************************************************/ +/* 8 bit AVR-based boards */ +/**********************************************************/ + +#if defined(__AVR__) + +SPIClass SPI; + +uint8_t SPIClass::interruptMode = 0; +uint8_t SPIClass::interruptMask = 0; +uint8_t SPIClass::interruptSave = 0; +#ifdef SPI_TRANSACTION_MISMATCH_LED +uint8_t SPIClass::inTransactionFlag = 0; +#endif +uint8_t SPIClass::_transferWriteFill = 0; + + +void SPIClass::begin() +{ + // Set SS to high so a connected chip will be "deselected" by default + digitalWrite(SS, HIGH); + + // When the SS pin is set as OUTPUT, it can be used as + // a general purpose output port (it doesn't influence + // SPI operations). + pinMode(SS, OUTPUT); + + // Warning: if the SS pin ever becomes a LOW INPUT then SPI + // automatically switches to Slave, so the data direction of + // the SS pin MUST be kept as OUTPUT. + SPCR |= _BV(MSTR); + SPCR |= _BV(SPE); + + // Set direction register for SCK and MOSI pin. + // MISO pin automatically overrides to INPUT. + // By doing this AFTER enabling SPI, we avoid accidentally + // clocking in a single bit since the lines go directly + // from "input" to SPI control. + // http://code.google.com/p/arduino/issues/detail?id=888 + pinMode(SCK, OUTPUT); + pinMode(MOSI, OUTPUT); +} + +void SPIClass::end() { + SPCR &= ~_BV(SPE); +} + +// mapping of interrupt numbers to bits within SPI_AVR_EIMSK +#if defined(__AVR_ATmega32U4__) + #define SPI_INT0_MASK (1< 1) return; + + stmp = SREG; + noInterrupts(); + switch (interruptNumber) { + #ifdef SPI_INT0_MASK + case 0: mask = SPI_INT0_MASK; break; + #endif + #ifdef SPI_INT1_MASK + case 1: mask = SPI_INT1_MASK; break; + #endif + #ifdef SPI_INT2_MASK + case 2: mask = SPI_INT2_MASK; break; + #endif + #ifdef SPI_INT3_MASK + case 3: mask = SPI_INT3_MASK; break; + #endif + #ifdef SPI_INT4_MASK + case 4: mask = SPI_INT4_MASK; break; + #endif + #ifdef SPI_INT5_MASK + case 5: mask = SPI_INT5_MASK; break; + #endif + #ifdef SPI_INT6_MASK + case 6: mask = SPI_INT6_MASK; break; + #endif + #ifdef SPI_INT7_MASK + case 7: mask = SPI_INT7_MASK; break; + #endif + default: + interruptMode = 2; + SREG = stmp; + return; + } + interruptMode = 1; + interruptMask |= mask; + SREG = stmp; +} + +void SPIClass::transfer(const void * buf, void * retbuf, uint32_t count) { + if (count == 0) return; + + const uint8_t *p = (const uint8_t *)buf; + uint8_t *pret = (uint8_t *)retbuf; + uint8_t in; + + uint8_t out = p ? *p++ : _transferWriteFill; + SPDR = out; + while (--count > 0) { + if (p) { + out = *p++; + } + while (!(SPSR & _BV(SPIF))) ; + in = SPDR; + SPDR = out; + if (pret)*pret++ = in; + } + while (!(SPSR & _BV(SPIF))) ; + in = SPDR; + if (pret)*pret = in; +} + + +/**********************************************************/ +/* 32 bit Teensy 3.x */ +/**********************************************************/ + +#elif defined(__arm__) && defined(TEENSYDUINO) && defined(KINETISK) +#if defined(KINETISK) && defined( SPI_HAS_TRANSFER_ASYNC) + +#ifndef TRANSFER_COUNT_FIXED +inline void DMAChanneltransferCount(DMAChannel * dmac, unsigned int len) { + // note does no validation of length... + DMABaseClass::TCD_t *tcd = dmac->TCD; + if (!(tcd->BITER & DMA_TCD_BITER_ELINK)) { + tcd->BITER = len & 0x7fff; + } else { + tcd->BITER = (tcd->BITER & 0xFE00) | (len & 0x1ff); + } + tcd->CITER = tcd->BITER; +} +#else +inline void DMAChanneltransferCount(DMAChannel * dmac, unsigned int len) { + dmac->transferCount(len); +} +#endif +#endif + + +#if defined(__MK20DX128__) || defined(__MK20DX256__) +#ifdef SPI_HAS_TRANSFER_ASYNC +void _spi_dma_rxISR0(void) {SPI.dma_rxisr();} +#else +void _spi_dma_rxISR0(void) {;} +#endif + +const SPIClass::SPI_Hardware_t SPIClass::spi0_hardware = { + SIM_SCGC6, SIM_SCGC6_SPI0, 4, IRQ_SPI0, + 32767, DMAMUX_SOURCE_SPI0_TX, DMAMUX_SOURCE_SPI0_RX, + _spi_dma_rxISR0, + 12, 8, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), + 11, 7, + PORT_PCR_DSE | PORT_PCR_MUX(2), PORT_PCR_MUX(2), + 13, 14, + PORT_PCR_DSE | PORT_PCR_MUX(2), PORT_PCR_MUX(2), + 10, 2, 9, 6, 20, 23, 21, 22, 15, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), + 0x1, 0x1, 0x2, 0x2, 0x4, 0x4, 0x8, 0x8, 0x10 +}; +SPIClass SPI((uintptr_t)&KINETISK_SPI0, (uintptr_t)&SPIClass::spi0_hardware); + +#elif defined(__MK64FX512__) || defined(__MK66FX1M0__) +#ifdef SPI_HAS_TRANSFER_ASYNC +void _spi_dma_rxISR0(void) {SPI.dma_rxisr();} +void _spi_dma_rxISR1(void) {SPI1.dma_rxisr();} +void _spi_dma_rxISR2(void) {SPI2.dma_rxisr();} +#else +void _spi_dma_rxISR0(void) {;} +void _spi_dma_rxISR1(void) {;} +void _spi_dma_rxISR2(void) {;} +#endif +const SPIClass::SPI_Hardware_t SPIClass::spi0_hardware = { + SIM_SCGC6, SIM_SCGC6_SPI0, 4, IRQ_SPI0, + 32767, DMAMUX_SOURCE_SPI0_TX, DMAMUX_SOURCE_SPI0_RX, + _spi_dma_rxISR0, + 12, 8, 39, 255, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), 0, + 11, 7, 28, 255, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), 0, + 13, 14, 27, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), + 10, 2, 9, 6, 20, 23, 21, 22, 15, 26, 45, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(3), + 0x1, 0x1, 0x2, 0x2, 0x4, 0x4, 0x8, 0x8, 0x10, 0x1, 0x20 +}; +const SPIClass::SPI_Hardware_t SPIClass::spi1_hardware = { + SIM_SCGC6, SIM_SCGC6_SPI1, 1, IRQ_SPI1, + #if defined(__MK66FX1M0__) + 32767, DMAMUX_SOURCE_SPI1_TX, DMAMUX_SOURCE_SPI1_RX, + #else + // T3.5 does not have good DMA support on 1 and 2 + 511, 0, DMAMUX_SOURCE_SPI1, + #endif + _spi_dma_rxISR1, + 1, 5, 61, 59, + PORT_PCR_MUX(2), PORT_PCR_MUX(7), PORT_PCR_MUX(2), PORT_PCR_MUX(7), + 0, 21, 61, 59, + PORT_PCR_MUX(2), PORT_PCR_MUX(7), PORT_PCR_MUX(7), PORT_PCR_MUX(2), + 32, 20, 60, + PORT_PCR_MUX(2), PORT_PCR_MUX(7), PORT_PCR_MUX(2), + 6, 31, 58, 62, 63, 255, 255, 255, 255, 255, 255, + PORT_PCR_MUX(7), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), 0, 0, 0, 0, 0, 0, + 0x1, 0x1, 0x2, 0x1, 0x4, 0, 0, 0, 0, 0, 0 +}; +const SPIClass::SPI_Hardware_t SPIClass::spi2_hardware = { + SIM_SCGC3, SIM_SCGC3_SPI2, 1, IRQ_SPI2, + #if defined(__MK66FX1M0__) + 32767, DMAMUX_SOURCE_SPI2_TX, DMAMUX_SOURCE_SPI2_RX, + #else + // T3.5 does not have good DMA support on 1 and 2 + 511, 0, DMAMUX_SOURCE_SPI2, + #endif + _spi_dma_rxISR2, + 45, 51, 255, 255, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), 0, 0, + 44, 52, 255, 255, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), 0, 0, + 46, 53, 255, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), 0, + 43, 54, 55, 255, 255, 255, 255, 255, 255, 255, 255, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), PORT_PCR_MUX(2), 0, 0, 0, 0, 0, 0, 0, 0, + 0x1, 0x2, 0x1, 0, 0, 0, 0, 0, 0, 0, 0 +}; +SPIClass SPI((uintptr_t)&KINETISK_SPI0, (uintptr_t)&SPIClass::spi0_hardware); +SPIClass SPI1((uintptr_t)&KINETISK_SPI1, (uintptr_t)&SPIClass::spi1_hardware); +SPIClass SPI2((uintptr_t)&KINETISK_SPI2, (uintptr_t)&SPIClass::spi2_hardware); +#endif + + +void SPIClass::begin() +{ + volatile uint32_t *reg; + + hardware().clock_gate_register |= hardware().clock_gate_mask; + port().MCR = SPI_MCR_MDIS | SPI_MCR_HALT | SPI_MCR_PCSIS(0x1F); + port().CTAR0 = SPI_CTAR_FMSZ(7) | SPI_CTAR_PBR(0) | SPI_CTAR_BR(1) | SPI_CTAR_CSSCK(1); + port().CTAR1 = SPI_CTAR_FMSZ(15) | SPI_CTAR_PBR(0) | SPI_CTAR_BR(1) | SPI_CTAR_CSSCK(1); + port().MCR = SPI_MCR_MSTR | SPI_MCR_PCSIS(0x1F); + reg = portConfigRegister(hardware().mosi_pin[mosi_pin_index]); + *reg = hardware().mosi_mux[mosi_pin_index]; + reg = portConfigRegister(hardware().miso_pin[miso_pin_index]); + *reg= hardware().miso_mux[miso_pin_index]; + reg = portConfigRegister(hardware().sck_pin[sck_pin_index]); + *reg = hardware().sck_mux[sck_pin_index]; +} + +void SPIClass::end() +{ + volatile uint32_t *reg; + + reg = portConfigRegister(hardware().mosi_pin[mosi_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware().miso_pin[miso_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware().sck_pin[sck_pin_index]); + *reg = 0; + port().MCR = SPI_MCR_MDIS | SPI_MCR_HALT | SPI_MCR_PCSIS(0x1F); +} + +void SPIClass::usingInterrupt(IRQ_NUMBER_t interruptName) +{ + uint32_t n = (uint32_t)interruptName; + + if (n >= NVIC_NUM_INTERRUPTS) return; + + //Serial.print("usingInterrupt "); + //Serial.println(n); + interruptMasksUsed |= (1 << (n >> 5)); + interruptMask[n >> 5] |= (1 << (n & 0x1F)); + //Serial.printf("interruptMasksUsed = %d\n", interruptMasksUsed); + //Serial.printf("interruptMask[0] = %08X\n", interruptMask[0]); + //Serial.printf("interruptMask[1] = %08X\n", interruptMask[1]); + //Serial.printf("interruptMask[2] = %08X\n", interruptMask[2]); +} + +void SPIClass::notUsingInterrupt(IRQ_NUMBER_t interruptName) +{ + uint32_t n = (uint32_t)interruptName; + if (n >= NVIC_NUM_INTERRUPTS) return; + interruptMask[n >> 5] &= ~(1 << (n & 0x1F)); + if (interruptMask[n >> 5] == 0) { + interruptMasksUsed &= ~(1 << (n >> 5)); + } +} + +const uint16_t SPISettings::ctar_div_table[23] = { + 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, 32, 40, + 56, 64, 96, 128, 192, 256, 384, 512, 640, 768 +}; +const uint32_t SPISettings::ctar_clock_table[23] = { + SPI_CTAR_PBR(0) | SPI_CTAR_BR(0) | SPI_CTAR_DBR | SPI_CTAR_CSSCK(0), + SPI_CTAR_PBR(1) | SPI_CTAR_BR(0) | SPI_CTAR_DBR | SPI_CTAR_CSSCK(0), + SPI_CTAR_PBR(0) | SPI_CTAR_BR(0) | SPI_CTAR_CSSCK(0), + SPI_CTAR_PBR(2) | SPI_CTAR_BR(0) | SPI_CTAR_DBR | SPI_CTAR_CSSCK(0), + SPI_CTAR_PBR(1) | SPI_CTAR_BR(0) | SPI_CTAR_CSSCK(0), + SPI_CTAR_PBR(0) | SPI_CTAR_BR(1) | SPI_CTAR_CSSCK(1), + SPI_CTAR_PBR(2) | SPI_CTAR_BR(0) | SPI_CTAR_CSSCK(0), + SPI_CTAR_PBR(1) | SPI_CTAR_BR(1) | SPI_CTAR_CSSCK(1), + SPI_CTAR_PBR(0) | SPI_CTAR_BR(3) | SPI_CTAR_CSSCK(2), + SPI_CTAR_PBR(2) | SPI_CTAR_BR(1) | SPI_CTAR_CSSCK(0), + SPI_CTAR_PBR(1) | SPI_CTAR_BR(3) | SPI_CTAR_CSSCK(2), + SPI_CTAR_PBR(0) | SPI_CTAR_BR(4) | SPI_CTAR_CSSCK(3), + SPI_CTAR_PBR(2) | SPI_CTAR_BR(3) | SPI_CTAR_CSSCK(2), + SPI_CTAR_PBR(3) | SPI_CTAR_BR(3) | SPI_CTAR_CSSCK(2), + SPI_CTAR_PBR(0) | SPI_CTAR_BR(5) | SPI_CTAR_CSSCK(4), + SPI_CTAR_PBR(1) | SPI_CTAR_BR(5) | SPI_CTAR_CSSCK(4), + SPI_CTAR_PBR(0) | SPI_CTAR_BR(6) | SPI_CTAR_CSSCK(5), + SPI_CTAR_PBR(1) | SPI_CTAR_BR(6) | SPI_CTAR_CSSCK(5), + SPI_CTAR_PBR(0) | SPI_CTAR_BR(7) | SPI_CTAR_CSSCK(6), + SPI_CTAR_PBR(1) | SPI_CTAR_BR(7) | SPI_CTAR_CSSCK(6), + SPI_CTAR_PBR(0) | SPI_CTAR_BR(8) | SPI_CTAR_CSSCK(7), + SPI_CTAR_PBR(2) | SPI_CTAR_BR(7) | SPI_CTAR_CSSCK(6), + SPI_CTAR_PBR(1) | SPI_CTAR_BR(8) | SPI_CTAR_CSSCK(7) +}; + +void SPIClass::updateCTAR(uint32_t ctar) +{ + if (port().CTAR0 != ctar) { + uint32_t mcr = port().MCR; + if (mcr & SPI_MCR_MDIS) { + port().CTAR0 = ctar; + port().CTAR1 = ctar | SPI_CTAR_FMSZ(8); + } else { + port().MCR = SPI_MCR_MDIS | SPI_MCR_HALT | SPI_MCR_PCSIS(0x1F); + port().CTAR0 = ctar; + port().CTAR1 = ctar | SPI_CTAR_FMSZ(8); + port().MCR = mcr; + } + } +} + +void SPIClass::setBitOrder(uint8_t bitOrder) +{ + hardware().clock_gate_register |= hardware().clock_gate_mask; + uint32_t ctar = port().CTAR0; + if (bitOrder == LSBFIRST) { + ctar |= SPI_CTAR_LSBFE; + } else { + ctar &= ~SPI_CTAR_LSBFE; + } + updateCTAR(ctar); +} + +void SPIClass::setDataMode(uint8_t dataMode) +{ + hardware().clock_gate_register |= hardware().clock_gate_mask; + //uint32_t ctar = port().CTAR0; + + // TODO: implement with native code + //SPCR = (SPCR & ~SPI_MODE_MASK) | dataMode; +} + +void SPIClass::setClockDivider_noInline(uint32_t clk) +{ + hardware().clock_gate_register |= hardware().clock_gate_mask; + uint32_t ctar = port().CTAR0; + ctar &= (SPI_CTAR_CPOL | SPI_CTAR_CPHA | SPI_CTAR_LSBFE); + if (ctar & SPI_CTAR_CPHA) { + clk = (clk & 0xFFFF0FFF) | ((clk & 0xF000) >> 4); + } + ctar |= clk; + updateCTAR(ctar); +} + +uint8_t SPIClass::pinIsChipSelect(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().cs_pin); i++) { + if (pin == hardware().cs_pin[i]) return hardware().cs_mask[i]; + } + return 0; +} + +bool SPIClass::pinIsChipSelect(uint8_t pin1, uint8_t pin2) +{ + uint8_t pin1_mask, pin2_mask; + if ((pin1_mask = (uint8_t)pinIsChipSelect(pin1)) == 0) return false; + if ((pin2_mask = (uint8_t)pinIsChipSelect(pin2)) == 0) return false; + //Serial.printf("pinIsChipSelect %d %d %x %x\n\r", pin1, pin2, pin1_mask, pin2_mask); + if ((pin1_mask & pin2_mask) != 0) return false; + return true; +} + +bool SPIClass::pinIsMOSI(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().mosi_pin); i++) { + if (pin == hardware().mosi_pin[i]) return true; + } + return false; +} + +bool SPIClass::pinIsMISO(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().miso_pin); i++) { + if (pin == hardware().miso_pin[i]) return true; + } + return false; +} + +bool SPIClass::pinIsSCK(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().sck_pin); i++) { + if (pin == hardware().sck_pin[i]) return true; + } + return false; +} + +// setCS() is not intended for use from normal Arduino programs/sketches. +uint8_t SPIClass::setCS(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().cs_pin); i++) { + if (pin == hardware().cs_pin[i]) { + volatile uint32_t *reg = portConfigRegister(pin); + *reg = hardware().cs_mux[i]; + return hardware().cs_mask[i]; + } + } + return 0; +} + +void SPIClass::setMOSI(uint8_t pin) +{ + if (hardware_addr == (uintptr_t)&spi0_hardware) { + SPCR.setMOSI_soft(pin); + } + if (pin != hardware().mosi_pin[mosi_pin_index]) { + for (unsigned int i = 0; i < sizeof(hardware().mosi_pin); i++) { + if (pin == hardware().mosi_pin[i]) { + if (hardware().clock_gate_register & hardware().clock_gate_mask) { + volatile uint32_t *reg; + reg = portConfigRegister(hardware().mosi_pin[mosi_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware().mosi_pin[i]); + *reg = hardware().mosi_mux[i]; + } + mosi_pin_index = i; + return; + } + } + } +} + +void SPIClass::setMISO(uint8_t pin) +{ + if (hardware_addr == (uintptr_t)&spi0_hardware) { + SPCR.setMISO_soft(pin); + } + if (pin != hardware().miso_pin[miso_pin_index]) { + for (unsigned int i = 0; i < sizeof(hardware().miso_pin); i++) { + if (pin == hardware().miso_pin[i]) { + if (hardware().clock_gate_register & hardware().clock_gate_mask) { + volatile uint32_t *reg; + reg = portConfigRegister(hardware().miso_pin[miso_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware().miso_pin[i]); + *reg = hardware().miso_mux[i]; + } + miso_pin_index = i; + return; + } + } + } +} + +void SPIClass::setSCK(uint8_t pin) +{ + if (hardware_addr == (uintptr_t)&spi0_hardware) { + SPCR.setSCK_soft(pin); + } + if (pin != hardware().sck_pin[sck_pin_index]) { + for (unsigned int i = 0; i < sizeof(hardware().sck_pin); i++) { + if (pin == hardware().sck_pin[i]) { + if (hardware().clock_gate_register & hardware().clock_gate_mask) { + volatile uint32_t *reg; + reg = portConfigRegister(hardware().sck_pin[sck_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware().sck_pin[i]); + *reg = hardware().sck_mux[i]; + } + sck_pin_index = i; + return; + } + } + } +} + +void SPIClass::transfer(const void * buf, void * retbuf, size_t count) +{ + + if (count == 0) return; + if (!(port().CTAR0 & SPI_CTAR_LSBFE)) { + // We are doing the standard MSB order + const uint8_t *p_write = (const uint8_t *)buf; + uint8_t *p_read = (uint8_t *)retbuf; + size_t count_read = count; + + // Lets clear the reader queue + port().MCR = SPI_MCR_MSTR | SPI_MCR_CLR_RXF | SPI_MCR_PCSIS(0x1F); + + uint32_t sr; + + // Now lets loop while we still have data to output + if (count & 1) { + if (p_write) { + if (count > 1) + port().PUSHR = *p_write++ | SPI_PUSHR_CONT | SPI_PUSHR_CTAS(0); + else + port().PUSHR = *p_write++ | SPI_PUSHR_CTAS(0); + } else { + if (count > 1) + port().PUSHR = _transferWriteFill | SPI_PUSHR_CONT | SPI_PUSHR_CTAS(0); + else + port().PUSHR = _transferWriteFill | SPI_PUSHR_CTAS(0); + } + count--; + } + + uint16_t w = (uint16_t)(_transferWriteFill << 8) | _transferWriteFill; + + while (count > 0) { + // Push out the next byte; + if (p_write) { + w = (*p_write++) << 8; + w |= *p_write++; + } + uint16_t queue_full_status_mask = (hardware().queue_size-1) << 12; + if (count == 2) + port().PUSHR = w | SPI_PUSHR_CTAS(1); + else + port().PUSHR = w | SPI_PUSHR_CONT | SPI_PUSHR_CTAS(1); + count -= 2; // how many bytes to output. + // Make sure queue is not full before pushing next byte out + do { + sr = port().SR; + if (sr & 0xF0) { + uint16_t w = port().POPR; // Read any pending RX bytes in + if (count_read & 1) { + if (p_read) { + *p_read++ = w; // Read any pending RX bytes in + } + count_read--; + } else { + if (p_read) { + *p_read++ = w >> 8; + *p_read++ = (w & 0xff); + } + count_read -= 2; + } + } + } while ((sr & (15 << 12)) > queue_full_status_mask); + + } + + // now lets wait for all of the read bytes to be returned... + while (count_read) { + sr = port().SR; + if (sr & 0xF0) { + uint16_t w = port().POPR; // Read any pending RX bytes in + if (count_read & 1) { + if (p_read) + *p_read++ = w; // Read any pending RX bytes in + count_read--; + } else { + if (p_read) { + *p_read++ = w >> 8; + *p_read++ = (w & 0xff); + } + count_read -= 2; + } + } + } + } else { + // We are doing the less ofen LSB mode + const uint8_t *p_write = (const uint8_t *)buf; + uint8_t *p_read = (uint8_t *)retbuf; + size_t count_read = count; + + // Lets clear the reader queue + port().MCR = SPI_MCR_MSTR | SPI_MCR_CLR_RXF | SPI_MCR_PCSIS(0x1F); + + uint32_t sr; + + // Now lets loop while we still have data to output + if (count & 1) { + if (p_write) { + if (count > 1) + port().PUSHR = *p_write++ | SPI_PUSHR_CONT | SPI_PUSHR_CTAS(0); + else + port().PUSHR = *p_write++ | SPI_PUSHR_CTAS(0); + } else { + if (count > 1) + port().PUSHR = _transferWriteFill | SPI_PUSHR_CONT | SPI_PUSHR_CTAS(0); + else + port().PUSHR = _transferWriteFill | SPI_PUSHR_CTAS(0); + } + count--; + } + + uint16_t w = _transferWriteFill; + + while (count > 0) { + // Push out the next byte; + if (p_write) { + w = *p_write++; + w |= ((*p_write++) << 8); + } + uint16_t queue_full_status_mask = (hardware().queue_size-1) << 12; + if (count == 2) + port().PUSHR = w | SPI_PUSHR_CTAS(1); + else + port().PUSHR = w | SPI_PUSHR_CONT | SPI_PUSHR_CTAS(1); + count -= 2; // how many bytes to output. + // Make sure queue is not full before pushing next byte out + do { + sr = port().SR; + if (sr & 0xF0) { + uint16_t w = port().POPR; // Read any pending RX bytes in + if (count_read & 1) { + if (p_read) { + *p_read++ = w; // Read any pending RX bytes in + } + count_read--; + } else { + if (p_read) { + *p_read++ = (w & 0xff); + *p_read++ = w >> 8; + } + count_read -= 2; + } + } + } while ((sr & (15 << 12)) > queue_full_status_mask); + + } + + // now lets wait for all of the read bytes to be returned... + while (count_read) { + sr = port().SR; + if (sr & 0xF0) { + uint16_t w = port().POPR; // Read any pending RX bytes in + if (count_read & 1) { + if (p_read) + *p_read++ = w; // Read any pending RX bytes in + count_read--; + } else { + if (p_read) { + *p_read++ = (w & 0xff); + *p_read++ = w >> 8; + } + count_read -= 2; + } + } + } + } +} +//============================================================================= +// ASYNCH Support +//============================================================================= +//========================================================================= +// Try Transfer using DMA. +//========================================================================= +#ifdef SPI_HAS_TRANSFER_ASYNC +static uint8_t bit_bucket; +#define dontInterruptAtCompletion(dmac) (dmac)->TCD->CSR &= ~DMA_TCD_CSR_INTMAJOR + +//========================================================================= +// Init the DMA channels +//========================================================================= +bool SPIClass::initDMAChannels() { + // Allocate our channels. + _dmaTX = new DMAChannel(); + if (_dmaTX == nullptr) { + return false; + } + + _dmaRX = new DMAChannel(); + if (_dmaRX == nullptr) { + delete _dmaTX; // release it + _dmaTX = nullptr; + return false; + } + + // Let's setup the RX chain + _dmaRX->disable(); + _dmaRX->source((volatile uint8_t&)port().POPR); + _dmaRX->disableOnCompletion(); + _dmaRX->triggerAtHardwareEvent(hardware().rx_dma_channel); + _dmaRX->attachInterrupt(hardware().dma_rxisr); + _dmaRX->interruptAtCompletion(); + + // We may be using settings chain here so lets set it up. + // Now lets setup TX chain. Note if trigger TX is not set + // we need to have the RX do it for us. + _dmaTX->disable(); + _dmaTX->destination((volatile uint8_t&)port().PUSHR); + _dmaTX->disableOnCompletion(); + + if (hardware().tx_dma_channel) { + _dmaTX->triggerAtHardwareEvent(hardware().tx_dma_channel); + } else { +// Serial.printf("SPI InitDMA tx triger by RX: %x\n", (uint32_t)_dmaRX); + _dmaTX->triggerAtTransfersOf(*_dmaRX); + } + + + _dma_state = DMAState::idle; // Should be first thing set! + return true; +} + +//========================================================================= +// Main Async Transfer function +//========================================================================= + +bool SPIClass::transfer(const void *buf, void *retbuf, size_t count, EventResponderRef event_responder) { + uint8_t dma_first_byte; + if (_dma_state == DMAState::notAllocated) { + if (!initDMAChannels()) + return false; + } + + if (_dma_state == DMAState::active) + return false; // already active + + event_responder.clearEvent(); // Make sure it is not set yet + if (count < 2) { + // Use non-async version to simplify cases... + transfer(buf, retbuf, count); + event_responder.triggerEvent(); + return true; + } + + // Now handle the cases where the count > then how many we can output in one DMA request + if (count > hardware().max_dma_count) { + _dma_count_remaining = count - hardware().max_dma_count; + count = hardware().max_dma_count; + } else { + _dma_count_remaining = 0; + } + + // Now See if caller passed in a source buffer. + _dmaTX->TCD->ATTR_DST = 0; // Make sure set for 8 bit mode + uint8_t *write_data = (uint8_t*) buf; + if (buf) { + dma_first_byte = *write_data; + _dmaTX->sourceBuffer((uint8_t*)write_data+1, count-1); + _dmaTX->TCD->SLAST = 0; // Finish with it pointing to next location + } else { + dma_first_byte = _transferWriteFill; + _dmaTX->source((uint8_t&)_transferWriteFill); // maybe have setable value + DMAChanneltransferCount(_dmaTX, count-1); + } + if (retbuf) { + // On T3.5 must handle SPI1/2 differently as only one DMA channel + _dmaRX->TCD->ATTR_SRC = 0; //Make sure set for 8 bit mode... + _dmaRX->destinationBuffer((uint8_t*)retbuf, count); + _dmaRX->TCD->DLASTSGA = 0; // At end point after our bufffer + } else { + // Write only mode + _dmaRX->TCD->ATTR_SRC = 0; //Make sure set for 8 bit mode... + _dmaRX->destination((uint8_t&)bit_bucket); + DMAChanneltransferCount(_dmaRX, count); + } + + _dma_event_responder = &event_responder; + // Now try to start it? + // Setup DMA main object + yield(); + port().MCR = SPI_MCR_MSTR | SPI_MCR_CLR_RXF | SPI_MCR_CLR_TXF | SPI_MCR_PCSIS(0x1F); + + port().SR = 0xFF0F0000; + + // Lets try to output the first byte to make sure that we are in 8 bit mode... + port().PUSHR = dma_first_byte | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT; + + if (hardware().tx_dma_channel) { + port().RSER = SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS | SPI_RSER_TFFF_RE | SPI_RSER_TFFF_DIRS; + _dmaRX->enable(); + // Get the initial settings. + _dmaTX->enable(); + } else { + //T3.5 SP1 and SPI2 - TX is not triggered by SPI but by RX... + port().RSER = SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS ; + _dmaTX->triggerAtTransfersOf(*_dmaRX); + _dmaTX->enable(); + _dmaRX->enable(); + } + + _dma_state = DMAState::active; + return true; +} + + +//------------------------------------------------------------------------- +// DMA RX ISR +//------------------------------------------------------------------------- +void SPIClass::dma_rxisr(void) { + _dmaRX->clearInterrupt(); + _dmaTX->clearComplete(); + _dmaRX->clearComplete(); + + uint8_t should_reenable_tx = true; // should we re-enable TX maybe not if count will be 0... + if (_dma_count_remaining) { + // What do I need to do to start it back up again... + // We will use the BITR/CITR from RX as TX may have prefed some stuff + if (_dma_count_remaining > hardware().max_dma_count) { + _dma_count_remaining -= hardware().max_dma_count; + } else { + DMAChanneltransferCount(_dmaTX, _dma_count_remaining-1); + DMAChanneltransferCount(_dmaRX, _dma_count_remaining); + if (_dma_count_remaining == 1) should_reenable_tx = false; + + _dma_count_remaining = 0; + } + // In some cases we need to again start the TX manually to get it to work... + if (_dmaTX->TCD->SADDR == &_transferWriteFill) { + if (port().CTAR0 & SPI_CTAR_FMSZ(8)) { + port().PUSHR = (_transferWriteFill | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT); + } else { + port().PUSHR = (_transferWriteFill | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT); + } + } else { + if (port().CTAR0 & SPI_CTAR_FMSZ(8)) { + // 16 bit mode + uint16_t w = *((uint16_t*)_dmaTX->TCD->SADDR); + _dmaTX->TCD->SADDR = (volatile uint8_t*)(_dmaTX->TCD->SADDR) + 2; + port().PUSHR = (w | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT); + } else { + uint8_t w = *((uint8_t*)_dmaTX->TCD->SADDR); + _dmaTX->TCD->SADDR = (volatile uint8_t*)(_dmaTX->TCD->SADDR) + 1; + port().PUSHR = (w | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT); + } + } + _dmaRX->enable(); + if (should_reenable_tx) + _dmaTX->enable(); + } else { + + port().RSER = 0; + //port().MCR = SPI_MCR_MSTR | SPI_MCR_CLR_RXF | SPI_MCR_PCSIS(0x1F); // clear out the queue + port().SR = 0xFF0F0000; + port().CTAR0 &= ~(SPI_CTAR_FMSZ(8)); // Hack restore back to 8 bits + + _dma_state = DMAState::completed; // set back to 1 in case our call wants to start up dma again + _dma_event_responder->triggerEvent(); + + } +} +#endif // SPI_HAS_TRANSFER_ASYNC + + +/**********************************************************/ +/* 32 bit Teensy-LC */ +/**********************************************************/ + +#elif defined(__arm__) && defined(TEENSYDUINO) && defined(KINETISL) + +#ifdef SPI_HAS_TRANSFER_ASYNC +void _spi_dma_rxISR0(void) {SPI.dma_isr();} +void _spi_dma_rxISR1(void) {SPI1.dma_isr();} +#else +void _spi_dma_rxISR0(void) {;} +void _spi_dma_rxISR1(void) {;} +#endif + +const SPIClass::SPI_Hardware_t SPIClass::spi0_hardware = { + SIM_SCGC4, SIM_SCGC4_SPI0, + 0, // BR index 0 + DMAMUX_SOURCE_SPI0_TX, DMAMUX_SOURCE_SPI0_RX, _spi_dma_rxISR0, + 12, 8, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), + 11, 7, + PORT_PCR_DSE | PORT_PCR_MUX(2), PORT_PCR_MUX(2), + 13, 14, + PORT_PCR_DSE | PORT_PCR_MUX(2), PORT_PCR_MUX(2), + 10, 2, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), + 0x1, 0x1 +}; +SPIClass SPI((uintptr_t)&KINETISL_SPI0, (uintptr_t)&SPIClass::spi0_hardware); + +const SPIClass::SPI_Hardware_t SPIClass::spi1_hardware = { + SIM_SCGC4, SIM_SCGC4_SPI1, + 1, // BR index 1 in SPI Settings + DMAMUX_SOURCE_SPI1_TX, DMAMUX_SOURCE_SPI1_RX, _spi_dma_rxISR1, + 1, 5, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), + 0, 21, + PORT_PCR_MUX(2), PORT_PCR_MUX(2), + 20, 255, + PORT_PCR_MUX(2), 0, + 6, 255, + PORT_PCR_MUX(2), 0, + 0x1, 0 +}; +SPIClass SPI1((uintptr_t)&KINETISL_SPI1, (uintptr_t)&SPIClass::spi1_hardware); + + +void SPIClass::begin() +{ + volatile uint32_t *reg; + + hardware().clock_gate_register |= hardware().clock_gate_mask; + port().C1 = SPI_C1_SPE | SPI_C1_MSTR; + port().C2 = 0; + uint8_t tmp __attribute__((unused)) = port().S; + reg = portConfigRegister(hardware().mosi_pin[mosi_pin_index]); + *reg = hardware().mosi_mux[mosi_pin_index]; + reg = portConfigRegister(hardware().miso_pin[miso_pin_index]); + *reg = hardware().miso_mux[miso_pin_index]; + reg = portConfigRegister(hardware().sck_pin[sck_pin_index]); + *reg = hardware().sck_mux[sck_pin_index]; +} + +void SPIClass::end() { + volatile uint32_t *reg; + + reg = portConfigRegister(hardware().mosi_pin[mosi_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware().miso_pin[miso_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware().sck_pin[sck_pin_index]); + *reg = 0; + port().C1 = 0; +} + +const uint16_t SPISettings::br_div_table[30] = { + 2, 4, 6, 8, 10, 12, 14, 16, 20, 24, + 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, + 160, 192, 224, 256, 320, 384, 448, 512, 640, 768, +}; + +const uint8_t SPISettings::br_clock_table[30] = { + SPI_BR_SPPR(0) | SPI_BR_SPR(0), + SPI_BR_SPPR(1) | SPI_BR_SPR(0), + SPI_BR_SPPR(2) | SPI_BR_SPR(0), + SPI_BR_SPPR(3) | SPI_BR_SPR(0), + SPI_BR_SPPR(4) | SPI_BR_SPR(0), + SPI_BR_SPPR(5) | SPI_BR_SPR(0), + SPI_BR_SPPR(6) | SPI_BR_SPR(0), + SPI_BR_SPPR(7) | SPI_BR_SPR(0), + SPI_BR_SPPR(4) | SPI_BR_SPR(1), + SPI_BR_SPPR(5) | SPI_BR_SPR(1), + SPI_BR_SPPR(6) | SPI_BR_SPR(1), + SPI_BR_SPPR(7) | SPI_BR_SPR(1), + SPI_BR_SPPR(4) | SPI_BR_SPR(2), + SPI_BR_SPPR(5) | SPI_BR_SPR(2), + SPI_BR_SPPR(6) | SPI_BR_SPR(2), + SPI_BR_SPPR(7) | SPI_BR_SPR(2), + SPI_BR_SPPR(4) | SPI_BR_SPR(3), + SPI_BR_SPPR(5) | SPI_BR_SPR(3), + SPI_BR_SPPR(6) | SPI_BR_SPR(3), + SPI_BR_SPPR(7) | SPI_BR_SPR(3), + SPI_BR_SPPR(4) | SPI_BR_SPR(4), + SPI_BR_SPPR(5) | SPI_BR_SPR(4), + SPI_BR_SPPR(6) | SPI_BR_SPR(4), + SPI_BR_SPPR(7) | SPI_BR_SPR(4), + SPI_BR_SPPR(4) | SPI_BR_SPR(5), + SPI_BR_SPPR(5) | SPI_BR_SPR(5), + SPI_BR_SPPR(6) | SPI_BR_SPR(5), + SPI_BR_SPPR(7) | SPI_BR_SPR(5), + SPI_BR_SPPR(4) | SPI_BR_SPR(6), + SPI_BR_SPPR(5) | SPI_BR_SPR(6) +}; + +void SPIClass::setMOSI(uint8_t pin) +{ + if (pin != hardware().mosi_pin[mosi_pin_index]) { + for (unsigned int i = 0; i < sizeof(hardware().mosi_pin); i++) { + if (pin == hardware().mosi_pin[i] ) { + if (hardware().clock_gate_register & hardware().clock_gate_mask) { + volatile uint32_t *reg; + reg = portConfigRegister(hardware().mosi_pin[mosi_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware().mosi_pin[i]); + *reg = hardware().mosi_mux[i]; + } + mosi_pin_index = i; + return; + } + } + } +} + +void SPIClass::setMISO(uint8_t pin) +{ + if (pin != hardware().miso_pin[miso_pin_index]) { + for (unsigned int i = 0; i < sizeof(hardware().miso_pin); i++) { + if (pin == hardware().miso_pin[i] ) { + if (hardware().clock_gate_register & hardware().clock_gate_mask) { + volatile uint32_t *reg; + reg = portConfigRegister(hardware().miso_pin[miso_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware().miso_pin[i]); + *reg = hardware().miso_mux[i]; + } + miso_pin_index = i; + return; + } + } + } +} + +void SPIClass::setSCK(uint8_t pin) +{ + if (pin != hardware().sck_pin[sck_pin_index]) { + for (unsigned int i = 0; i < sizeof(hardware().sck_pin); i++) { + if (pin == hardware().sck_pin[i] ) { + if (hardware().clock_gate_register & hardware().clock_gate_mask) { + volatile uint32_t *reg; + reg = portConfigRegister(hardware().sck_pin[sck_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware().sck_pin[i]); + *reg = hardware().sck_mux[i]; + } + sck_pin_index = i; + return; + } + } + } +} + +bool SPIClass::pinIsChipSelect(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().cs_pin); i++) { + if (pin == hardware().cs_pin[i]) return hardware().cs_mask[i]; + } + return 0; +} + +bool SPIClass::pinIsMOSI(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().mosi_pin); i++) { + if (pin == hardware().mosi_pin[i]) return true; + } + return false; +} + +bool SPIClass::pinIsMISO(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().miso_pin); i++) { + if (pin == hardware().miso_pin[i]) return true; + } + return false; +} + +bool SPIClass::pinIsSCK(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().sck_pin); i++) { + if (pin == hardware().sck_pin[i]) return true; + } + return false; +} + +// setCS() is not intended for use from normal Arduino programs/sketches. +uint8_t SPIClass::setCS(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().cs_pin); i++) { + if (pin == hardware().cs_pin[i]) { + volatile uint32_t *reg = portConfigRegister(pin); + *reg = hardware().cs_mux[i]; + return hardware().cs_mask[i]; + } + } + return 0; +} + +void SPIClass::transfer(const void * buf, void * retbuf, size_t count) { + if (count == 0) return; + const uint8_t *p = (const uint8_t *)buf; + uint8_t *pret = (uint8_t *)retbuf; + uint8_t in; + + while (!(port().S & SPI_S_SPTEF)) ; // wait + uint8_t out = p ? *p++ : _transferWriteFill; + port().DL = out; + while (--count > 0) { + if (p) { + out = *p++; + } + while (!(port().S & SPI_S_SPTEF)) ; // wait + __disable_irq(); + port().DL = out; + while (!(port().S & SPI_S_SPRF)) ; // wait + in = port().DL; + __enable_irq(); + if (pret)*pret++ = in; + } + while (!(port().S & SPI_S_SPRF)) ; // wait + in = port().DL; + if (pret)*pret = in; +} +//============================================================================= +// ASYNCH Support +//============================================================================= +//========================================================================= +// Try Transfer using DMA. +//========================================================================= +#ifdef SPI_HAS_TRANSFER_ASYNC +static uint8_t _dma_dummy_rx; + +void SPIClass::dma_isr(void) { + // Serial.println("_spi_dma_rxISR"); + _dmaRX->clearInterrupt(); + port().C2 = 0; + uint8_t tmp __attribute__((unused)) = port().S; + _dmaTX->clearComplete(); + _dmaRX->clearComplete(); + + _dma_state = DMAState::completed; // set back to 1 in case our call wants to start up dma again + _dma_event_responder->triggerEvent(); +} + +bool SPIClass::initDMAChannels() { + //Serial.println("First dma call"); Serial.flush(); + _dmaTX = new DMAChannel(); + if (_dmaTX == nullptr) { + return false; + } + + _dmaTX->disable(); + _dmaTX->destination((volatile uint8_t&)port().DL); + _dmaTX->disableOnCompletion(); + _dmaTX->triggerAtHardwareEvent(hardware().tx_dma_channel); + + + _dmaRX = new DMAChannel(); + if (_dmaRX == NULL) { + delete _dmaTX; + _dmaRX = nullptr; + return false; + } + _dmaRX->disable(); + _dmaRX->source((volatile uint8_t&)port().DL); + _dmaRX->disableOnCompletion(); + _dmaRX->triggerAtHardwareEvent(hardware().rx_dma_channel); + _dmaRX->attachInterrupt(hardware().dma_isr); + _dmaRX->interruptAtCompletion(); + + _dma_state = DMAState::idle; // Should be first thing set! + //Serial.println("end First dma call"); + return true; +} + +//========================================================================= +// Main Async Transfer function +//========================================================================= +bool SPIClass::transfer(const void *buf, void *retbuf, size_t count, EventResponderRef event_responder) { + if (_dma_state == DMAState::notAllocated) { + if (!initDMAChannels()) { + return false; + } + } + + if (_dma_state == DMAState::active) + return false; // already active + + event_responder.clearEvent(); // Make sure it is not set yet + + if (count < 2) { + // Use non-async version to simplify cases... + transfer(buf, retbuf, count); + event_responder.triggerEvent(); + return true; + } + //_dmaTX->destination((volatile uint8_t&)port().DL); + //_dmaRX->source((volatile uint8_t&)port().DL); + _dmaTX->CFG->DCR = (_dmaTX->CFG->DCR & ~DMA_DCR_DSIZE(3)) | DMA_DCR_DSIZE(1); + _dmaRX->CFG->DCR = (_dmaRX->CFG->DCR & ~DMA_DCR_SSIZE(3)) | DMA_DCR_SSIZE(1); // 8 bit transfer + + // Now see if the user passed in TX buffer to send. + uint8_t first_char; + if (buf) { + uint8_t *data_out = (uint8_t*)buf; + first_char = *data_out++; + _dmaTX->sourceBuffer(data_out, count-1); + } else { + first_char = (_transferWriteFill & 0xff); + _dmaTX->source((uint8_t&)_transferWriteFill); // maybe have setable value + _dmaTX->transferCount(count-1); + } + + if (retbuf) { + _dmaRX->destinationBuffer((uint8_t*)retbuf, count); + } else { + _dmaRX->destination(_dma_dummy_rx); // NULL ? + _dmaRX->transferCount(count); + } + + _dma_event_responder = &event_responder; + + //Serial.println("Before DMA C2"); + // Try pushing the first character + while (!(port().S & SPI_S_SPTEF)); + port().DL = first_char; + + port().C2 |= SPI_C2_TXDMAE | SPI_C2_RXDMAE; + + // Now make sure SPI is enabled. + port().C1 |= SPI_C1_SPE; + + _dmaRX->enable(); + _dmaTX->enable(); + _dma_state = DMAState::active; + return true; +} +#endif //SPI_HAS_TRANSFER_ASYNC + + + + +/**********************************************************/ +/* 32 bit Teensy 4.x */ +/**********************************************************/ + +#elif defined(__arm__) && defined(TEENSYDUINO) && (defined(__IMXRT1052__) || defined(__IMXRT1062__)) + +//#include "debug/printf.h" + +void SPIClass::begin() +{ + + // CBCMR[LPSPI_CLK_SEL] - PLL2 = 528 MHz + // CBCMR[LPSPI_PODF] - div4 = 132 MHz + + + hardware().clock_gate_register &= ~hardware().clock_gate_mask; + + CCM_CBCMR = (CCM_CBCMR & ~(CCM_CBCMR_LPSPI_PODF_MASK | CCM_CBCMR_LPSPI_CLK_SEL_MASK)) | + CCM_CBCMR_LPSPI_PODF(2) | CCM_CBCMR_LPSPI_CLK_SEL(1); // pg 714 +// CCM_CBCMR_LPSPI_PODF(6) | CCM_CBCMR_LPSPI_CLK_SEL(2); // pg 714 + + uint32_t fastio = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_SPEED(2); + //uint32_t fastio = IOMUXC_PAD_DSE(6) | IOMUXC_PAD_SPEED(1); + //uint32_t fastio = IOMUXC_PAD_DSE(3) | IOMUXC_PAD_SPEED(3); + //Serial.printf("SPI MISO: %d MOSI: %d, SCK: %d\n", hardware().miso_pin[miso_pin_index], hardware().mosi_pin[mosi_pin_index], hardware().sck_pin[sck_pin_index]); + *(portControlRegister(hardware().miso_pin[miso_pin_index])) = fastio; + *(portControlRegister(hardware().mosi_pin[mosi_pin_index])) = fastio; + *(portControlRegister(hardware().sck_pin[sck_pin_index])) = fastio; + + //printf("CBCMR = %08lX\n", CCM_CBCMR); + hardware().clock_gate_register |= hardware().clock_gate_mask; + *(portConfigRegister(hardware().miso_pin[miso_pin_index])) = hardware().miso_mux[miso_pin_index]; + *(portConfigRegister(hardware().mosi_pin [mosi_pin_index])) = hardware().mosi_mux[mosi_pin_index]; + *(portConfigRegister(hardware().sck_pin [sck_pin_index])) = hardware().sck_mux[sck_pin_index]; + + // Set the Mux pins + //Serial.println("SPI: Set Input select registers"); + hardware().sck_select_input_register = hardware().sck_select_val[sck_pin_index]; + hardware().miso_select_input_register = hardware().miso_select_val[miso_pin_index]; + hardware().mosi_select_input_register = hardware().mosi_select_val[mosi_pin_index]; + + //digitalWriteFast(10, HIGH); + //pinMode(10, OUTPUT); + //digitalWriteFast(10, HIGH); + port().CR = LPSPI_CR_RST; + + // Lets initialize the Transmit FIFO watermark to FIFO size - 1... + // BUGBUG:: I assume queue of 16 for now... + port().FCR = LPSPI_FCR_TXWATER(15); + + // We should initialize the SPI to be in a known default state. + beginTransaction(SPISettings()); + endTransaction(); +} + +void SPIClass::setClockDivider_noInline(uint32_t clk) { + // Again depreciated, but... + hardware().clock_gate_register |= hardware().clock_gate_mask; + if (clk != _clock) { + static const uint32_t clk_sel[4] = {664615384, // PLL3 PFD1 + 720000000, // PLL3 PFD0 + 528000000, // PLL2 + 396000000}; // PLL2 PFD2 + + // First save away the new settings.. + _clock = clk; + + uint32_t cbcmr = CCM_CBCMR; + uint32_t clkhz = clk_sel[(cbcmr >> 4) & 0x03] / (((cbcmr >> 26 ) & 0x07 ) + 1); // LPSPI peripheral clock + + uint32_t d, div; + d = _clock ? clkhz/_clock : clkhz; + + if (d && clkhz/d > _clock) d++; + if (d > 257) d= 257; // max div + if (d > 2) { + div = d-2; + } else { + div =0; + } + + _ccr = LPSPI_CCR_SCKDIV(div) | LPSPI_CCR_DBT(div/2) | LPSPI_CCR_PCSSCK(div/2); + + } + //Serial.printf("SPI.setClockDivider_noInline CCR:%x TCR:%x\n", _ccr, port().TCR); + port().CR = 0; + port().CFGR1 = LPSPI_CFGR1_MASTER | LPSPI_CFGR1_SAMPLE; + port().CCR = _ccr; + port().CR = LPSPI_CR_MEN; +} + + +uint8_t SPIClass::pinIsChipSelect(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().cs_pin); i++) { + if (pin == hardware().cs_pin[i]) return hardware().cs_mask[i]; + } + return 0; +} + +bool SPIClass::pinIsChipSelect(uint8_t pin1, uint8_t pin2) +{ + uint8_t pin1_mask, pin2_mask; + if ((pin1_mask = (uint8_t)pinIsChipSelect(pin1)) == 0) return false; + if ((pin2_mask = (uint8_t)pinIsChipSelect(pin2)) == 0) return false; + //Serial.printf("pinIsChipSelect %d %d %x %x\n\r", pin1, pin2, pin1_mask, pin2_mask); + if ((pin1_mask & pin2_mask) != 0) return false; + return true; +} + + +bool SPIClass::pinIsMOSI(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().mosi_pin); i++) { + if (pin == hardware().mosi_pin[i]) return true; + } + return false; +} + +bool SPIClass::pinIsMISO(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().miso_pin); i++) { + if (pin == hardware().miso_pin[i]) return true; + } + return false; +} + +bool SPIClass::pinIsSCK(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().sck_pin); i++) { + if (pin == hardware().sck_pin[i]) return true; + } + return false; +} + +// setCS() is not intended for use from normal Arduino programs/sketches. +uint8_t SPIClass::setCS(uint8_t pin) +{ + for (unsigned int i = 0; i < sizeof(hardware().cs_pin); i++) { + if (pin == hardware().cs_pin[i]) { + *(portConfigRegister(pin)) = hardware().cs_mux[i]; + if (hardware().pcs_select_input_register[i]) + *hardware().pcs_select_input_register[i] = hardware().pcs_select_val[i]; + return hardware().cs_mask[i]; + } + } + return 0; +} + +void SPIClass::setMOSI(uint8_t pin) +{ + if (pin != hardware().mosi_pin[mosi_pin_index]) { + for (unsigned int i = 0; i < sizeof(hardware().mosi_pin); i++) { + if (pin == hardware().mosi_pin[i] ) { + if (hardware().clock_gate_register & hardware().clock_gate_mask) { + // BUGBUG:: Unclear what to do with previous pin as there is no unused setting like t3.x + uint32_t fastio = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_SPEED(2); + *(portControlRegister(hardware().mosi_pin[i])) = fastio; + *(portConfigRegister(hardware().mosi_pin [i])) = hardware().mosi_mux[i]; + hardware().mosi_select_input_register = hardware().mosi_select_val[i]; + } + mosi_pin_index = i; + return; + } + } + } +} + +void SPIClass::setMISO(uint8_t pin) +{ + if (pin != hardware().miso_pin[miso_pin_index]) { + for (unsigned int i = 0; i < sizeof(hardware().miso_pin); i++) { + if (pin == hardware().miso_pin[i] ) { + if (hardware().clock_gate_register & hardware().clock_gate_mask) { + // BUGBUG:: Unclear what to do with previous pin as there is no unused setting like t3.x + uint32_t fastio = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_SPEED(2); + *(portControlRegister(hardware().miso_pin[i])) = fastio; + *(portConfigRegister(hardware().miso_pin[i])) = hardware().miso_mux[i]; + hardware().miso_select_input_register = hardware().miso_select_val[i]; + } + miso_pin_index = i; + return; + } + } + } +} + +void SPIClass::setSCK(uint8_t pin) +{ + if (pin != hardware().sck_pin[sck_pin_index]) { + for (unsigned int i = 0; i < sizeof(hardware().sck_pin); i++) { + if (pin == hardware().sck_pin[i] ) { + if (hardware().clock_gate_register & hardware().clock_gate_mask) { + // BUGBUG:: Unclear what to do with previous pin as there is no unused setting like t3.x + uint32_t fastio = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_SPEED(2); + *(portControlRegister(hardware().sck_pin[i])) = fastio; + *(portConfigRegister(hardware().sck_pin [i])) = hardware().sck_mux[i]; + hardware().sck_select_input_register = hardware().sck_select_val[i]; + } + sck_pin_index = i; + return; + } + } + } +} + + +void SPIClass::setBitOrder(uint8_t bitOrder) +{ + hardware().clock_gate_register |= hardware().clock_gate_mask; + + if (bitOrder == LSBFIRST) { + port().TCR |= LPSPI_TCR_LSBF; + } else { + port().TCR &= ~LPSPI_TCR_LSBF; + } +} + +void SPIClass::setDataMode(uint8_t dataMode) +{ + hardware().clock_gate_register |= hardware().clock_gate_mask; + //SPCR = (SPCR & ~SPI_MODE_MASK) | dataMode; + + // Handle Data Mode + uint32_t tcr = port().TCR & ~(LPSPI_TCR_CPOL | LPSPI_TCR_CPHA); + + if (dataMode & 0x08) tcr |= LPSPI_TCR_CPOL; + + // Note: On T3.2 when we set CPHA it also updated the timing. It moved the + // PCS to SCK Delay Prescaler into the After SCK Delay Prescaler + if (dataMode & 0x04) tcr |= LPSPI_TCR_CPHA; + + // Save back out + port().TCR = tcr; + +} + +void _spi_dma_rxISR0(void) {SPI.dma_rxisr();} + +// NOTE pin definitions are in the order MISO, MOSI, SCK, CS +// With each group, having pin number[n], setting[n], INPUT_SELECT_MUX settings[n], SELECT INPUT register +#if defined(ARDUINO_TEENSY41) +const SPIClass::SPI_Hardware_t SPIClass::spiclass_lpspi4_hardware = { + CCM_CCGR1, CCM_CCGR1_LPSPI4(CCM_CCGR_ON), + DMAMUX_SOURCE_LPSPI4_TX, DMAMUX_SOURCE_LPSPI4_RX, _spi_dma_rxISR0, + 12, 255, // MISO + 3 | 0x10, 0, + 0, 0, + IOMUXC_LPSPI4_SDI_SELECT_INPUT, + 11, 255, // MOSI + 3 | 0x10, 0, + 0, 0, + IOMUXC_LPSPI4_SDO_SELECT_INPUT, + 13, 255, // SCK + 3 | 0x10, 0, + 0, 0, + IOMUXC_LPSPI4_SCK_SELECT_INPUT, + 10, 37, 36, // CS + 3 | 0x10, 2 | 0x10, 2 | 0x10, + 1, 2, 3, + 0, 0, 0, + &IOMUXC_LPSPI4_PCS0_SELECT_INPUT, 0, 0 +}; +#else +const SPIClass::SPI_Hardware_t SPIClass::spiclass_lpspi4_hardware = { + CCM_CCGR1, CCM_CCGR1_LPSPI4(CCM_CCGR_ON), + DMAMUX_SOURCE_LPSPI4_TX, DMAMUX_SOURCE_LPSPI4_RX, _spi_dma_rxISR0, + 12, + 3 | 0x10, + 0, + IOMUXC_LPSPI4_SDI_SELECT_INPUT, + 11, + 3 | 0x10, + 0, + IOMUXC_LPSPI4_SDO_SELECT_INPUT, + 13, + 3 | 0x10, + 0, + IOMUXC_LPSPI4_SCK_SELECT_INPUT, + 10, + 3 | 0x10, + 1, + 0, + &IOMUXC_LPSPI4_PCS0_SELECT_INPUT +}; +#endif + +SPIClass SPI((uintptr_t)&IMXRT_LPSPI4_S, (uintptr_t)&SPIClass::spiclass_lpspi4_hardware); + +#if defined(__IMXRT1062__) +// T4 has two other possible SPI objects... +void _spi_dma_rxISR1(void) {SPI1.dma_rxisr();} + +#if defined(ARDUINO_TEENSY41) +const SPIClass::SPI_Hardware_t SPIClass::spiclass_lpspi3_hardware = { + CCM_CCGR1, CCM_CCGR1_LPSPI3(CCM_CCGR_ON), + DMAMUX_SOURCE_LPSPI3_TX, DMAMUX_SOURCE_LPSPI3_RX, _spi_dma_rxISR1, + 1, 39, + 7 | 0x10, 2 | 0x10, + 0, 1, + IOMUXC_LPSPI3_SDI_SELECT_INPUT, + 26, 255, + 2 | 0x10, 0, + 1, 0, + IOMUXC_LPSPI3_SDO_SELECT_INPUT, + 27, 255, + 2 | 0x10, 0, + 1, 0, + IOMUXC_LPSPI3_SCK_SELECT_INPUT, + 0, 38, 255, + 7 | 0x10, 2 | 0x10, 0, + 1, 1, 0, + 0, 1, 0, + &IOMUXC_LPSPI3_PCS0_SELECT_INPUT, &IOMUXC_LPSPI3_PCS0_SELECT_INPUT, 0 +}; +#else +const SPIClass::SPI_Hardware_t SPIClass::spiclass_lpspi3_hardware = { + CCM_CCGR1, CCM_CCGR1_LPSPI3(CCM_CCGR_ON), + DMAMUX_SOURCE_LPSPI3_TX, DMAMUX_SOURCE_LPSPI3_RX, _spi_dma_rxISR1, + 1, + 7 | 0x10, + 0, + IOMUXC_LPSPI3_SDI_SELECT_INPUT, + 26, + 2 | 0x10, + 1, + IOMUXC_LPSPI3_SDO_SELECT_INPUT, + 27, + 2 | 0x10, + 1, + IOMUXC_LPSPI3_SCK_SELECT_INPUT, + 0, + 7 | 0x10, + 1, + 0, + &IOMUXC_LPSPI3_PCS0_SELECT_INPUT +}; +#endif +SPIClass SPI1((uintptr_t)&IMXRT_LPSPI3_S, (uintptr_t)&SPIClass::spiclass_lpspi3_hardware); + +void _spi_dma_rxISR2(void) {SPI2.dma_rxisr();} + +#if defined(ARDUINO_TEENSY41) +const SPIClass::SPI_Hardware_t SPIClass::spiclass_lpspi1_hardware = { + CCM_CCGR1, CCM_CCGR1_LPSPI1(CCM_CCGR_ON), + DMAMUX_SOURCE_LPSPI1_TX, DMAMUX_SOURCE_LPSPI1_RX, _spi_dma_rxISR1, + 42, 54, + 4 | 0x10, 3 | 0x10, + 1, 0, + IOMUXC_LPSPI1_SDI_SELECT_INPUT, + 43, 50, + 4 | 0x10, 3 | 0x10, + 1, 0, + IOMUXC_LPSPI1_SDO_SELECT_INPUT, + 45, 49, + 4 | 0x10, 3 | 0x10, + 1, 0, + IOMUXC_LPSPI1_SCK_SELECT_INPUT, + 44, 255, 255, + 4 | 0x10, 0, 0, + 1, 0, 0, + 0, 0, 0, + &IOMUXC_LPSPI1_PCS0_SELECT_INPUT, 0, 0 +}; +#else +const SPIClass::SPI_Hardware_t SPIClass::spiclass_lpspi1_hardware = { + CCM_CCGR1, CCM_CCGR1_LPSPI1(CCM_CCGR_ON), + DMAMUX_SOURCE_LPSPI1_TX, DMAMUX_SOURCE_LPSPI1_RX, _spi_dma_rxISR1, + 34, + 4 | 0x10, + 1, + IOMUXC_LPSPI1_SDI_SELECT_INPUT, + 35, + 4 | 0x10, + 1, + IOMUXC_LPSPI1_SDO_SELECT_INPUT, + 37, + 4 | 0x10, + 1, + IOMUXC_LPSPI1_SCK_SELECT_INPUT, + 36, + 4 | 0x10, + 1, + 0, + &IOMUXC_LPSPI1_PCS0_SELECT_INPUT +}; +#endif +SPIClass SPI2((uintptr_t)&IMXRT_LPSPI1_S, (uintptr_t)&SPIClass::spiclass_lpspi1_hardware); +#endif + +//SPIClass SPI(&IMXRT_LPSPI4_S, &spiclass_lpspi4_hardware); + +void SPIClass::usingInterrupt(IRQ_NUMBER_t interruptName) +{ + uint32_t n = (uint32_t)interruptName; + + if (n >= NVIC_NUM_INTERRUPTS) return; + + //Serial.print("usingInterrupt "); + //Serial.println(n); + interruptMasksUsed |= (1 << (n >> 5)); + interruptMask[n >> 5] |= (1 << (n & 0x1F)); + //Serial.printf("interruptMasksUsed = %d\n", interruptMasksUsed); + //Serial.printf("interruptMask[0] = %08X\n", interruptMask[0]); + //Serial.printf("interruptMask[1] = %08X\n", interruptMask[1]); + //Serial.printf("interruptMask[2] = %08X\n", interruptMask[2]); +} + +void SPIClass::notUsingInterrupt(IRQ_NUMBER_t interruptName) +{ + uint32_t n = (uint32_t)interruptName; + if (n >= NVIC_NUM_INTERRUPTS) return; + interruptMask[n >> 5] &= ~(1 << (n & 0x1F)); + if (interruptMask[n >> 5] == 0) { + interruptMasksUsed &= ~(1 << (n >> 5)); + } +} + +void SPIClass::transfer(const void * buf, void * retbuf, size_t count) +{ + + if (count == 0) return; + uint8_t *p_write = (uint8_t*)buf; + uint8_t *p_read = (uint8_t*)retbuf; + size_t count_read = count; + + // Pass 1 keep it simple and don't try packing 8 bits into 16 yet.. + // Lets clear the reader queue + port().CR = LPSPI_CR_RRF | LPSPI_CR_MEN; // clear the queue and make sure still enabled. + + while (count > 0) { + // Push out the next byte; + port().TDR = p_write? *p_write++ : _transferWriteFill; + count--; // how many bytes left to output. + // Make sure queue is not full before pushing next byte out + do { + if ((port().RSR & LPSPI_RSR_RXEMPTY) == 0) { + uint8_t b = port().RDR; // Read any pending RX bytes in + if (p_read) *p_read++ = b; + count_read--; + } + } while ((port().SR & LPSPI_SR_TDF) == 0) ; + + } + + // now lets wait for all of the read bytes to be returned... + while (count_read) { + if ((port().RSR & LPSPI_RSR_RXEMPTY) == 0) { + uint8_t b = port().RDR; // Read any pending RX bytes in + if (p_read) *p_read++ = b; + count_read--; + } + } +} + + +void SPIClass::end() { + // only do something if we have begun + if (hardware().clock_gate_register & hardware().clock_gate_mask) { + port().CR = 0; // turn off the enable + pinMode(hardware().miso_pin[miso_pin_index], INPUT_DISABLE); + pinMode(hardware().mosi_pin[mosi_pin_index], INPUT_DISABLE); + pinMode(hardware().sck_pin[sck_pin_index], INPUT_DISABLE); + } +} + +//============================================================================= +// ASYNCH Support +//============================================================================= +//========================================================================= +// Try Transfer using DMA. +//========================================================================= +#ifdef SPI_HAS_TRANSFER_ASYNC +static uint8_t bit_bucket; +#define dontInterruptAtCompletion(dmac) (dmac)->TCD->CSR &= ~DMA_TCD_CSR_INTMAJOR + +//========================================================================= +// Init the DMA channels +//========================================================================= +bool SPIClass::initDMAChannels() { + // Allocate our channels. + _dmaTX = new DMAChannel(); + if (_dmaTX == nullptr) { + return false; + } + + _dmaRX = new DMAChannel(); + if (_dmaRX == nullptr) { + delete _dmaTX; // release it + _dmaTX = nullptr; + return false; + } + + // Let's setup the RX chain + _dmaRX->disable(); + _dmaRX->source((volatile uint8_t&)port().RDR); + _dmaRX->disableOnCompletion(); + _dmaRX->triggerAtHardwareEvent(hardware().rx_dma_channel); + _dmaRX->attachInterrupt(hardware().dma_rxisr); + _dmaRX->interruptAtCompletion(); + + // We may be using settings chain here so lets set it up. + // Now lets setup TX chain. Note if trigger TX is not set + // we need to have the RX do it for us. + _dmaTX->disable(); + _dmaTX->destination((volatile uint8_t&)port().TDR); + _dmaTX->disableOnCompletion(); + + if (hardware().tx_dma_channel) { + _dmaTX->triggerAtHardwareEvent(hardware().tx_dma_channel); + } else { +// Serial.printf("SPI InitDMA tx triger by RX: %x\n", (uint32_t)_dmaRX); + _dmaTX->triggerAtTransfersOf(*_dmaRX); + } + + + _dma_state = DMAState::idle; // Should be first thing set! + return true; +} + +//========================================================================= +// Main Async Transfer function +//========================================================================= +#ifndef TRANSFER_COUNT_FIXED +inline void DMAChanneltransferCount(DMAChannel * dmac, unsigned int len) { + // note does no validation of length... + DMABaseClass::TCD_t *tcd = dmac->TCD; + if (!(tcd->BITER & DMA_TCD_BITER_ELINK)) { + tcd->BITER = len & 0x7fff; + } else { + tcd->BITER = (tcd->BITER & 0xFE00) | (len & 0x1ff); + } + tcd->CITER = tcd->BITER; +} +#else +inline void DMAChanneltransferCount(DMAChannel * dmac, unsigned int len) { + dmac->transferCount(len); +} +#endif +#ifdef DEBUG_DMA_TRANSFERS +void dumpDMA_TCD(DMABaseClass *dmabc) +{ + Serial4.printf("%x %x:", (uint32_t)dmabc, (uint32_t)dmabc->TCD); + + Serial4.printf("SA:%x SO:%d AT:%x NB:%x SL:%d DA:%x DO: %d CI:%x DL:%x CS:%x BI:%x\n", (uint32_t)dmabc->TCD->SADDR, + dmabc->TCD->SOFF, dmabc->TCD->ATTR, dmabc->TCD->NBYTES, dmabc->TCD->SLAST, (uint32_t)dmabc->TCD->DADDR, + dmabc->TCD->DOFF, dmabc->TCD->CITER, dmabc->TCD->DLASTSGA, dmabc->TCD->CSR, dmabc->TCD->BITER); +} +#endif + +bool SPIClass::transfer(const void *buf, void *retbuf, size_t count, EventResponderRef event_responder) { + if (_dma_state == DMAState::notAllocated) { + if (!initDMAChannels()) + return false; + } + + if (_dma_state == DMAState::active) + return false; // already active + + event_responder.clearEvent(); // Make sure it is not set yet + if (count < 2) { + // Use non-async version to simplify cases... + transfer(buf, retbuf, count); + event_responder.triggerEvent(); + return true; + } + + // Now handle the cases where the count > then how many we can output in one DMA request + if (count > MAX_DMA_COUNT) { + _dma_count_remaining = count - MAX_DMA_COUNT; + count = MAX_DMA_COUNT; + } else { + _dma_count_remaining = 0; + } + + // Now See if caller passed in a source buffer. + _dmaTX->TCD->ATTR_DST = 0; // Make sure set for 8 bit mode + uint8_t *write_data = (uint8_t*) buf; + if (buf) { + _dmaTX->sourceBuffer((uint8_t*)write_data, count); + _dmaTX->TCD->SLAST = 0; // Finish with it pointing to next location + if ((uint32_t)write_data >= 0x20200000u) arm_dcache_flush(write_data, count); + } else { + _dmaTX->source((uint8_t&)_transferWriteFill); // maybe have setable value + DMAChanneltransferCount(_dmaTX, count); + } + if (retbuf) { + // On T3.5 must handle SPI1/2 differently as only one DMA channel + _dmaRX->TCD->ATTR_SRC = 0; //Make sure set for 8 bit mode... + _dmaRX->destinationBuffer((uint8_t*)retbuf, count); + _dmaRX->TCD->DLASTSGA = 0; // At end point after our bufffer + if ((uint32_t)retbuf >= 0x20200000u) arm_dcache_delete(retbuf, count); + } else { + // Write only mode + _dmaRX->TCD->ATTR_SRC = 0; //Make sure set for 8 bit mode... + _dmaRX->destination((uint8_t&)bit_bucket); + DMAChanneltransferCount(_dmaRX, count); + } + + _dma_event_responder = &event_responder; + // Now try to start it? + // Setup DMA main object + yield(); + +#ifdef DEBUG_DMA_TRANSFERS + // Lets dump TX, RX + dumpDMA_TCD(_dmaTX); + dumpDMA_TCD(_dmaRX); +#endif + + // Make sure port is in 8 bit mode and clear watermark + port().TCR = (port().TCR & ~(LPSPI_TCR_FRAMESZ(31))) | LPSPI_TCR_FRAMESZ(7); + port().FCR = 0; + + // Lets try to output the first byte to make sure that we are in 8 bit mode... + port().DER = LPSPI_DER_TDDE | LPSPI_DER_RDDE; //enable DMA on both TX and RX + port().SR = 0x3f00; // clear out all of the other status... + + _dmaRX->enable(); + _dmaTX->enable(); + + _dma_state = DMAState::active; + return true; +} + + +//------------------------------------------------------------------------- +// DMA RX ISR +//------------------------------------------------------------------------- +void SPIClass::dma_rxisr(void) { + _dmaRX->clearInterrupt(); + _dmaTX->clearComplete(); + _dmaRX->clearComplete(); + + if (_dma_count_remaining) { + // What do I need to do to start it back up again... + // We will use the BITR/CITR from RX as TX may have prefed some stuff + if (_dma_count_remaining > MAX_DMA_COUNT) { + _dma_count_remaining -= MAX_DMA_COUNT; + } else { + DMAChanneltransferCount(_dmaTX, _dma_count_remaining); + DMAChanneltransferCount(_dmaRX, _dma_count_remaining); + + _dma_count_remaining = 0; + } + _dmaRX->enable(); + _dmaTX->enable(); + } else { + + port().FCR = LPSPI_FCR_TXWATER(15); // _spi_fcr_save; // restore the FSR status... + port().DER = 0; // DMA no longer doing TX (or RX) + + port().CR = LPSPI_CR_MEN | LPSPI_CR_RRF | LPSPI_CR_RTF; // actually clear both... + port().SR = 0x3f00; // clear out all of the other status... + + _dma_state = DMAState::completed; // set back to 1 in case our call wants to start up dma again + _dma_event_responder->triggerEvent(); + + } +} +#endif // SPI_HAS_TRANSFER_ASYNC + + + +#endif diff --git a/Firmware_V3/lib/SPI/SPI.h b/Firmware_V3/lib/SPI/SPI.h new file mode 100644 index 0000000..99aa937 --- /dev/null +++ b/Firmware_V3/lib/SPI/SPI.h @@ -0,0 +1,1406 @@ +/* + * Copyright (c) 2010 by Cristian Maglie + * Copyright (c) 2014 by Paul Stoffregen (Transaction API) + * Copyright (c) 2014 by Matthijs Kooijman (SPISettings AVR) + * SPI Master library for arduino. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#ifndef _SPI_H_INCLUDED +#define _SPI_H_INCLUDED + +#include + +#if defined(__arm__) && defined(TEENSYDUINO) +#if defined(__has_include) && __has_include() +// SPI_HAS_TRANSFER_ASYNC - Defined to say that the SPI supports an ASYNC version +// of the SPI_HAS_TRANSFER_BUF +#define SPI_HAS_TRANSFER_ASYNC 1 +#include +#include +#endif +#endif + +// SPI_HAS_TRANSACTION means SPI has beginTransaction(), endTransaction(), +// usingInterrupt(), and SPISetting(clock, bitOrder, dataMode) +#define SPI_HAS_TRANSACTION 1 + +// Uncomment this line to add detection of mismatched begin/end transactions. +// A mismatch occurs if other libraries fail to use SPI.endTransaction() for +// each SPI.beginTransaction(). Connect a LED to this pin. The LED will turn +// on if any mismatch is ever detected. +//#define SPI_TRANSACTION_MISMATCH_LED 5 + +// SPI_HAS_TRANSFER_BUF - is defined to signify that this library supports +// a version of transfer which allows you to pass in both TX and RX buffer +// pointers, either of which could be NULL +#define SPI_HAS_TRANSFER_BUF 1 + + +#ifndef LSBFIRST +#define LSBFIRST 0 +#endif +#ifndef MSBFIRST +#define MSBFIRST 1 +#endif + +#define SPI_MODE0 0x00 +#define SPI_MODE1 0x04 +#define SPI_MODE2 0x08 +#define SPI_MODE3 0x0C + +#define SPI_CLOCK_DIV4 0x00 +#define SPI_CLOCK_DIV16 0x01 +#define SPI_CLOCK_DIV64 0x02 +#define SPI_CLOCK_DIV128 0x03 +#define SPI_CLOCK_DIV2 0x04 +#define SPI_CLOCK_DIV8 0x05 +#define SPI_CLOCK_DIV32 0x06 + +#define SPI_MODE_MASK 0x0C // CPOL = bit 3, CPHA = bit 2 on SPCR +#define SPI_CLOCK_MASK 0x03 // SPR1 = bit 1, SPR0 = bit 0 on SPCR +#define SPI_2XCLOCK_MASK 0x01 // SPI2X = bit 0 on SPSR + + +/**********************************************************/ +/* 8 bit AVR-based boards */ +/**********************************************************/ + +#if defined(__AVR__) +#define SPI_ATOMIC_VERSION 1 +// define SPI_AVR_EIMSK for AVR boards with external interrupt pins +#if defined(EIMSK) + #define SPI_AVR_EIMSK EIMSK +#elif defined(GICR) + #define SPI_AVR_EIMSK GICR +#elif defined(GIMSK) + #define SPI_AVR_EIMSK GIMSK +#endif + +class SPISettings { +public: + SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) { + if (__builtin_constant_p(clock)) { + init_AlwaysInline(clock, bitOrder, dataMode); + } else { + init_MightInline(clock, bitOrder, dataMode); + } + } + SPISettings() { + init_AlwaysInline(4000000, MSBFIRST, SPI_MODE0); + } +private: + void init_MightInline(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) { + init_AlwaysInline(clock, bitOrder, dataMode); + } + void init_AlwaysInline(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) + __attribute__((__always_inline__)) { + // Clock settings are defined as follows. Note that this shows SPI2X + // inverted, so the bits form increasing numbers. Also note that + // fosc/64 appears twice + // SPR1 SPR0 ~SPI2X Freq + // 0 0 0 fosc/2 + // 0 0 1 fosc/4 + // 0 1 0 fosc/8 + // 0 1 1 fosc/16 + // 1 0 0 fosc/32 + // 1 0 1 fosc/64 + // 1 1 0 fosc/64 + // 1 1 1 fosc/128 + + // We find the fastest clock that is less than or equal to the + // given clock rate. The clock divider that results in clock_setting + // is 2 ^^ (clock_div + 1). If nothing is slow enough, we'll use the + // slowest (128 == 2 ^^ 7, so clock_div = 6). + uint8_t clockDiv; + + // When the clock is known at compiletime, use this if-then-else + // cascade, which the compiler knows how to completely optimize + // away. When clock is not known, use a loop instead, which generates + // shorter code. + if (__builtin_constant_p(clock)) { + if (clock >= F_CPU / 2) { + clockDiv = 0; + } else if (clock >= F_CPU / 4) { + clockDiv = 1; + } else if (clock >= F_CPU / 8) { + clockDiv = 2; + } else if (clock >= F_CPU / 16) { + clockDiv = 3; + } else if (clock >= F_CPU / 32) { + clockDiv = 4; + } else if (clock >= F_CPU / 64) { + clockDiv = 5; + } else { + clockDiv = 6; + } + } else { + uint32_t clockSetting = F_CPU / 2; + clockDiv = 0; + while (clockDiv < 6 && clock < clockSetting) { + clockSetting /= 2; + clockDiv++; + } + } + + // Compensate for the duplicate fosc/64 + if (clockDiv == 6) + clockDiv = 7; + + // Invert the SPI2X bit + clockDiv ^= 0x1; + + // Pack into the SPISettings class + spcr = _BV(SPE) | _BV(MSTR) | ((bitOrder == LSBFIRST) ? _BV(DORD) : 0) | + (dataMode & SPI_MODE_MASK) | ((clockDiv >> 1) & SPI_CLOCK_MASK); + spsr = clockDiv & SPI_2XCLOCK_MASK; + } + uint8_t spcr; + uint8_t spsr; + friend class SPIClass; +}; + + + +class SPIClass { // AVR +public: + // Initialize the SPI library + static void begin(); + + // If SPI is used from within an interrupt, this function registers + // that interrupt with the SPI library, so beginTransaction() can + // prevent conflicts. The input interruptNumber is the number used + // with attachInterrupt. If SPI is used from a different interrupt + // (eg, a timer), interruptNumber should be 255. + static void usingInterrupt(uint8_t interruptNumber); + + // Before using SPI.transfer() or asserting chip select pins, + // this function is used to gain exclusive access to the SPI bus + // and configure the correct settings. + inline static void beginTransaction(SPISettings settings) { + if (interruptMode > 0) { + #ifdef SPI_AVR_EIMSK + if (interruptMode == 1) { + interruptSave = SPI_AVR_EIMSK; + SPI_AVR_EIMSK &= ~interruptMask; + } else + #endif + { + uint8_t tmp = SREG; + cli(); + interruptSave = tmp; + } + } + #ifdef SPI_TRANSACTION_MISMATCH_LED + if (inTransactionFlag) { + pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT); + digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH); + } + inTransactionFlag = 1; + #endif + SPCR = settings.spcr; + SPSR = settings.spsr; + } + + // Write to the SPI bus (MOSI pin) and also receive (MISO pin) + inline static uint8_t transfer(uint8_t data) { + SPDR = data; + asm volatile("nop"); + while (!(SPSR & _BV(SPIF))) ; // wait + return SPDR; + } + inline static uint16_t transfer16(uint16_t data) { + union { uint16_t val; struct { uint8_t lsb; uint8_t msb; }; } in, out; + in.val = data; + if ((SPCR & _BV(DORD))) { + SPDR = in.lsb; + asm volatile("nop"); + while (!(SPSR & _BV(SPIF))) ; + out.lsb = SPDR; + SPDR = in.msb; + asm volatile("nop"); + while (!(SPSR & _BV(SPIF))) ; + out.msb = SPDR; + } else { + SPDR = in.msb; + asm volatile("nop"); + while (!(SPSR & _BV(SPIF))) ; + out.msb = SPDR; + SPDR = in.lsb; + asm volatile("nop"); + while (!(SPSR & _BV(SPIF))) ; + out.lsb = SPDR; + } + return out.val; + } + inline static void transfer(void *buf, size_t count) { + if (count == 0) return; + uint8_t *p = (uint8_t *)buf; + SPDR = *p; + while (--count > 0) { + uint8_t out = *(p + 1); + while (!(SPSR & _BV(SPIF))) ; + uint8_t in = SPDR; + SPDR = out; + *p++ = in; + } + while (!(SPSR & _BV(SPIF))) ; + *p = SPDR; + } + static void setTransferWriteFill(uint8_t ch ) {_transferWriteFill = ch;} + static void transfer(const void * buf, void * retbuf, uint32_t count); + + // After performing a group of transfers and releasing the chip select + // signal, this function allows others to access the SPI bus + inline static void endTransaction(void) { + #ifdef SPI_TRANSACTION_MISMATCH_LED + if (!inTransactionFlag) { + pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT); + digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH); + } + inTransactionFlag = 0; + #endif + if (interruptMode > 0) { + #ifdef SPI_AVR_EIMSK + if (interruptMode == 1) { + SPI_AVR_EIMSK = interruptSave; + } else + #endif + { + SREG = interruptSave; + } + } + } + + // Disable the SPI bus + static void end(); + + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + inline static void setBitOrder(uint8_t bitOrder) { + if (bitOrder == LSBFIRST) SPCR |= _BV(DORD); + else SPCR &= ~(_BV(DORD)); + } + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + inline static void setDataMode(uint8_t dataMode) { + SPCR = (SPCR & ~SPI_MODE_MASK) | dataMode; + } + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + inline static void setClockDivider(uint8_t clockDiv) { + SPCR = (SPCR & ~SPI_CLOCK_MASK) | (clockDiv & SPI_CLOCK_MASK); + SPSR = (SPSR & ~SPI_2XCLOCK_MASK) | ((clockDiv >> 2) & SPI_2XCLOCK_MASK); + } + // These undocumented functions should not be used. SPI.transfer() + // polls the hardware flag which is automatically cleared as the + // AVR responds to SPI's interrupt + inline static void attachInterrupt() { SPCR |= _BV(SPIE); } + inline static void detachInterrupt() { SPCR &= ~_BV(SPIE); } + +private: + static uint8_t interruptMode; // 0=none, 1=mask, 2=global + static uint8_t interruptMask; // which interrupts to mask + static uint8_t interruptSave; // temp storage, to restore state + #ifdef SPI_TRANSACTION_MISMATCH_LED + static uint8_t inTransactionFlag; + #endif + static uint8_t _transferWriteFill; +}; + + + +/**********************************************************/ +/* 32 bit Teensy 3.x */ +/**********************************************************/ + +#elif defined(__arm__) && defined(TEENSYDUINO) && defined(KINETISK) + +#define SPI_HAS_NOTUSINGINTERRUPT 1 +#define SPI_ATOMIC_VERSION 1 + +class SPISettings { +public: + SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) { + if (__builtin_constant_p(clock)) { + init_AlwaysInline(clock, bitOrder, dataMode); + } else { + init_MightInline(clock, bitOrder, dataMode); + } + } + SPISettings() { + init_AlwaysInline(4000000, MSBFIRST, SPI_MODE0); + } +private: + void init_MightInline(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) { + init_AlwaysInline(clock, bitOrder, dataMode); + } + void init_AlwaysInline(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) + __attribute__((__always_inline__)) { + uint32_t t, c = SPI_CTAR_FMSZ(7); + if (bitOrder == LSBFIRST) c |= SPI_CTAR_LSBFE; + if (__builtin_constant_p(clock)) { + if (clock >= F_BUS / 2) { + t = SPI_CTAR_PBR(0) | SPI_CTAR_BR(0) | SPI_CTAR_DBR + | SPI_CTAR_CSSCK(0); + } else if (clock >= F_BUS / 3) { + t = SPI_CTAR_PBR(1) | SPI_CTAR_BR(0) | SPI_CTAR_DBR + | SPI_CTAR_CSSCK(0); + } else if (clock >= F_BUS / 4) { + t = SPI_CTAR_PBR(0) | SPI_CTAR_BR(0) | SPI_CTAR_CSSCK(0); + } else if (clock >= F_BUS / 5) { + t = SPI_CTAR_PBR(2) | SPI_CTAR_BR(0) | SPI_CTAR_DBR + | SPI_CTAR_CSSCK(0); + } else if (clock >= F_BUS / 6) { + t = SPI_CTAR_PBR(1) | SPI_CTAR_BR(0) | SPI_CTAR_CSSCK(0); + } else if (clock >= F_BUS / 8) { + t = SPI_CTAR_PBR(0) | SPI_CTAR_BR(1) | SPI_CTAR_CSSCK(1); + } else if (clock >= F_BUS / 10) { + t = SPI_CTAR_PBR(2) | SPI_CTAR_BR(0) | SPI_CTAR_CSSCK(0); + } else if (clock >= F_BUS / 12) { + t = SPI_CTAR_PBR(1) | SPI_CTAR_BR(1) | SPI_CTAR_CSSCK(1); + } else if (clock >= F_BUS / 16) { + t = SPI_CTAR_PBR(0) | SPI_CTAR_BR(3) | SPI_CTAR_CSSCK(2); + } else if (clock >= F_BUS / 20) { + t = SPI_CTAR_PBR(2) | SPI_CTAR_BR(1) | SPI_CTAR_CSSCK(0); + } else if (clock >= F_BUS / 24) { + t = SPI_CTAR_PBR(1) | SPI_CTAR_BR(3) | SPI_CTAR_CSSCK(2); + } else if (clock >= F_BUS / 32) { + t = SPI_CTAR_PBR(0) | SPI_CTAR_BR(4) | SPI_CTAR_CSSCK(3); + } else if (clock >= F_BUS / 40) { + t = SPI_CTAR_PBR(2) | SPI_CTAR_BR(3) | SPI_CTAR_CSSCK(2); + } else if (clock >= F_BUS / 56) { + t = SPI_CTAR_PBR(3) | SPI_CTAR_BR(3) | SPI_CTAR_CSSCK(2); + } else if (clock >= F_BUS / 64) { + t = SPI_CTAR_PBR(0) | SPI_CTAR_BR(5) | SPI_CTAR_CSSCK(4); + } else if (clock >= F_BUS / 96) { + t = SPI_CTAR_PBR(1) | SPI_CTAR_BR(5) | SPI_CTAR_CSSCK(4); + } else if (clock >= F_BUS / 128) { + t = SPI_CTAR_PBR(0) | SPI_CTAR_BR(6) | SPI_CTAR_CSSCK(5); + } else if (clock >= F_BUS / 192) { + t = SPI_CTAR_PBR(1) | SPI_CTAR_BR(6) | SPI_CTAR_CSSCK(5); + } else if (clock >= F_BUS / 256) { + t = SPI_CTAR_PBR(0) | SPI_CTAR_BR(7) | SPI_CTAR_CSSCK(6); + } else if (clock >= F_BUS / 384) { + t = SPI_CTAR_PBR(1) | SPI_CTAR_BR(7) | SPI_CTAR_CSSCK(6); + } else if (clock >= F_BUS / 512) { + t = SPI_CTAR_PBR(0) | SPI_CTAR_BR(8) | SPI_CTAR_CSSCK(7); + } else if (clock >= F_BUS / 640) { + t = SPI_CTAR_PBR(2) | SPI_CTAR_BR(7) | SPI_CTAR_CSSCK(6); + } else { /* F_BUS / 768 */ + t = SPI_CTAR_PBR(1) | SPI_CTAR_BR(8) | SPI_CTAR_CSSCK(7); + } + } else { + for (uint32_t i=0; i<23; i++) { + t = ctar_clock_table[i]; + if (clock >= F_BUS / ctar_div_table[i]) break; + } + } + if (dataMode & 0x08) { + c |= SPI_CTAR_CPOL; + } + if (dataMode & 0x04) { + c |= SPI_CTAR_CPHA; + t = (t & 0xFFFF0FFF) | ((t & 0xF000) >> 4); + } + ctar = c | t; + } + static const uint16_t ctar_div_table[23]; + static const uint32_t ctar_clock_table[23]; + uint32_t ctar; + friend class SPIClass; +}; + + + +class SPIClass { // Teensy 3.x +public: +#if defined(__MK20DX128__) || defined(__MK20DX256__) + static const uint8_t CNT_MISO_PINS = 2; + static const uint8_t CNT_MOSI_PINS = 2; + static const uint8_t CNT_SCK_PINS = 2; + static const uint8_t CNT_CS_PINS = 9; +#elif defined(__MK64FX512__) || defined(__MK66FX1M0__) + static const uint8_t CNT_MISO_PINS = 4; + static const uint8_t CNT_MOSI_PINS = 4; + static const uint8_t CNT_SCK_PINS = 3; + static const uint8_t CNT_CS_PINS = 11; +#endif + typedef struct { + volatile uint32_t &clock_gate_register; + uint32_t clock_gate_mask; + uint8_t queue_size; + uint8_t spi_irq; + uint32_t max_dma_count; + uint8_t tx_dma_channel; + uint8_t rx_dma_channel; + void (*dma_rxisr)(); + uint8_t miso_pin[CNT_MISO_PINS]; + uint32_t miso_mux[CNT_MISO_PINS]; + uint8_t mosi_pin[CNT_MOSI_PINS]; + uint32_t mosi_mux[CNT_MOSI_PINS]; + uint8_t sck_pin[CNT_SCK_PINS]; + uint32_t sck_mux[CNT_SCK_PINS]; + uint8_t cs_pin[CNT_CS_PINS]; + uint32_t cs_mux[CNT_CS_PINS]; + uint8_t cs_mask[CNT_CS_PINS]; + } SPI_Hardware_t; + static const SPI_Hardware_t spi0_hardware; + static const SPI_Hardware_t spi1_hardware; + static const SPI_Hardware_t spi2_hardware; + + enum DMAState { notAllocated, idle, active, completed}; +public: + constexpr SPIClass(uintptr_t myport, uintptr_t myhardware) + : port_addr(myport), hardware_addr(myhardware) { + } + // Initialize the SPI library + void begin(); + + // If SPI is to used from within an interrupt, this function registers + // that interrupt with the SPI library, so beginTransaction() can + // prevent conflicts. The input interruptNumber is the number used + // with attachInterrupt. If SPI is used from a different interrupt + // (eg, a timer), interruptNumber should be 255. + void usingInterrupt(uint8_t n) { + if (n == 3 || n == 4 || n == 24 || n == 33) { + usingInterrupt(IRQ_PORTA); + } else if (n == 0 || n == 1 || (n >= 16 && n <= 19) || n == 25 || n == 32) { + usingInterrupt(IRQ_PORTB); + } else if ((n >= 9 && n <= 13) || n == 15 || n == 22 || n == 23 + || (n >= 27 && n <= 30)) { + usingInterrupt(IRQ_PORTC); + } else if (n == 2 || (n >= 5 && n <= 8) || n == 14 || n == 20 || n == 21) { + usingInterrupt(IRQ_PORTD); + } else if (n == 26 || n == 31) { + usingInterrupt(IRQ_PORTE); + } + } + void usingInterrupt(IRQ_NUMBER_t interruptName); + void notUsingInterrupt(IRQ_NUMBER_t interruptName); + + // Before using SPI.transfer() or asserting chip select pins, + // this function is used to gain exclusive access to the SPI bus + // and configure the correct settings. + void beginTransaction(SPISettings settings) { + if (interruptMasksUsed) { + __disable_irq(); + if (interruptMasksUsed & 0x01) { + interruptSave[0] = NVIC_ICER0 & interruptMask[0]; + NVIC_ICER0 = interruptSave[0]; + } + #if NVIC_NUM_INTERRUPTS > 32 + if (interruptMasksUsed & 0x02) { + interruptSave[1] = NVIC_ICER1 & interruptMask[1]; + NVIC_ICER1 = interruptSave[1]; + } + #endif + #if NVIC_NUM_INTERRUPTS > 64 && defined(NVIC_ISER2) + if (interruptMasksUsed & 0x04) { + interruptSave[2] = NVIC_ICER2 & interruptMask[2]; + NVIC_ICER2 = interruptSave[2]; + } + #endif + #if NVIC_NUM_INTERRUPTS > 96 && defined(NVIC_ISER3) + if (interruptMasksUsed & 0x08) { + interruptSave[3] = NVIC_ICER3 & interruptMask[3]; + NVIC_ICER3 = interruptSave[3]; + } + #endif + __enable_irq(); + } + #ifdef SPI_TRANSACTION_MISMATCH_LED + if (inTransactionFlag) { + pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT); + digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH); + } + inTransactionFlag = 1; + #endif + if (port().CTAR0 != settings.ctar) { + port().MCR = SPI_MCR_MDIS | SPI_MCR_HALT | SPI_MCR_PCSIS(0x3F); + port().CTAR0 = settings.ctar; + port().CTAR1 = settings.ctar| SPI_CTAR_FMSZ(8); + port().MCR = SPI_MCR_MSTR | SPI_MCR_PCSIS(0x3F); + } + } + + // Write to the SPI bus (MOSI pin) and also receive (MISO pin) + uint8_t transfer(uint8_t data) { + port().SR = SPI_SR_TCF; + port().PUSHR = data; + while (!(port().SR & SPI_SR_TCF)) ; // wait + return port().POPR; + } + uint16_t transfer16(uint16_t data) { + port().SR = SPI_SR_TCF; + port().PUSHR = data | SPI_PUSHR_CTAS(1); + while (!(port().SR & SPI_SR_TCF)) ; // wait + return port().POPR; + } + + void inline transfer(void *buf, size_t count) {transfer(buf, buf, count);} + void setTransferWriteFill(uint8_t ch ) {_transferWriteFill = ch;} + void transfer(const void * buf, void * retbuf, size_t count); + + // Asynch support (DMA ) +#ifdef SPI_HAS_TRANSFER_ASYNC + bool transfer(const void *txBuffer, void *rxBuffer, size_t count, EventResponderRef event_responder); + + friend void _spi_dma_rxISR0(void); + friend void _spi_dma_rxISR1(void); + friend void _spi_dma_rxISR2(void); + + inline void dma_rxisr(void); +#endif + + + // After performing a group of transfers and releasing the chip select + // signal, this function allows others to access the SPI bus + void endTransaction(void) { + #ifdef SPI_TRANSACTION_MISMATCH_LED + if (!inTransactionFlag) { + pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT); + digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH); + } + inTransactionFlag = 0; + #endif + if (interruptMasksUsed) { + if (interruptMasksUsed & 0x01) { + NVIC_ISER0 = interruptSave[0]; + } + #if NVIC_NUM_INTERRUPTS > 32 + if (interruptMasksUsed & 0x02) { + NVIC_ISER1 = interruptSave[1]; + } + #endif + #if NVIC_NUM_INTERRUPTS > 64 && defined(NVIC_ISER2) + if (interruptMasksUsed & 0x04) { + NVIC_ISER2 = interruptSave[2]; + } + #endif + #if NVIC_NUM_INTERRUPTS > 96 && defined(NVIC_ISER3) + if (interruptMasksUsed & 0x08) { + NVIC_ISER3 = interruptSave[3]; + } + #endif + } + } + + // Disable the SPI bus + void end(); + + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + void setBitOrder(uint8_t bitOrder); + + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + void setDataMode(uint8_t dataMode); + + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + void setClockDivider(uint8_t clockDiv) { + if (clockDiv == SPI_CLOCK_DIV2) { + setClockDivider_noInline(SPISettings(12000000, MSBFIRST, SPI_MODE0).ctar); + } else if (clockDiv == SPI_CLOCK_DIV4) { + setClockDivider_noInline(SPISettings(4000000, MSBFIRST, SPI_MODE0).ctar); + } else if (clockDiv == SPI_CLOCK_DIV8) { + setClockDivider_noInline(SPISettings(2000000, MSBFIRST, SPI_MODE0).ctar); + } else if (clockDiv == SPI_CLOCK_DIV16) { + setClockDivider_noInline(SPISettings(1000000, MSBFIRST, SPI_MODE0).ctar); + } else if (clockDiv == SPI_CLOCK_DIV32) { + setClockDivider_noInline(SPISettings(500000, MSBFIRST, SPI_MODE0).ctar); + } else if (clockDiv == SPI_CLOCK_DIV64) { + setClockDivider_noInline(SPISettings(250000, MSBFIRST, SPI_MODE0).ctar); + } else { /* clockDiv == SPI_CLOCK_DIV128 */ + setClockDivider_noInline(SPISettings(125000, MSBFIRST, SPI_MODE0).ctar); + } + } + void setClockDivider_noInline(uint32_t clk); + + // These undocumented functions should not be used. SPI.transfer() + // polls the hardware flag which is automatically cleared as the + // AVR responds to SPI's interrupt + void attachInterrupt() { } + void detachInterrupt() { } + + // Teensy 3.x can use alternate pins for these 3 SPI signals. + void setMOSI(uint8_t pin); + void setMISO(uint8_t pin); + void setSCK(uint8_t pin); + + // return true if "pin" has special chip select capability + uint8_t pinIsChipSelect(uint8_t pin); + bool pinIsMOSI(uint8_t pin); + bool pinIsMISO(uint8_t pin); + bool pinIsSCK(uint8_t pin); + // return true if both pin1 and pin2 have independent chip select capability + bool pinIsChipSelect(uint8_t pin1, uint8_t pin2); + // configure a pin for chip select and return its SPI_MCR_PCSIS bitmask + // setCS() is a special function, not intended for use from normal Arduino + // programs/sketches. See the ILI3941_t3 library for an example. + uint8_t setCS(uint8_t pin); + +private: + KINETISK_SPI_t & port() { return *(KINETISK_SPI_t *)port_addr; } + const SPI_Hardware_t & hardware() { return *(const SPI_Hardware_t *)hardware_addr; } + void updateCTAR(uint32_t ctar); + uintptr_t port_addr; + uintptr_t hardware_addr; + uint8_t miso_pin_index = 0; + uint8_t mosi_pin_index = 0; + uint8_t sck_pin_index = 0; + uint8_t interruptMasksUsed = 0; + uint32_t interruptMask[(NVIC_NUM_INTERRUPTS+31)/32] = {}; + uint32_t interruptSave[(NVIC_NUM_INTERRUPTS+31)/32] = {}; + #ifdef SPI_TRANSACTION_MISMATCH_LED + uint8_t inTransactionFlag = 0; + #endif + + uint8_t _transferWriteFill = 0; + + // DMA Support +#ifdef SPI_HAS_TRANSFER_ASYNC + bool initDMAChannels(); + DMAState _dma_state = DMAState::notAllocated; + uint32_t _dma_count_remaining = 0; // How many bytes left to output after current DMA completes + DMAChannel *_dmaTX = nullptr; + DMAChannel *_dmaRX = nullptr; + EventResponder *_dma_event_responder = nullptr; +#endif +}; + + + +/**********************************************************/ +/* 32 bit Teensy-LC */ +/**********************************************************/ + +#elif defined(__arm__) && defined(TEENSYDUINO) && defined(KINETISL) +#define SPI_ATOMIC_VERSION 1 + +class SPISettings { +public: + SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) { + if (__builtin_constant_p(clock)) { + init_AlwaysInline(clock, bitOrder, dataMode); + } else { + init_MightInline(clock, bitOrder, dataMode); + } + } + SPISettings() { + init_AlwaysInline(4000000, MSBFIRST, SPI_MODE0); + } +private: + void init_MightInline(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) { + init_AlwaysInline(clock, bitOrder, dataMode); + } + void init_AlwaysInline(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) + __attribute__((__always_inline__)) { + uint8_t c = SPI_C1_MSTR | SPI_C1_SPE; + if (dataMode & 0x04) c |= SPI_C1_CPHA; + if (dataMode & 0x08) c |= SPI_C1_CPOL; + if (bitOrder == LSBFIRST) c |= SPI_C1_LSBFE; + c1 = c; + if (__builtin_constant_p(clock)) { + if (clock >= F_BUS / 2) { c = SPI_BR_SPPR(0) | SPI_BR_SPR(0); + } else if (clock >= F_BUS / 4) { c = SPI_BR_SPPR(1) | SPI_BR_SPR(0); + } else if (clock >= F_BUS / 6) { c = SPI_BR_SPPR(2) | SPI_BR_SPR(0); + } else if (clock >= F_BUS / 8) { c = SPI_BR_SPPR(3) | SPI_BR_SPR(0); + } else if (clock >= F_BUS / 10) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(0); + } else if (clock >= F_BUS / 12) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(0); + } else if (clock >= F_BUS / 14) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(0); + } else if (clock >= F_BUS / 16) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(0); + } else if (clock >= F_BUS / 20) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(1); + } else if (clock >= F_BUS / 24) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(1); + } else if (clock >= F_BUS / 28) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(1); + } else if (clock >= F_BUS / 32) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(1); + } else if (clock >= F_BUS / 40) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(2); + } else if (clock >= F_BUS / 48) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(2); + } else if (clock >= F_BUS / 56) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(2); + } else if (clock >= F_BUS / 64) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(2); + } else if (clock >= F_BUS / 80) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(3); + } else if (clock >= F_BUS / 96) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(3); + } else if (clock >= F_BUS / 112) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(3); + } else if (clock >= F_BUS / 128) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(3); + } else if (clock >= F_BUS / 160) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(4); + } else if (clock >= F_BUS / 192) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(4); + } else if (clock >= F_BUS / 224) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(4); + } else if (clock >= F_BUS / 256) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(4); + } else if (clock >= F_BUS / 320) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(5); + } else if (clock >= F_BUS / 384) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(5); + } else if (clock >= F_BUS / 448) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(5); + } else if (clock >= F_BUS / 512) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(5); + } else if (clock >= F_BUS / 640) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(6); + } else /* F_BUS / 768 */ { c = SPI_BR_SPPR(5) | SPI_BR_SPR(6); + } + } else { + for (uint32_t i=0; i<30; i++) { + c = br_clock_table[i]; + if (clock >= F_BUS / br_div_table[i]) break; + } + } + br[0] = c; + if (__builtin_constant_p(clock)) { + if (clock >= (F_PLL/2) / 2) { c = SPI_BR_SPPR(0) | SPI_BR_SPR(0); + } else if (clock >= (F_PLL/2) / 4) { c = SPI_BR_SPPR(1) | SPI_BR_SPR(0); + } else if (clock >= (F_PLL/2) / 6) { c = SPI_BR_SPPR(2) | SPI_BR_SPR(0); + } else if (clock >= (F_PLL/2) / 8) { c = SPI_BR_SPPR(3) | SPI_BR_SPR(0); + } else if (clock >= (F_PLL/2) / 10) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(0); + } else if (clock >= (F_PLL/2) / 12) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(0); + } else if (clock >= (F_PLL/2) / 14) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(0); + } else if (clock >= (F_PLL/2) / 16) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(0); + } else if (clock >= (F_PLL/2) / 20) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(1); + } else if (clock >= (F_PLL/2) / 24) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(1); + } else if (clock >= (F_PLL/2) / 28) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(1); + } else if (clock >= (F_PLL/2) / 32) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(1); + } else if (clock >= (F_PLL/2) / 40) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(2); + } else if (clock >= (F_PLL/2) / 48) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(2); + } else if (clock >= (F_PLL/2) / 56) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(2); + } else if (clock >= (F_PLL/2) / 64) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(2); + } else if (clock >= (F_PLL/2) / 80) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(3); + } else if (clock >= (F_PLL/2) / 96) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(3); + } else if (clock >= (F_PLL/2) / 112) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(3); + } else if (clock >= (F_PLL/2) / 128) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(3); + } else if (clock >= (F_PLL/2) / 160) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(4); + } else if (clock >= (F_PLL/2) / 192) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(4); + } else if (clock >= (F_PLL/2) / 224) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(4); + } else if (clock >= (F_PLL/2) / 256) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(4); + } else if (clock >= (F_PLL/2) / 320) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(5); + } else if (clock >= (F_PLL/2) / 384) { c = SPI_BR_SPPR(5) | SPI_BR_SPR(5); + } else if (clock >= (F_PLL/2) / 448) { c = SPI_BR_SPPR(6) | SPI_BR_SPR(5); + } else if (clock >= (F_PLL/2) / 512) { c = SPI_BR_SPPR(7) | SPI_BR_SPR(5); + } else if (clock >= (F_PLL/2) / 640) { c = SPI_BR_SPPR(4) | SPI_BR_SPR(6); + } else /* (F_PLL/2) / 768 */ { c = SPI_BR_SPPR(5) | SPI_BR_SPR(6); + } + } else { + for (uint32_t i=0; i<30; i++) { + c = br_clock_table[i]; + if (clock >= (F_PLL/2) / br_div_table[i]) break; + } + } + br[1] = c; + } + static const uint8_t br_clock_table[30]; + static const uint16_t br_div_table[30]; + uint8_t c1, br[2]; + friend class SPIClass; +}; + + +class SPIClass { // Teensy-LC +public: + static const uint8_t CNT_MISO_PINS = 2; + static const uint8_t CNT_MMOSI_PINS = 2; + static const uint8_t CNT_SCK_PINS = 2; + static const uint8_t CNT_CS_PINS = 2; + typedef struct { + volatile uint32_t &clock_gate_register; + uint32_t clock_gate_mask; + uint8_t br_index; + uint8_t tx_dma_channel; + uint8_t rx_dma_channel; + void (*dma_isr)(); + uint8_t miso_pin[CNT_MISO_PINS]; + uint32_t miso_mux[CNT_MISO_PINS]; + uint8_t mosi_pin[CNT_MMOSI_PINS]; + uint32_t mosi_mux[CNT_MMOSI_PINS]; + uint8_t sck_pin[CNT_SCK_PINS]; + uint32_t sck_mux[CNT_SCK_PINS]; + uint8_t cs_pin[CNT_CS_PINS]; + uint32_t cs_mux[CNT_CS_PINS]; + uint8_t cs_mask[CNT_CS_PINS]; + } SPI_Hardware_t; + static const SPI_Hardware_t spi0_hardware; + static const SPI_Hardware_t spi1_hardware; + enum DMAState { notAllocated, idle, active, completed}; +public: + constexpr SPIClass(uintptr_t myport, uintptr_t myhardware) + : port_addr(myport), hardware_addr(myhardware) { + } + // Initialize the SPI library + void begin(); + + // If SPI is to used from within an interrupt, this function registers + // that interrupt with the SPI library, so beginTransaction() can + // prevent conflicts. The input interruptNumber is the number used + // with attachInterrupt. If SPI is used from a different interrupt + // (eg, a timer), interruptNumber should be 255. + void usingInterrupt(uint8_t n) { + if (n == 3 || n == 4) { + usingInterrupt(IRQ_PORTA); + } else if ((n >= 2 && n <= 15) || (n >= 20 && n <= 23)) { + usingInterrupt(IRQ_PORTCD); + } + } + void usingInterrupt(IRQ_NUMBER_t interruptName) { + uint32_t n = (uint32_t)interruptName; + if (n < NVIC_NUM_INTERRUPTS) interruptMask |= (1 << n); + } + void notUsingInterrupt(IRQ_NUMBER_t interruptName) { + uint32_t n = (uint32_t)interruptName; + if (n < NVIC_NUM_INTERRUPTS) interruptMask &= ~(1 << n); + } + + // Before using SPI.transfer() or asserting chip select pins, + // this function is used to gain exclusive access to the SPI bus + // and configure the correct settings. + void beginTransaction(SPISettings settings) { + if (interruptMask) { + __disable_irq(); + interruptSave = NVIC_ICER0 & interruptMask; + NVIC_ICER0 = interruptSave; + __enable_irq(); + } + #ifdef SPI_TRANSACTION_MISMATCH_LED + if (inTransactionFlag) { + pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT); + digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH); + } + inTransactionFlag = 1; + #endif + port().C1 = settings.c1; + port().BR = settings.br[hardware().br_index]; + } + + // Write to the SPI bus (MOSI pin) and also receive (MISO pin) + uint8_t transfer(uint8_t data) { + port().DL = data; + while (!(port().S & SPI_S_SPRF)) ; // wait + return port().DL; + } + uint16_t transfer16(uint16_t data) { + port().C2 = SPI_C2_SPIMODE; + port().S; + port().DL = data; + port().DH = data >> 8; + while (!(port().S & SPI_S_SPRF)) ; // wait + uint16_t r = port().DL | (port().DH << 8); + port().C2 = 0; + port().S; + return r; + } + void transfer(void *buf, size_t count) { + if (count == 0) return; + uint8_t *p = (uint8_t *)buf; + while (!(port().S & SPI_S_SPTEF)) ; // wait + port().DL = *p; + while (--count > 0) { + uint8_t out = *(p + 1); + while (!(port().S & SPI_S_SPTEF)) ; // wait + __disable_irq(); + port().DL = out; + while (!(port().S & SPI_S_SPRF)) ; // wait + uint8_t in = port().DL; + __enable_irq(); + *p++ = in; + } + while (!(port().S & SPI_S_SPRF)) ; // wait + *p = port().DL; + } + + void setTransferWriteFill(uint8_t ch ) {_transferWriteFill = ch;} + void transfer(const void * buf, void * retbuf, size_t count); + + // Asynch support (DMA ) +#ifdef SPI_HAS_TRANSFER_ASYNC + bool transfer(const void *txBuffer, void *rxBuffer, size_t count, EventResponderRef event_responder); + + friend void _spi_dma_rxISR0(void); + friend void _spi_dma_rxISR1(void); + inline void dma_isr(void); +#endif + + // After performing a group of transfers and releasing the chip select + // signal, this function allows others to access the SPI bus + void endTransaction(void) { + #ifdef SPI_TRANSACTION_MISMATCH_LED + if (!inTransactionFlag) { + pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT); + digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH); + } + inTransactionFlag = 0; + #endif + if (interruptMask) { + NVIC_ISER0 = interruptSave; + } + } + + // Disable the SPI bus + void end(); + + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + void setBitOrder(uint8_t bitOrder) { + uint8_t c = port().C1 | SPI_C1_SPE; + if (bitOrder == LSBFIRST) c |= SPI_C1_LSBFE; + else c &= ~SPI_C1_LSBFE; + port().C1 = c; + } + + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + void setDataMode(uint8_t dataMode) { + uint8_t c = port().C1 | SPI_C1_SPE; + if (dataMode & 0x04) c |= SPI_C1_CPHA; + else c &= ~SPI_C1_CPHA; + if (dataMode & 0x08) c |= SPI_C1_CPOL; + else c &= ~SPI_C1_CPOL; + port().C1 = c; + } + + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + void setClockDivider(uint8_t clockDiv) { + unsigned int i = hardware().br_index; + if (clockDiv == SPI_CLOCK_DIV2) { + port().BR = (SPISettings(12000000, MSBFIRST, SPI_MODE0).br[i]); + } else if (clockDiv == SPI_CLOCK_DIV4) { + port().BR = (SPISettings(4000000, MSBFIRST, SPI_MODE0).br[i]); + } else if (clockDiv == SPI_CLOCK_DIV8) { + port().BR = (SPISettings(2000000, MSBFIRST, SPI_MODE0).br[i]); + } else if (clockDiv == SPI_CLOCK_DIV16) { + port().BR = (SPISettings(1000000, MSBFIRST, SPI_MODE0).br[i]); + } else if (clockDiv == SPI_CLOCK_DIV32) { + port().BR = (SPISettings(500000, MSBFIRST, SPI_MODE0).br[i]); + } else if (clockDiv == SPI_CLOCK_DIV64) { + port().BR = (SPISettings(250000, MSBFIRST, SPI_MODE0).br[i]); + } else { /* clockDiv == SPI_CLOCK_DIV128 */ + port().BR = (SPISettings(125000, MSBFIRST, SPI_MODE0).br[i]); + } + } + + // These undocumented functions should not be used. SPI.transfer() + // polls the hardware flag which is automatically cleared as the + // AVR responds to SPI's interrupt + void attachInterrupt() { } + void detachInterrupt() { } + + // Teensy LC can use alternate pins for these 3 SPI signals. + void setMOSI(uint8_t pin); + void setMISO(uint8_t pin); + void setSCK(uint8_t pin); + // return true if "pin" has special chip select capability + bool pinIsChipSelect(uint8_t pin); + bool pinIsMOSI(uint8_t pin); + bool pinIsMISO(uint8_t pin); + bool pinIsSCK(uint8_t pin); + // return true if both pin1 and pin2 have independent chip select capability + bool pinIsChipSelect(uint8_t pin1, uint8_t pin2) { return false; } + // configure a pin for chip select and return its SPI_MCR_PCSIS bitmask + // setCS() is a special function, not intended for use from normal Arduino + // programs/sketches. See the ILI3941_t3 library for an example. + uint8_t setCS(uint8_t pin); + +private: + KINETISL_SPI_t & port() { return *(KINETISL_SPI_t *)port_addr; } + const SPI_Hardware_t & hardware() { return *(const SPI_Hardware_t *)hardware_addr; } + uintptr_t port_addr; + uintptr_t hardware_addr; + uint32_t interruptMask = 0; + uint32_t interruptSave = 0; + uint8_t mosi_pin_index = 0; + uint8_t miso_pin_index = 0; + uint8_t sck_pin_index = 0; + #ifdef SPI_TRANSACTION_MISMATCH_LED + uint8_t inTransactionFlag = 0; + #endif + uint8_t _transferWriteFill = 0; +#ifdef SPI_HAS_TRANSFER_ASYNC + // DMA Support + bool initDMAChannels(); + + DMAState _dma_state = DMAState::notAllocated; + uint32_t _dma_count_remaining = 0; // How many bytes left to output after current DMA completes + DMAChannel *_dmaTX = nullptr; + DMAChannel *_dmaRX = nullptr; + EventResponder *_dma_event_responder = nullptr; +#endif +}; + + + +/**********************************************************/ +/* 32 bit Teensy 4.x */ +/**********************************************************/ + +#elif defined(__arm__) && defined(TEENSYDUINO) && (defined(__IMXRT1052__) || defined(__IMXRT1062__)) +#define SPI_ATOMIC_VERSION 1 + +//#include "debug/printf.h" + + +class SPISettings { +public: + SPISettings(uint32_t clockIn, uint8_t bitOrderIn, uint8_t dataModeIn) : _clock(clockIn) { + init_AlwaysInline(bitOrderIn, dataModeIn); + } + + SPISettings() : _clock(4000000) { + init_AlwaysInline(MSBFIRST, SPI_MODE0); + } +private: + void init_AlwaysInline(uint8_t bitOrder, uint8_t dataMode) + __attribute__((__always_inline__)) { + tcr = LPSPI_TCR_FRAMESZ(7); // TCR has polarity and bit order too + + // handle LSB setup + if (bitOrder == LSBFIRST) tcr |= LPSPI_TCR_LSBF; + + // Handle Data Mode + if (dataMode & 0x08) tcr |= LPSPI_TCR_CPOL; + + // Note: On T3.2 when we set CPHA it also updated the timing. It moved the + // PCS to SCK Delay Prescaler into the After SCK Delay Prescaler + if (dataMode & 0x04) tcr |= LPSPI_TCR_CPHA; + } + + inline uint32_t clock() {return _clock;} + + uint32_t _clock; + uint32_t tcr; // transmit command, pg 2664 (RT1050 ref, rev 2) + friend class SPIClass; +}; + +class SPIClass { // Teensy 4 +public: + #if defined(ARDUINO_TEENSY41) + // T4.1 has SPI2 pins on memory connectors as well as SDCard + static const uint8_t CNT_MISO_PINS = 2; + static const uint8_t CNT_MOSI_PINS = 2; + static const uint8_t CNT_SCK_PINS = 2; + static const uint8_t CNT_CS_PINS = 3; + #else + static const uint8_t CNT_MISO_PINS = 1; + static const uint8_t CNT_MOSI_PINS = 1; + static const uint8_t CNT_SCK_PINS = 1; + static const uint8_t CNT_CS_PINS = 1; +#endif + typedef struct { + volatile uint32_t &clock_gate_register; + const uint32_t clock_gate_mask; + uint8_t tx_dma_channel; + uint8_t rx_dma_channel; + void (*dma_rxisr)(); + // MISO pins + const uint8_t miso_pin[CNT_MISO_PINS]; + const uint32_t miso_mux[CNT_MISO_PINS]; + const uint8_t miso_select_val[CNT_MISO_PINS]; + volatile uint32_t &miso_select_input_register; + + // MOSI pins + const uint8_t mosi_pin[CNT_MOSI_PINS]; + const uint32_t mosi_mux[CNT_MOSI_PINS]; + const uint8_t mosi_select_val[CNT_MOSI_PINS]; + volatile uint32_t &mosi_select_input_register; + + // SCK pins + const uint8_t sck_pin[CNT_SCK_PINS]; + const uint32_t sck_mux[CNT_SCK_PINS]; + const uint8_t sck_select_val[CNT_SCK_PINS]; + volatile uint32_t &sck_select_input_register; + + // CS Pins + const uint8_t cs_pin[CNT_CS_PINS]; + const uint32_t cs_mux[CNT_CS_PINS]; + const uint8_t cs_mask[CNT_CS_PINS]; + const uint8_t pcs_select_val[CNT_CS_PINS]; + volatile uint32_t *pcs_select_input_register[CNT_CS_PINS]; + + } SPI_Hardware_t; + static const SPI_Hardware_t spiclass_lpspi4_hardware; +#if defined(__IMXRT1062__) + static const SPI_Hardware_t spiclass_lpspi3_hardware; + static const SPI_Hardware_t spiclass_lpspi1_hardware; +#endif +public: + constexpr SPIClass(uintptr_t myport, uintptr_t myhardware) + : port_addr(myport), hardware_addr(myhardware) { + } +// constexpr SPIClass(IMXRT_LPSPI_t *myport, const SPI_Hardware_t *myhardware) +// : port(myport), hardware(myhardware) { +// } + // Initialize the SPI library + void begin(); + + // If SPI is to used from within an interrupt, this function registers + // that interrupt with the SPI library, so beginTransaction() can + // prevent conflicts. The input interruptNumber is the number used + // with attachInterrupt. If SPI is used from a different interrupt + // (eg, a timer), interruptNumber should be 255. + void usingInterrupt(uint8_t n) { + if (n >= CORE_NUM_DIGITAL) return; +#if defined(__IMXRT1062__) + usingInterrupt(IRQ_GPIO6789); +#elif defined(__IMXRT1052__) + volatile uint32_t *gpio = portOutputRegister(n); + switch((uint32_t)gpio) { + case (uint32_t)&GPIO1_DR: + usingInterrupt(IRQ_GPIO1_0_15); + usingInterrupt(IRQ_GPIO1_16_31); + break; + case (uint32_t)&GPIO2_DR: + usingInterrupt(IRQ_GPIO2_0_15); + usingInterrupt(IRQ_GPIO2_16_31); + break; + case (uint32_t)&GPIO3_DR: + usingInterrupt(IRQ_GPIO3_0_15); + usingInterrupt(IRQ_GPIO3_16_31); + break; + case (uint32_t)&GPIO4_DR: + usingInterrupt(IRQ_GPIO4_0_15); + usingInterrupt(IRQ_GPIO4_16_31); + break; + } +#endif + } + void usingInterrupt(IRQ_NUMBER_t interruptName); + void notUsingInterrupt(IRQ_NUMBER_t interruptName); + + // Before using SPI.transfer() or asserting chip select pins, + // this function is used to gain exclusive access to the SPI bus + // and configure the correct settings. + void beginTransaction(SPISettings settings) { + if (interruptMasksUsed) { + __disable_irq(); + if (interruptMasksUsed & 0x01) { + interruptSave[0] = NVIC_ICER0 & interruptMask[0]; + NVIC_ICER0 = interruptSave[0]; + } + if (interruptMasksUsed & 0x02) { + interruptSave[1] = NVIC_ICER1 & interruptMask[1]; + NVIC_ICER1 = interruptSave[1]; + } + if (interruptMasksUsed & 0x04) { + interruptSave[2] = NVIC_ICER2 & interruptMask[2]; + NVIC_ICER2 = interruptSave[2]; + } + if (interruptMasksUsed & 0x08) { + interruptSave[3] = NVIC_ICER3 & interruptMask[3]; + NVIC_ICER3 = interruptSave[3]; + } + if (interruptMasksUsed & 0x10) { + interruptSave[4] = NVIC_ICER4 & interruptMask[4]; + NVIC_ICER4 = interruptSave[4]; + } + __enable_irq(); + } + #ifdef SPI_TRANSACTION_MISMATCH_LED + if (inTransactionFlag) { + pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT); + digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH); + } + inTransactionFlag = 1; + #endif + + //printf("trans\n"); + if (settings.clock() != _clock) { + static const uint32_t clk_sel[4] = {664615384, // PLL3 PFD1 + 720000000, // PLL3 PFD0 + 528000000, // PLL2 + 396000000}; // PLL2 PFD2 + + // First save away the new settings.. + _clock = settings.clock(); + + uint32_t cbcmr = CCM_CBCMR; + uint32_t clkhz = clk_sel[(cbcmr >> 4) & 0x03] / (((cbcmr >> 26 ) & 0x07 ) + 1); // LPSPI peripheral clock + + uint32_t d, div; + d = _clock ? clkhz/_clock : clkhz; + + if (d && clkhz/d > _clock) d++; + if (d > 257) d= 257; // max div + if (d > 2) { + div = d-2; + } else { + div =0; + } + + _ccr = LPSPI_CCR_SCKDIV(div) | LPSPI_CCR_DBT(div/2) | LPSPI_CCR_PCSSCK(div/2); + + } + //Serial.printf("SPI.beginTransaction CCR:%x TCR:%x\n", _ccr, settings.tcr); + port().CR = 0; + port().CFGR1 = LPSPI_CFGR1_MASTER | LPSPI_CFGR1_SAMPLE; + port().CCR = _ccr; + port().TCR = settings.tcr; + port().CR = LPSPI_CR_MEN; + + } + + // Write to the SPI bus (MOSI pin) and also receive (MISO pin) + uint8_t transfer(uint8_t data) { + // TODO: check for space in fifo? + port().TDR = data; + while (1) { + uint32_t fifo = (port().FSR >> 16) & 0x1F; + if (fifo > 0) return port().RDR; + } + //port().SR = SPI_SR_TCF; + //port().PUSHR = data; + //while (!(port().SR & SPI_SR_TCF)) ; // wait + //return port().POPR; + } + uint16_t transfer16(uint16_t data) { + uint32_t tcr = port().TCR; + port().TCR = (tcr & 0xfffff000) | LPSPI_TCR_FRAMESZ(15); // turn on 16 bit mode + port().TDR = data; // output 16 bit data. + while ((port().RSR & LPSPI_RSR_RXEMPTY)) ; // wait while the RSR fifo is empty... + port().TCR = tcr; // restore back + return port().RDR; + } + + void inline transfer(void *buf, size_t count) {transfer(buf, buf, count);} + void setTransferWriteFill(uint8_t ch ) {_transferWriteFill = ch;} + void transfer(const void * buf, void * retbuf, size_t count); + + // Asynch support (DMA ) +#ifdef SPI_HAS_TRANSFER_ASYNC + bool transfer(const void *txBuffer, void *rxBuffer, size_t count, EventResponderRef event_responder); + + friend void _spi_dma_rxISR0(void); + inline void dma_rxisr(void); +#endif + + + // After performing a group of transfers and releasing the chip select + // signal, this function allows others to access the SPI bus + void endTransaction(void) { + #ifdef SPI_TRANSACTION_MISMATCH_LED + if (!inTransactionFlag) { + pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT); + digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH); + } + inTransactionFlag = 0; + #endif + if (interruptMasksUsed) { + if (interruptMasksUsed & 0x01) NVIC_ISER0 = interruptSave[0]; + if (interruptMasksUsed & 0x02) NVIC_ISER1 = interruptSave[1]; + if (interruptMasksUsed & 0x04) NVIC_ISER2 = interruptSave[2]; + if (interruptMasksUsed & 0x08) NVIC_ISER3 = interruptSave[3]; + if (interruptMasksUsed & 0x10) NVIC_ISER4 = interruptSave[4]; + } + //Serial.printf("SPI.endTransaction CCR:%x TCR:%x\n", port().CCR, port().TCR); + } + + // Disable the SPI bus + void end(); + + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + void setBitOrder(uint8_t bitOrder); + + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + void setDataMode(uint8_t dataMode); + + // This function is deprecated. New applications should use + // beginTransaction() to configure SPI settings. + void setClockDivider(uint8_t clockDiv) { + if (clockDiv == SPI_CLOCK_DIV2) { + setClockDivider_noInline(12000000); + } else if (clockDiv == SPI_CLOCK_DIV4) { + setClockDivider_noInline(4000000); + } else if (clockDiv == SPI_CLOCK_DIV8) { + setClockDivider_noInline(2000000); + } else if (clockDiv == SPI_CLOCK_DIV16) { + setClockDivider_noInline(1000000); + } else if (clockDiv == SPI_CLOCK_DIV32) { + setClockDivider_noInline(500000); + } else if (clockDiv == SPI_CLOCK_DIV64) { + setClockDivider_noInline(250000); + } else { /* clockDiv == SPI_CLOCK_DIV128 */ + setClockDivider_noInline(125000); + } + } + void setClockDivider_noInline(uint32_t clk); + + // These undocumented functions should not be used. SPI.transfer() + // polls the hardware flag which is automatically cleared as the + // AVR responds to SPI's interrupt + void attachInterrupt() { } + void detachInterrupt() { } + + // Teensy 3.x can use alternate pins for these 3 SPI signals. + void setMOSI(uint8_t pin); + void setMISO(uint8_t pin); + void setSCK(uint8_t pin); + + // return true if "pin" has special chip select capability + uint8_t pinIsChipSelect(uint8_t pin); + bool pinIsMOSI(uint8_t pin); + bool pinIsMISO(uint8_t pin); + bool pinIsSCK(uint8_t pin); + // return true if both pin1 and pin2 have independent chip select capability + bool pinIsChipSelect(uint8_t pin1, uint8_t pin2); + // configure a pin for chip select and return its SPI_MCR_PCSIS bitmask + // setCS() is a special function, not intended for use from normal Arduino + // programs/sketches. See the ILI3941_t3 library for an example. + uint8_t setCS(uint8_t pin); + +private: +private: + IMXRT_LPSPI_t & port() { return *(IMXRT_LPSPI_t *)port_addr; } + const SPI_Hardware_t & hardware() { return *(const SPI_Hardware_t *)hardware_addr; } + uintptr_t port_addr; + uintptr_t hardware_addr; + + uint32_t _clock = 0; + uint32_t _ccr = 0; + + //KINETISK_SPI_t & port() { return *(KINETISK_SPI_t *)port_addr; } +// IMXRT_LPSPI_t * const port; +// const SPI_Hardware_t * const hardware; + void updateCTAR(uint32_t ctar); + uint8_t miso_pin_index = 0; + uint8_t mosi_pin_index = 0; + uint8_t sck_pin_index = 0; + uint8_t interruptMasksUsed = 0; + uint32_t interruptMask[(NVIC_NUM_INTERRUPTS+31)/32] = {}; + uint32_t interruptSave[(NVIC_NUM_INTERRUPTS+31)/32] = {}; + #ifdef SPI_TRANSACTION_MISMATCH_LED + uint8_t inTransactionFlag = 0; + #endif + + uint8_t _transferWriteFill = 0; + + // DMA Support +#ifdef SPI_HAS_TRANSFER_ASYNC + bool initDMAChannels(); + enum DMAState { notAllocated, idle, active, completed}; + enum {MAX_DMA_COUNT=32767}; + DMAState _dma_state = DMAState::notAllocated; + uint32_t _dma_count_remaining = 0; // How many bytes left to output after current DMA completes + DMAChannel *_dmaTX = nullptr; + DMAChannel *_dmaRX = nullptr; + EventResponder *_dma_event_responder = nullptr; +#endif +}; + + + + + + + + + + +#endif + + + +extern SPIClass SPI; +#if defined(__MKL26Z64__) +extern SPIClass SPI1; +#endif +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) || defined(__IMXRT1062__) +extern SPIClass SPI1; +extern SPIClass SPI2; +#endif +#endif diff --git a/Firmware_V3/lib/SdFat/src/BufferedPrint.h b/Firmware_V3/lib/SdFat/src/BufferedPrint.h new file mode 100644 index 0000000..a6a3e1f --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/BufferedPrint.h @@ -0,0 +1,269 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef BufferedPrint_h +#define BufferedPrint_h +/** + * \file + * \brief Fast buffered print. + */ +#include "common/FmtNumber.h" +/** + * \class BufferedPrint + * \brief Fast buffered print template. + */ +template +class BufferedPrint { + public: + BufferedPrint() : m_wr(nullptr), m_in(0) {} + /** BufferedPrint constructor. + * \param[in] wr Print destination. + */ + explicit BufferedPrint(WriteClass* wr) : m_wr(wr), m_in(0) {} + /** Initialize the BuffedPrint class. + * \param[in] wr Print destination. + */ + void begin(WriteClass* wr) { + m_wr = wr; + m_in = 0; + } + /** Flush the buffer - same as sync() with no status return. */ + void flush() {sync();} + /** Print a character followed by a field terminator. + * \param[in] c character to print. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \return true for success or false if an error occurs. + */ + size_t printField(char c, char term) { + char buf[3]; + char* str = buf + sizeof(buf); + if (term) { + *--str = term; + if (term == '\n') { + *--str = '\r'; + } + } + *--str = c; + return write(str, buf + sizeof(buf) - str); + } + /** Print a string stored in AVR flash followed by a field terminator. + * \param[in] fsh string to print. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \return true for success or false if an error occurs. + */ + size_t printField(const __FlashStringHelper *fsh, char term) { +#ifdef __AVR__ + size_t rtn = 0; + PGM_P p = reinterpret_cast(fsh); + char c; + while ((c = pgm_read_byte(p++))) { + if (!write(&c, 1)) { + return 0; + } + rtn++; + } + if (term) { + char buf[2]; + char* str = buf + sizeof(buf); + *--str = term; + if (term == '\n') { + *--str = '\r'; + } + rtn += write(str, buf + sizeof(buf) - str); + } + return rtn; +#else // __AVR__ + return printField(reinterpret_cast(fsh), term); +#endif // __AVR__ + } + /** Print a string followed by a field terminator. + * \param[in] str string to print. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \return true for success or false if an error occurs. + */ + size_t printField(const char* str, char term) { + size_t rtn = write(str, strlen(str)); + if (term) { + char buf[2]; + char* ptr = buf + sizeof(buf); + *--ptr = term; + if (term == '\n') { + *--ptr = '\r'; + } + rtn += write(ptr, buf + sizeof(buf) - ptr); + } + return rtn; + } + /** Print a double followed by a field terminator. + * \param[in] d The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \param[in] prec Number of digits after decimal point. + * \return true for success or false if an error occurs. + */ + size_t printField(double d, char term, uint8_t prec = 2) { + char buf[24]; + char* str = buf + sizeof(buf); + if (term) { + *--str = term; + if (term == '\n') { + *--str = '\r'; + } + } + str = fmtDouble(str, d, prec, false); + return write(str, buf + sizeof(buf) - str); + } + /** Print a float followed by a field terminator. + * \param[in] f The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \param[in] prec Number of digits after decimal point. + * \return true for success or false if an error occurs. + */ + size_t printField(float f, char term, uint8_t prec = 2) { + return printField(static_cast(f), term, prec); + } + /** Print an integer value for 8, 16, and 32 bit signed and unsigned types. + * \param[in] n The value to print. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \return true for success or false if an error occurs. + */ + template + size_t printField(Type n, char term) { + const uint8_t DIM = sizeof(Type) <= 2 ? 8 : 13; + char buf[DIM]; + char* str = buf + sizeof(buf); + + if (term) { + *--str = term; + if (term == '\n') { + *--str = '\r'; + } + } + Type p = n < 0 ? -n : n; + if (sizeof(Type) <= 2) { + str = fmtBase10(str, (uint16_t)p); + } else { + str = fmtBase10(str, (uint32_t)p); + } + if (n < 0) { + *--str = '-'; + } + return write(str, buf + sizeof(buf) - str); + } + /** Print CR LF. + * \return true for success or false if an error occurs. + */ + size_t println() { + char buf[2]; + buf[0] = '\r'; + buf[1] = '\n'; + return write(buf, 2); + } + /** Print a double. + * \param[in] d The number to be printed. + * \param[in] prec Number of digits after decimal point. + * \return true for success or false if an error occurs. + */ + size_t print(double d, uint8_t prec = 2) { + return printField(d, 0, prec); + } + /** Print a double followed by CR LF. + * \param[in] d The number to be printed. + * \param[in] prec Number of digits after decimal point. + * \return true for success or false if an error occurs. + */ + size_t println(double d, uint8_t prec = 2) { + return printField(d, '\n', prec); + } + /** Print a float. + * \param[in] f The number to be printed. + * \param[in] prec Number of digits after decimal point. + * \return true for success or false if an error occurs. + */ + size_t print(float f, uint8_t prec = 2) { + return printField(static_cast(f), 0, prec); + } + /** Print a float followed by CR LF. + * \param[in] f The number to be printed. + * \param[in] prec Number of digits after decimal point. + * \return true for success or false if an error occurs. + */ + size_t println(float f, uint8_t prec) { + return printField(static_cast(f), '\n', prec); + } + /** Print character, string, or number. + * \param[in] v item to print. + * \return true for success or false if an error occurs. + */ + template + size_t print(Type v) { + return printField(v, 0); + } + /** Print character, string, or number followed by CR LF. + * \param[in] v item to print. + * \return true for success or false if an error occurs. + */ + template + size_t println(Type v) { + return printField(v, '\n'); + } + + /** Flush the buffer. + * \return true for success or false if an error occurs. + */ + bool sync() { + if (!m_wr || m_wr->write(m_buf, m_in) != m_in) { + return false; + } + m_in = 0; + return true; + } + /** Write data to an open file. + * \param[in] src Pointer to the location of the data to be written. + * + * \param[in] n Number of bytes to write. + * + * \return For success write() returns the number of bytes written, always + * \a n. + */ + size_t write(const void* src, size_t n) { + if ((m_in + n) > sizeof(m_buf)) { + if (!sync()) { + return 0; + } + if (n >= sizeof(m_buf)) { + return n == m_wr->write((const uint8_t*)src, n) ? n : 0; + } + } + memcpy(m_buf + m_in, src, n); + m_in += n; + return n; + } + + private: + WriteClass* m_wr; + uint8_t m_in; + // Insure room for double. + uint8_t m_buf[BUF_DIM < 24 ? 24 : BUF_DIM]; // NOLINT +}; +#endif // BufferedPrint_h diff --git a/Firmware_V3/lib/SdFat/src/DigitalIO/DigitalPin.h b/Firmware_V3/lib/SdFat/src/DigitalIO/DigitalPin.h new file mode 100644 index 0000000..d44bb57 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/DigitalIO/DigitalPin.h @@ -0,0 +1,381 @@ +/* Arduino DigitalIO Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the Arduino DigitalIO Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino DigitalIO Library. If not, see + * . + */ +/** + * @file + * @brief Fast Digital Pin functions + * + * @defgroup digitalPin Fast Pin I/O + * @details Fast Digital I/O functions and template class. + * @{ + */ +#ifndef DigitalPin_h +#define DigitalPin_h +#if defined(__AVR__) || defined(DOXYGEN) +#include +/** GpioPinMap type */ +struct GpioPinMap_t { + volatile uint8_t* pin; /**< address of PIN for this pin */ + volatile uint8_t* ddr; /**< address of DDR for this pin */ + volatile uint8_t* port; /**< address of PORT for this pin */ + uint8_t mask; /**< bit mask for this pin */ +}; + +/** Initializer macro. */ +#define GPIO_PIN(reg, bit) {&PIN##reg, &DDR##reg, &PORT##reg, 1 << bit} + +// Include pin map for current board. +#include "boards/GpioPinMap.h" +//------------------------------------------------------------------------------ +/** generate bad pin number error */ +void badPinNumber(void) + __attribute__((error("Pin number is too large or not a constant"))); +//------------------------------------------------------------------------------ +/** Check for valid pin number + * @param[in] pin Number of pin to be checked. + */ +static inline __attribute__((always_inline)) +void badPinCheck(uint8_t pin) { + if (!__builtin_constant_p(pin) || pin >= NUM_DIGITAL_PINS) { + badPinNumber(); + } +} +//------------------------------------------------------------------------------ +/** DDR register address + * @param[in] pin Arduino pin number + * @return register address + */ +static inline __attribute__((always_inline)) +volatile uint8_t* ddrReg(uint8_t pin) { + badPinCheck(pin); + return GpioPinMap[pin].ddr; +} +//------------------------------------------------------------------------------ +/** Bit mask for pin + * @param[in] pin Arduino pin number + * @return mask + */ +static inline __attribute__((always_inline)) +uint8_t pinMask(uint8_t pin) { + badPinCheck(pin); + return GpioPinMap[pin].mask; +} +//------------------------------------------------------------------------------ +/** PIN register address + * @param[in] pin Arduino pin number + * @return register address + */ +static inline __attribute__((always_inline)) +volatile uint8_t* pinReg(uint8_t pin) { + badPinCheck(pin); + return GpioPinMap[pin].pin; +} +//------------------------------------------------------------------------------ +/** PORT register address + * @param[in] pin Arduino pin number + * @return register address + */ +static inline __attribute__((always_inline)) +volatile uint8_t* portReg(uint8_t pin) { + badPinCheck(pin); + return GpioPinMap[pin].port; +} +//------------------------------------------------------------------------------ +/** Fast write helper. + * @param[in] address I/O register address + * @param[in] mask bit mask for pin + * @param[in] level value for bit + */ +static inline __attribute__((always_inline)) +void fastBitWriteSafe(volatile uint8_t* address, uint8_t mask, bool level) { + uint8_t s; + if (address > reinterpret_cast(0X3F)) { + s = SREG; + cli(); + } + if (level) { + *address |= mask; + } else { + *address &= ~mask; + } + if (address > reinterpret_cast(0X3F)) { + SREG = s; + } +} +//------------------------------------------------------------------------------ +/** Read pin value. + * @param[in] pin Arduino pin number + * @return value read + */ +static inline __attribute__((always_inline)) +bool fastDigitalRead(uint8_t pin) { + return *pinReg(pin) & pinMask(pin); +} +//------------------------------------------------------------------------------ +/** Toggle a pin. + * @param[in] pin Arduino pin number + * + * If the pin is in output mode toggle the pin level. + * If the pin is in input mode toggle the state of the 20K pullup. + */ +static inline __attribute__((always_inline)) +void fastDigitalToggle(uint8_t pin) { + if (pinReg(pin) > reinterpret_cast(0X3F)) { + // must write bit to high address port + *pinReg(pin) = pinMask(pin); + } else { + // will compile to sbi and PIN register will not be read. + *pinReg(pin) |= pinMask(pin); + } +} +//------------------------------------------------------------------------------ +/** Set pin value. + * @param[in] pin Arduino pin number + * @param[in] level value to write + */ +static inline __attribute__((always_inline)) +void fastDigitalWrite(uint8_t pin, bool level) { + fastBitWriteSafe(portReg(pin), pinMask(pin), level); +} +//------------------------------------------------------------------------------ +/** Write the DDR register. + * @param[in] pin Arduino pin number + * @param[in] level value to write + */ +static inline __attribute__((always_inline)) +void fastDdrWrite(uint8_t pin, bool level) { + fastBitWriteSafe(ddrReg(pin), pinMask(pin), level); +} +//------------------------------------------------------------------------------ +/** Set pin mode. + * @param[in] pin Arduino pin number + * @param[in] mode INPUT, OUTPUT, or INPUT_PULLUP. + * + * The internal pullup resistors will be enabled if mode is INPUT_PULLUP + * and disabled if the mode is INPUT. + */ +static inline __attribute__((always_inline)) +void fastPinMode(uint8_t pin, uint8_t mode) { + fastDdrWrite(pin, mode == OUTPUT); + if (mode != OUTPUT) { + fastDigitalWrite(pin, mode == INPUT_PULLUP); + } +} +#else // defined(__AVR__) +#if defined(CORE_TEENSY) +//------------------------------------------------------------------------------ +/** read pin value + * @param[in] pin Arduino pin number + * @return value read + */ +static inline __attribute__((always_inline)) +bool fastDigitalRead(uint8_t pin) { + return *portInputRegister(pin); +} +//------------------------------------------------------------------------------ +/** Set pin value + * @param[in] pin Arduino pin number + * @param[in] level value to write + */ +static inline __attribute__((always_inline)) +void fastDigitalWrite(uint8_t pin, bool value) { + if (value) { + *portSetRegister(pin) = 1; + } else { + *portClearRegister(pin) = 1; + } +} +#elif defined(__SAM3X8E__) || defined(__SAM3X8H__) +//------------------------------------------------------------------------------ +/** read pin value + * @param[in] pin Arduino pin number + * @return value read + */ +static inline __attribute__((always_inline)) +bool fastDigitalRead(uint8_t pin) { + return g_APinDescription[pin].pPort->PIO_PDSR & g_APinDescription[pin].ulPin; +} +//------------------------------------------------------------------------------ +/** Set pin value + * @param[in] pin Arduino pin number + * @param[in] level value to write + */ +static inline __attribute__((always_inline)) +void fastDigitalWrite(uint8_t pin, bool value) { + if (value) { + g_APinDescription[pin].pPort->PIO_SODR = g_APinDescription[pin].ulPin; + } else { + g_APinDescription[pin].pPort->PIO_CODR = g_APinDescription[pin].ulPin; + } +} +#elif defined(ESP8266) +//------------------------------------------------------------------------------ +/** Set pin value + * @param[in] pin Arduino pin number + * @param[in] val value to write + */ +static inline __attribute__((always_inline)) +void fastDigitalWrite(uint8_t pin, uint8_t val) { + if (pin < 16) { + if (val) { + GPOS = (1 << pin); + } else { + GPOC = (1 << pin); + } + } else if (pin == 16) { + if (val) { + GP16O |= 1; + } else { + GP16O &= ~1; + } + } +} +//------------------------------------------------------------------------------ +/** Read pin value + * @param[in] pin Arduino pin number + * @return value read + */ +static inline __attribute__((always_inline)) +bool fastDigitalRead(uint8_t pin) { + if (pin < 16) { + return GPIP(pin); + } else if (pin == 16) { + return GP16I & 0x01; + } + return 0; +} +#else // CORE_TEENSY +//------------------------------------------------------------------------------ +inline void fastDigitalWrite(uint8_t pin, bool value) { + digitalWrite(pin, value); +} +//------------------------------------------------------------------------------ +inline bool fastDigitalRead(uint8_t pin) { + return digitalRead(pin); +} +#endif // CORE_TEENSY +//------------------------------------------------------------------------------ +inline void fastDigitalToggle(uint8_t pin) { + fastDigitalWrite(pin, !fastDigitalRead(pin)); +} +//------------------------------------------------------------------------------ +inline void fastPinMode(uint8_t pin, uint8_t mode) { + pinMode(pin, mode); +} +#endif // __AVR__ +//------------------------------------------------------------------------------ +/** set pin configuration + * @param[in] pin Arduino pin number + * @param[in] mode mode INPUT or OUTPUT. + * @param[in] level If mode is output, set level high/low. + * If mode is input, enable or disable the pin's 20K pullup. + */ +#define fastPinConfig(pin, mode, level)\ + {fastPinMode(pin, mode); fastDigitalWrite(pin, level);} +//============================================================================== +/** + * @class DigitalPin + * @brief Fast digital port I/O + */ +template +class DigitalPin { + public: + //---------------------------------------------------------------------------- + /** Constructor */ + DigitalPin() {} + //---------------------------------------------------------------------------- + /** Asignment operator. + * @param[in] value If true set the pin's level high else set the + * pin's level low. + * + * @return This DigitalPin instance. + */ + inline DigitalPin & operator = (bool value) __attribute__((always_inline)) { + write(value); + return *this; + } + //---------------------------------------------------------------------------- + /** Parenthesis operator. + * @return Pin's level + */ + inline operator bool () const __attribute__((always_inline)) { + return read(); + } + //---------------------------------------------------------------------------- + /** Set pin configuration. + * @param[in] mode: INPUT or OUTPUT. + * @param[in] level If mode is OUTPUT, set level high/low. + * If mode is INPUT, enable or disable the pin's 20K pullup. + */ + inline __attribute__((always_inline)) + void config(uint8_t mode, bool level) { + fastPinConfig(PinNumber, mode, level); + } + //---------------------------------------------------------------------------- + /** + * Set pin level high if output mode or enable 20K pullup if input mode. + */ + inline __attribute__((always_inline)) + void high() {write(true);} + //---------------------------------------------------------------------------- + /** + * Set pin level low if output mode or disable 20K pullup if input mode. + */ + inline __attribute__((always_inline)) + void low() {write(false);} + //---------------------------------------------------------------------------- + /** + * Set pin mode. + * @param[in] mode: INPUT, OUTPUT, or INPUT_PULLUP. + * + * The internal pullup resistors will be enabled if mode is INPUT_PULLUP + * and disabled if the mode is INPUT. + */ + inline __attribute__((always_inline)) + void mode(uint8_t mode) { + fastPinMode(PinNumber, mode); + } + //---------------------------------------------------------------------------- + /** @return Pin's level. */ + inline __attribute__((always_inline)) + bool read() const { + return fastDigitalRead(PinNumber); + } + //---------------------------------------------------------------------------- + /** Toggle a pin. + * + * If the pin is in output mode toggle the pin's level. + * If the pin is in input mode toggle the state of the 20K pullup. + */ + inline __attribute__((always_inline)) + void toggle() { + fastDigitalToggle(PinNumber); + } + //---------------------------------------------------------------------------- + /** Write the pin's level. + * @param[in] value If true set the pin's level high else set the + * pin's level low. + */ + inline __attribute__((always_inline)) + void write(bool value) { + fastDigitalWrite(PinNumber, value); + } +}; +#endif // DigitalPin_h +/** @} */ diff --git a/Firmware_V3/lib/SdFat/src/DigitalIO/SoftSPI.h b/Firmware_V3/lib/SdFat/src/DigitalIO/SoftSPI.h new file mode 100644 index 0000000..6dd58df --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/DigitalIO/SoftSPI.h @@ -0,0 +1,161 @@ +/* Arduino DigitalIO Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the Arduino DigitalIO Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino DigitalIO Library. If not, see + * . + */ +/** + * @file + * @brief Software SPI. + * + * @defgroup softSPI Software SPI + * @details Software SPI Template Class. + * @{ + */ +#ifndef SoftSPI_h +#define SoftSPI_h +#include "DigitalPin.h" +//------------------------------------------------------------------------------ +/** Nop for timing. */ +#define nop asm volatile ("nop\n\t") +//------------------------------------------------------------------------------ +/** Pin Mode for MISO is input.*/ +#define MISO_MODE INPUT +/** Pullups disabled for MISO are disabled. */ +#define MISO_LEVEL false +/** Pin Mode for MOSI is output.*/ +#define MOSI_MODE OUTPUT +/** Pin Mode for SCK is output. */ +#define SCK_MODE OUTPUT +//------------------------------------------------------------------------------ +/** + * @class SoftSPI + * @brief Fast software SPI. + */ +template +class SoftSPI { + public: + //---------------------------------------------------------------------------- + /** Initialize SoftSPI pins. */ + void begin() { + fastPinConfig(MisoPin, MISO_MODE, MISO_LEVEL); + fastPinConfig(MosiPin, MOSI_MODE, !MODE_CPHA(Mode)); + fastPinConfig(SckPin, SCK_MODE, MODE_CPOL(Mode)); + } + //---------------------------------------------------------------------------- + /** Soft SPI receive byte. + * @return Data byte received. + */ + inline __attribute__((always_inline)) + uint8_t receive() { + uint8_t data = 0; + receiveBit(7, &data); + receiveBit(6, &data); + receiveBit(5, &data); + receiveBit(4, &data); + receiveBit(3, &data); + receiveBit(2, &data); + receiveBit(1, &data); + receiveBit(0, &data); + return data; + } + //---------------------------------------------------------------------------- + /** Soft SPI send byte. + * @param[in] data Data byte to send. + */ + inline __attribute__((always_inline)) + void send(uint8_t data) { + sendBit(7, data); + sendBit(6, data); + sendBit(5, data); + sendBit(4, data); + sendBit(3, data); + sendBit(2, data); + sendBit(1, data); + sendBit(0, data); + } + //---------------------------------------------------------------------------- + /** Soft SPI transfer byte. + * @param[in] txData Data byte to send. + * @return Data byte received. + */ + inline __attribute__((always_inline)) + uint8_t transfer(uint8_t txData) { + uint8_t rxData = 0; + transferBit(7, &rxData, txData); + transferBit(6, &rxData, txData); + transferBit(5, &rxData, txData); + transferBit(4, &rxData, txData); + transferBit(3, &rxData, txData); + transferBit(2, &rxData, txData); + transferBit(1, &rxData, txData); + transferBit(0, &rxData, txData); + return rxData; + } + + private: + //---------------------------------------------------------------------------- + inline __attribute__((always_inline)) + bool MODE_CPHA(uint8_t mode) {return (mode & 1) != 0;} + inline __attribute__((always_inline)) + bool MODE_CPOL(uint8_t mode) {return (mode & 2) != 0;} + inline __attribute__((always_inline)) + void receiveBit(uint8_t bit, uint8_t* data) { + if (MODE_CPHA(Mode)) { + fastDigitalWrite(SckPin, !MODE_CPOL(Mode)); + } + nop; + nop; + fastDigitalWrite(SckPin, + MODE_CPHA(Mode) ? MODE_CPOL(Mode) : !MODE_CPOL(Mode)); + if (fastDigitalRead(MisoPin)) *data |= 1 << bit; + if (!MODE_CPHA(Mode)) { + fastDigitalWrite(SckPin, MODE_CPOL(Mode)); + } + } + //---------------------------------------------------------------------------- + inline __attribute__((always_inline)) + void sendBit(uint8_t bit, uint8_t data) { + if (MODE_CPHA(Mode)) { + fastDigitalWrite(SckPin, !MODE_CPOL(Mode)); + } + fastDigitalWrite(MosiPin, data & (1 << bit)); + fastDigitalWrite(SckPin, + MODE_CPHA(Mode) ? MODE_CPOL(Mode) : !MODE_CPOL(Mode)); + nop; + nop; + if (!MODE_CPHA(Mode)) { + fastDigitalWrite(SckPin, MODE_CPOL(Mode)); + } + } + //---------------------------------------------------------------------------- + inline __attribute__((always_inline)) + void transferBit(uint8_t bit, uint8_t* rxData, uint8_t txData) { + if (MODE_CPHA(Mode)) { + fastDigitalWrite(SckPin, !MODE_CPOL(Mode)); + } + fastDigitalWrite(MosiPin, txData & (1 << bit)); + fastDigitalWrite(SckPin, + MODE_CPHA(Mode) ? MODE_CPOL(Mode) : !MODE_CPOL(Mode)); + if (fastDigitalRead(MisoPin)) *rxData |= 1 << bit; + if (!MODE_CPHA(Mode)) { + fastDigitalWrite(SckPin, MODE_CPOL(Mode)); + } + } + //---------------------------------------------------------------------------- +}; +#endif // SoftSPI_h +/** @} */ diff --git a/Firmware_V3/lib/SdFat/src/DigitalIO/boards/AvrDevelopersGpioPinMap.h b/Firmware_V3/lib/SdFat/src/DigitalIO/boards/AvrDevelopersGpioPinMap.h new file mode 100644 index 0000000..67a8ec2 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/DigitalIO/boards/AvrDevelopersGpioPinMap.h @@ -0,0 +1,37 @@ +#ifndef AvrDevelopersGpioPinMap_h +#define AvrDevelopersGpioPinMap_h +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(B, 0), // D0 + GPIO_PIN(B, 1), // D1 + GPIO_PIN(B, 2), // D2 + GPIO_PIN(B, 3), // D3 + GPIO_PIN(B, 4), // D4 + GPIO_PIN(B, 5), // D5 + GPIO_PIN(B, 6), // D6 + GPIO_PIN(B, 7), // D7 + GPIO_PIN(D, 0), // D8 + GPIO_PIN(D, 1), // D9 + GPIO_PIN(D, 2), // D10 + GPIO_PIN(D, 3), // D11 + GPIO_PIN(D, 4), // D12 + GPIO_PIN(D, 5), // D13 + GPIO_PIN(D, 6), // D14 + GPIO_PIN(D, 7), // D15 + GPIO_PIN(C, 0), // D16 + GPIO_PIN(C, 1), // D17 + GPIO_PIN(C, 2), // D18 + GPIO_PIN(C, 3), // D19 + GPIO_PIN(C, 4), // D20 + GPIO_PIN(C, 5), // D21 + GPIO_PIN(C, 6), // D22 + GPIO_PIN(C, 7), // D23 + GPIO_PIN(A, 7), // D24 + GPIO_PIN(A, 6), // D25 + GPIO_PIN(A, 5), // D26 + GPIO_PIN(A, 4), // D27 + GPIO_PIN(A, 3), // D28 + GPIO_PIN(A, 2), // D29 + GPIO_PIN(A, 1), // D30 + GPIO_PIN(A, 0) // D31 +}; +#endif // AvrDevelopersGpioPinMap_h \ No newline at end of file diff --git a/Firmware_V3/lib/SdFat/src/DigitalIO/boards/BobuinoGpioPinMap.h b/Firmware_V3/lib/SdFat/src/DigitalIO/boards/BobuinoGpioPinMap.h new file mode 100644 index 0000000..2d19944 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/DigitalIO/boards/BobuinoGpioPinMap.h @@ -0,0 +1,37 @@ +#ifndef BobuinoGpioPinMap_h +#define BobuinoGpioPinMap_h +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(B, 0), // D0 + GPIO_PIN(B, 1), // D1 + GPIO_PIN(B, 2), // D2 + GPIO_PIN(B, 3), // D3 + GPIO_PIN(B, 4), // D4 + GPIO_PIN(B, 5), // D5 + GPIO_PIN(B, 6), // D6 + GPIO_PIN(B, 7), // D7 + GPIO_PIN(D, 0), // D8 + GPIO_PIN(D, 1), // D9 + GPIO_PIN(D, 2), // D10 + GPIO_PIN(D, 3), // D11 + GPIO_PIN(D, 4), // D12 + GPIO_PIN(D, 5), // D13 + GPIO_PIN(D, 6), // D14 + GPIO_PIN(D, 7), // D15 + GPIO_PIN(C, 0), // D16 + GPIO_PIN(C, 1), // D17 + GPIO_PIN(C, 2), // D18 + GPIO_PIN(C, 3), // D19 + GPIO_PIN(C, 4), // D20 + GPIO_PIN(C, 5), // D21 + GPIO_PIN(C, 6), // D22 + GPIO_PIN(C, 7), // D23 + GPIO_PIN(A, 0), // D24 + GPIO_PIN(A, 1), // D25 + GPIO_PIN(A, 2), // D26 + GPIO_PIN(A, 3), // D27 + GPIO_PIN(A, 4), // D28 + GPIO_PIN(A, 5), // D29 + GPIO_PIN(A, 6), // D30 + GPIO_PIN(A, 7) // D31 +}; +#endif // BobuinoGpioPinMap_h \ No newline at end of file diff --git a/Firmware_V3/lib/SdFat/src/DigitalIO/boards/GpioPinMap.h b/Firmware_V3/lib/SdFat/src/DigitalIO/boards/GpioPinMap.h new file mode 100644 index 0000000..901ad3e --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/DigitalIO/boards/GpioPinMap.h @@ -0,0 +1,64 @@ +/* Arduino DigitalIO Library + * Copyright (C) 2013 by William Greiman + * + * This file is part of the Arduino DigitalIO Library + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino DigitalIO Library. If not, see + * . + */ +#ifndef GpioPinMap_h +#define GpioPinMap_h +#if defined(__AVR_ATmega168__)\ +||defined(__AVR_ATmega168P__)\ +||defined(__AVR_ATmega328P__) +// 168 and 328 Arduinos +#include "UnoGpioPinMap.h" +#elif defined(__AVR_ATmega1280__)\ +|| defined(__AVR_ATmega2560__) +// Mega ADK +#include "MegaGpioPinMap.h" +#elif defined(__AVR_ATmega32U4__) +#ifdef CORE_TEENSY +#include "Teensy2GpioPinMap.h" +#else // CORE_TEENSY +// Leonardo or Yun +#include "LeonardoGpioPinMap.h" +#endif // CORE_TEENSY +#elif defined(__AVR_AT90USB646__)\ +|| defined(__AVR_AT90USB1286__) +// Teensy++ 1.0 & 2.0 +#include "Teensy2ppGpioPinMap.h" +#elif defined(__AVR_ATmega1284P__)\ +|| defined(__AVR_ATmega1284__)\ +|| defined(__AVR_ATmega644P__)\ +|| defined(__AVR_ATmega644__)\ +|| defined(__AVR_ATmega64__)\ +|| defined(__AVR_ATmega32__)\ +|| defined(__AVR_ATmega324__)\ +|| defined(__AVR_ATmega16__) +#ifdef ARDUINO_1284P_AVR_DEVELOPERS +#include "AvrDevelopersGpioPinMap.h" +#elif defined(ARDUINO_1284P_BOBUINO) +#include "BobuinoGpioPinMap.h" +#elif defined(ARDUINO_1284P_SLEEPINGBEAUTY) +#include "SleepingBeautyGpioPinMap.h" +#elif defined(ARDUINO_1284P_STANDARD) +#include "Standard1284GpioPinMap.h" +#else // ARDUINO_1284P_SLEEPINGBEAUTY +#error Undefined variant 1284, 644, 324 +#endif // ARDUINO_1284P_SLEEPINGBEAUTY +#else // 1284P, 1284, 644 +#error Unknown board type. +#endif // end all boards +#endif // GpioPinMap_h diff --git a/Firmware_V3/lib/SdFat/src/DigitalIO/boards/LeonardoGpioPinMap.h b/Firmware_V3/lib/SdFat/src/DigitalIO/boards/LeonardoGpioPinMap.h new file mode 100644 index 0000000..73544e6 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/DigitalIO/boards/LeonardoGpioPinMap.h @@ -0,0 +1,35 @@ +#ifndef LeonardoGpioPinMap_h +#define LeonardoGpioPinMap_h +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(D, 2), // D0 + GPIO_PIN(D, 3), // D1 + GPIO_PIN(D, 1), // D2 + GPIO_PIN(D, 0), // D3 + GPIO_PIN(D, 4), // D4 + GPIO_PIN(C, 6), // D5 + GPIO_PIN(D, 7), // D6 + GPIO_PIN(E, 6), // D7 + GPIO_PIN(B, 4), // D8 + GPIO_PIN(B, 5), // D9 + GPIO_PIN(B, 6), // D10 + GPIO_PIN(B, 7), // D11 + GPIO_PIN(D, 6), // D12 + GPIO_PIN(C, 7), // D13 + GPIO_PIN(B, 3), // D14 + GPIO_PIN(B, 1), // D15 + GPIO_PIN(B, 2), // D16 + GPIO_PIN(B, 0), // D17 + GPIO_PIN(F, 7), // D18 + GPIO_PIN(F, 6), // D19 + GPIO_PIN(F, 5), // D20 + GPIO_PIN(F, 4), // D21 + GPIO_PIN(F, 1), // D22 + GPIO_PIN(F, 0), // D23 + GPIO_PIN(D, 4), // D24 + GPIO_PIN(D, 7), // D25 + GPIO_PIN(B, 4), // D26 + GPIO_PIN(B, 5), // D27 + GPIO_PIN(B, 6), // D28 + GPIO_PIN(D, 6) // D29 +}; +#endif // LeonardoGpioPinMap_h diff --git a/Firmware_V3/lib/SdFat/src/DigitalIO/boards/MegaGpioPinMap.h b/Firmware_V3/lib/SdFat/src/DigitalIO/boards/MegaGpioPinMap.h new file mode 100644 index 0000000..c041343 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/DigitalIO/boards/MegaGpioPinMap.h @@ -0,0 +1,75 @@ +#ifndef MegaGpioPinMap_h +#define MegaGpioPinMap_h +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(E, 0), // D0 + GPIO_PIN(E, 1), // D1 + GPIO_PIN(E, 4), // D2 + GPIO_PIN(E, 5), // D3 + GPIO_PIN(G, 5), // D4 + GPIO_PIN(E, 3), // D5 + GPIO_PIN(H, 3), // D6 + GPIO_PIN(H, 4), // D7 + GPIO_PIN(H, 5), // D8 + GPIO_PIN(H, 6), // D9 + GPIO_PIN(B, 4), // D10 + GPIO_PIN(B, 5), // D11 + GPIO_PIN(B, 6), // D12 + GPIO_PIN(B, 7), // D13 + GPIO_PIN(J, 1), // D14 + GPIO_PIN(J, 0), // D15 + GPIO_PIN(H, 1), // D16 + GPIO_PIN(H, 0), // D17 + GPIO_PIN(D, 3), // D18 + GPIO_PIN(D, 2), // D19 + GPIO_PIN(D, 1), // D20 + GPIO_PIN(D, 0), // D21 + GPIO_PIN(A, 0), // D22 + GPIO_PIN(A, 1), // D23 + GPIO_PIN(A, 2), // D24 + GPIO_PIN(A, 3), // D25 + GPIO_PIN(A, 4), // D26 + GPIO_PIN(A, 5), // D27 + GPIO_PIN(A, 6), // D28 + GPIO_PIN(A, 7), // D29 + GPIO_PIN(C, 7), // D30 + GPIO_PIN(C, 6), // D31 + GPIO_PIN(C, 5), // D32 + GPIO_PIN(C, 4), // D33 + GPIO_PIN(C, 3), // D34 + GPIO_PIN(C, 2), // D35 + GPIO_PIN(C, 1), // D36 + GPIO_PIN(C, 0), // D37 + GPIO_PIN(D, 7), // D38 + GPIO_PIN(G, 2), // D39 + GPIO_PIN(G, 1), // D40 + GPIO_PIN(G, 0), // D41 + GPIO_PIN(L, 7), // D42 + GPIO_PIN(L, 6), // D43 + GPIO_PIN(L, 5), // D44 + GPIO_PIN(L, 4), // D45 + GPIO_PIN(L, 3), // D46 + GPIO_PIN(L, 2), // D47 + GPIO_PIN(L, 1), // D48 + GPIO_PIN(L, 0), // D49 + GPIO_PIN(B, 3), // D50 + GPIO_PIN(B, 2), // D51 + GPIO_PIN(B, 1), // D52 + GPIO_PIN(B, 0), // D53 + GPIO_PIN(F, 0), // D54 + GPIO_PIN(F, 1), // D55 + GPIO_PIN(F, 2), // D56 + GPIO_PIN(F, 3), // D57 + GPIO_PIN(F, 4), // D58 + GPIO_PIN(F, 5), // D59 + GPIO_PIN(F, 6), // D60 + GPIO_PIN(F, 7), // D61 + GPIO_PIN(K, 0), // D62 + GPIO_PIN(K, 1), // D63 + GPIO_PIN(K, 2), // D64 + GPIO_PIN(K, 3), // D65 + GPIO_PIN(K, 4), // D66 + GPIO_PIN(K, 5), // D67 + GPIO_PIN(K, 6), // D68 + GPIO_PIN(K, 7) // D69 +}; +#endif // MegaGpioPinMap_h diff --git a/Firmware_V3/lib/SdFat/src/DigitalIO/boards/SleepingBeautyGpioPinMap.h b/Firmware_V3/lib/SdFat/src/DigitalIO/boards/SleepingBeautyGpioPinMap.h new file mode 100644 index 0000000..bf040d9 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/DigitalIO/boards/SleepingBeautyGpioPinMap.h @@ -0,0 +1,37 @@ +#ifndef SleepingBeautyGpioPinMap_h +#define SleepingBeautyGpioPinMap_h +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(D, 0), // D0 + GPIO_PIN(D, 1), // D1 + GPIO_PIN(D, 2), // D2 + GPIO_PIN(D, 3), // D3 + GPIO_PIN(B, 0), // D4 + GPIO_PIN(B, 1), // D5 + GPIO_PIN(B, 2), // D6 + GPIO_PIN(B, 3), // D7 + GPIO_PIN(D, 6), // D8 + GPIO_PIN(D, 5), // D9 + GPIO_PIN(B, 4), // D10 + GPIO_PIN(B, 5), // D11 + GPIO_PIN(B, 6), // D12 + GPIO_PIN(B, 7), // D13 + GPIO_PIN(C, 7), // D14 + GPIO_PIN(C, 6), // D15 + GPIO_PIN(A, 5), // D16 + GPIO_PIN(A, 4), // D17 + GPIO_PIN(A, 3), // D18 + GPIO_PIN(A, 2), // D19 + GPIO_PIN(A, 1), // D20 + GPIO_PIN(A, 0), // D21 + GPIO_PIN(D, 4), // D22 + GPIO_PIN(D, 7), // D23 + GPIO_PIN(C, 2), // D24 + GPIO_PIN(C, 3), // D25 + GPIO_PIN(C, 4), // D26 + GPIO_PIN(C, 5), // D27 + GPIO_PIN(C, 1), // D28 + GPIO_PIN(C, 0), // D29 + GPIO_PIN(A, 6), // D30 + GPIO_PIN(A, 7) // D31 +}; +#endif // SleepingBeautyGpioPinMap_h \ No newline at end of file diff --git a/Firmware_V3/lib/SdFat/src/DigitalIO/boards/Standard1284GpioPinMap.h b/Firmware_V3/lib/SdFat/src/DigitalIO/boards/Standard1284GpioPinMap.h new file mode 100644 index 0000000..d38ff0c --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/DigitalIO/boards/Standard1284GpioPinMap.h @@ -0,0 +1,37 @@ +#ifndef Standard1284GpioPinMap_h +#define Standard1284GpioPinMap_h +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(B, 0), // D0 + GPIO_PIN(B, 1), // D1 + GPIO_PIN(B, 2), // D2 + GPIO_PIN(B, 3), // D3 + GPIO_PIN(B, 4), // D4 + GPIO_PIN(B, 5), // D5 + GPIO_PIN(B, 6), // D6 + GPIO_PIN(B, 7), // D7 + GPIO_PIN(D, 0), // D8 + GPIO_PIN(D, 1), // D9 + GPIO_PIN(D, 2), // D10 + GPIO_PIN(D, 3), // D11 + GPIO_PIN(D, 4), // D12 + GPIO_PIN(D, 5), // D13 + GPIO_PIN(D, 6), // D14 + GPIO_PIN(D, 7), // D15 + GPIO_PIN(C, 0), // D16 + GPIO_PIN(C, 1), // D17 + GPIO_PIN(C, 2), // D18 + GPIO_PIN(C, 3), // D19 + GPIO_PIN(C, 4), // D20 + GPIO_PIN(C, 5), // D21 + GPIO_PIN(C, 6), // D22 + GPIO_PIN(C, 7), // D23 + GPIO_PIN(A, 0), // D24 + GPIO_PIN(A, 1), // D25 + GPIO_PIN(A, 2), // D26 + GPIO_PIN(A, 3), // D27 + GPIO_PIN(A, 4), // D28 + GPIO_PIN(A, 5), // D29 + GPIO_PIN(A, 6), // D30 + GPIO_PIN(A, 7) // D31 +}; +#endif // Standard1284GpioPinMap_h \ No newline at end of file diff --git a/Firmware_V3/lib/SdFat/src/DigitalIO/boards/Teensy2GpioPinMap.h b/Firmware_V3/lib/SdFat/src/DigitalIO/boards/Teensy2GpioPinMap.h new file mode 100644 index 0000000..00aa437 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/DigitalIO/boards/Teensy2GpioPinMap.h @@ -0,0 +1,30 @@ +#ifndef Teensy2GpioPinMap_h +#define Teensy2GpioPinMap_h +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(B, 0), // D0 + GPIO_PIN(B, 1), // D1 + GPIO_PIN(B, 2), // D2 + GPIO_PIN(B, 3), // D3 + GPIO_PIN(B, 7), // D4 + GPIO_PIN(D, 0), // D5 + GPIO_PIN(D, 1), // D6 + GPIO_PIN(D, 2), // D7 + GPIO_PIN(D, 3), // D8 + GPIO_PIN(C, 6), // D9 + GPIO_PIN(C, 7), // D10 + GPIO_PIN(D, 6), // D11 + GPIO_PIN(D, 7), // D12 + GPIO_PIN(B, 4), // D13 + GPIO_PIN(B, 5), // D14 + GPIO_PIN(B, 6), // D15 + GPIO_PIN(F, 7), // D16 + GPIO_PIN(F, 6), // D17 + GPIO_PIN(F, 5), // D18 + GPIO_PIN(F, 4), // D19 + GPIO_PIN(F, 1), // D20 + GPIO_PIN(F, 0), // D21 + GPIO_PIN(D, 4), // D22 + GPIO_PIN(D, 5), // D23 + GPIO_PIN(E, 6), // D24 +}; +#endif // Teensy2GpioPinMap_h diff --git a/Firmware_V3/lib/SdFat/src/DigitalIO/boards/Teensy2ppGpioPinMap.h b/Firmware_V3/lib/SdFat/src/DigitalIO/boards/Teensy2ppGpioPinMap.h new file mode 100644 index 0000000..68c51d7 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/DigitalIO/boards/Teensy2ppGpioPinMap.h @@ -0,0 +1,51 @@ +#ifndef Teensypp2GpioPinMap_h +#define Teensypp2GpioPinMap_h +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(D, 0), // D0 + GPIO_PIN(D, 1), // D1 + GPIO_PIN(D, 2), // D2 + GPIO_PIN(D, 3), // D3 + GPIO_PIN(D, 4), // D4 + GPIO_PIN(D, 5), // D5 + GPIO_PIN(D, 6), // D6 + GPIO_PIN(D, 7), // D7 + GPIO_PIN(E, 0), // D8 + GPIO_PIN(E, 1), // D9 + GPIO_PIN(C, 0), // D10 + GPIO_PIN(C, 1), // D11 + GPIO_PIN(C, 2), // D12 + GPIO_PIN(C, 3), // D13 + GPIO_PIN(C, 4), // D14 + GPIO_PIN(C, 5), // D15 + GPIO_PIN(C, 6), // D16 + GPIO_PIN(C, 7), // D17 + GPIO_PIN(E, 6), // D18 + GPIO_PIN(E, 7), // D19 + GPIO_PIN(B, 0), // D20 + GPIO_PIN(B, 1), // D21 + GPIO_PIN(B, 2), // D22 + GPIO_PIN(B, 3), // D23 + GPIO_PIN(B, 4), // D24 + GPIO_PIN(B, 5), // D25 + GPIO_PIN(B, 6), // D26 + GPIO_PIN(B, 7), // D27 + GPIO_PIN(A, 0), // D28 + GPIO_PIN(A, 1), // D29 + GPIO_PIN(A, 2), // D30 + GPIO_PIN(A, 3), // D31 + GPIO_PIN(A, 4), // D32 + GPIO_PIN(A, 5), // D33 + GPIO_PIN(A, 6), // D34 + GPIO_PIN(A, 7), // D35 + GPIO_PIN(E, 4), // D36 + GPIO_PIN(E, 5), // D37 + GPIO_PIN(F, 0), // D38 + GPIO_PIN(F, 1), // D39 + GPIO_PIN(F, 2), // D40 + GPIO_PIN(F, 3), // D41 + GPIO_PIN(F, 4), // D42 + GPIO_PIN(F, 5), // D43 + GPIO_PIN(F, 6), // D44 + GPIO_PIN(F, 7), // D45 +}; +#endif // Teensypp2GpioPinMap_h diff --git a/Firmware_V3/lib/SdFat/src/DigitalIO/boards/UnoGpioPinMap.h b/Firmware_V3/lib/SdFat/src/DigitalIO/boards/UnoGpioPinMap.h new file mode 100644 index 0000000..21ec75d --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/DigitalIO/boards/UnoGpioPinMap.h @@ -0,0 +1,25 @@ +#ifndef UnoGpioPinMap_h +#define UnoGpioPinMap_h +static const GpioPinMap_t GpioPinMap[] = { + GPIO_PIN(D, 0), // D0 + GPIO_PIN(D, 1), // D1 + GPIO_PIN(D, 2), // D2 + GPIO_PIN(D, 3), // D3 + GPIO_PIN(D, 4), // D4 + GPIO_PIN(D, 5), // D5 + GPIO_PIN(D, 6), // D6 + GPIO_PIN(D, 7), // D7 + GPIO_PIN(B, 0), // D8 + GPIO_PIN(B, 1), // D9 + GPIO_PIN(B, 2), // D10 + GPIO_PIN(B, 3), // D11 + GPIO_PIN(B, 4), // D12 + GPIO_PIN(B, 5), // D13 + GPIO_PIN(C, 0), // D14 + GPIO_PIN(C, 1), // D15 + GPIO_PIN(C, 2), // D16 + GPIO_PIN(C, 3), // D17 + GPIO_PIN(C, 4), // D18 + GPIO_PIN(C, 5) // D19 +}; +#endif // UnoGpioPinMap_h \ No newline at end of file diff --git a/Firmware_V3/lib/SdFat/src/DigitalIO/readme.txt b/Firmware_V3/lib/SdFat/src/DigitalIO/readme.txt new file mode 100644 index 0000000..8976c65 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/DigitalIO/readme.txt @@ -0,0 +1,3 @@ +Selected files from the DigitalIO library. + +https://github.com/greiman/DigitalIO \ No newline at end of file diff --git a/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatConfig.h b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatConfig.h new file mode 100644 index 0000000..08ffd18 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatConfig.h @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef ExFatConfig_h +#define ExFatConfig_h +#include "../SdFatConfig.h" +#ifndef USE_EXFAT_UNICODE_NAMES +#define USE_EXFAT_UNICODE_NAMES 0 +#endif // USE_EXFAT_UNICODE_NAMES +#ifndef READ_ONLY +#define READ_ONLY 0 +#endif // READ_ONLY +#endif // ExFatConfig_h diff --git a/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatDbg.cpp b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatDbg.cpp new file mode 100644 index 0000000..658af29 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatDbg.cpp @@ -0,0 +1,601 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "ExFatVolume.h" +#include "upcase.h" +#include "ExFatFile.h" +#ifndef DOXYGEN_SHOULD_SKIP_THIS +//------------------------------------------------------------------------------ +static void printHex(print_t* pr, uint8_t h); +static void printHex(print_t* pr, uint16_t val); +static void printHex(print_t* pr, uint32_t val); +static void printHex64(print_t* pr, uint64_t n); +static void println64(print_t* pr, uint64_t n); +//------------------------------------------------------------------------------ +static void dmpDirData(print_t* pr, DirGeneric_t* dir) { + for (uint8_t k = 0; k < 31; k++) { + if (k) { + pr->write(' '); + } + printHex(pr, dir->data[k]); + } + pr->println(); +} +//------------------------------------------------------------------------------ +static uint16_t exFatDirChecksum(const void* dir, uint16_t checksum) { + const uint8_t* data = reinterpret_cast(dir); + bool skip = data[0] == EXFAT_TYPE_FILE; + for (size_t i = 0; i < 32; i += (i == 1 && skip ? 3 : 1)) { + checksum = ((checksum << 15) | (checksum >> 1)) + data[i]; + } + return checksum; +} +//------------------------------------------------------------------------------ +static void printDateTime(print_t* pr, + uint32_t timeDate, uint8_t ms, int8_t tz) { + fsPrintDateTime(pr, timeDate, ms, tz); + pr->println(); +} +//------------------------------------------------------------------------------ +static void printDirBitmap(print_t* pr, DirBitmap_t* dir) { + pr->print(F("dirBitmap: 0x")); + pr->println(dir->type, HEX); + pr->print(F("flags: 0x")); + pr->println(dir->flags, HEX); + pr->print(F("firstCluster: ")); + pr->println(getLe32(dir->firstCluster)); + pr->print(F("size: ")); + println64(pr, getLe64(dir->size)); +} +//------------------------------------------------------------------------------ +static void printDirFile(print_t* pr, DirFile_t* dir) { + pr->print(F("dirFile: 0x")); + pr->println(dir->type, HEX); + pr->print(F("setCount: ")); + pr->println(dir->setCount); + pr->print(F("setChecksum: 0x")); + pr->println(getLe16(dir->setChecksum), HEX); + pr->print(F("attributes: 0x")); + pr->println(getLe16(dir->attributes), HEX); + pr->print(F("createTime: ")); + printDateTime(pr, getLe32(dir->createTime), + dir->createTimeMs, dir->createTimezone); + pr->print(F("modifyTime: ")); + printDateTime(pr, getLe32(dir->modifyTime), + dir->modifyTimeMs, dir->modifyTimezone); + pr->print(F("accessTime: ")); + printDateTime(pr, getLe32(dir->accessTime), 0, dir->accessTimezone); +} +//------------------------------------------------------------------------------ +static void printDirLabel(print_t* pr, DirLabel_t* dir) { + pr->print(F("dirLabel: 0x")); + pr->println(dir->type, HEX); + pr->print(F("labelLength: ")); + pr->println(dir->labelLength); + pr->print(F("unicode: ")); + for (size_t i = 0; i < dir->labelLength; i++) { + pr->write(dir->unicode[2*i]); + } + pr->println(); +} +//------------------------------------------------------------------------------ +static void printDirName(print_t* pr, DirName_t* dir) { + pr->print(F("dirName: 0x")); + pr->println(dir->type, HEX); + pr->print(F("unicode: ")); + for (size_t i = 0; i < 30; i += 2) { + if (dir->unicode[i] == 0) break; + pr->write(dir->unicode[i]); + } + pr->println(); +} +//------------------------------------------------------------------------------ +static void printDirStream(print_t* pr, DirStream_t* dir) { + pr->print(F("dirStream: 0x")); + pr->println(dir->type, HEX); + pr->print(F("flags: 0x")); + pr->println(dir->flags, HEX); + pr->print(F("nameLength: ")); + pr->println(dir->nameLength); + pr->print(F("nameHash: 0x")); + pr->println(getLe16(dir->nameHash), HEX); + pr->print(F("validLength: ")); + println64(pr, getLe64(dir->validLength)); + pr->print(F("firstCluster: ")); + pr->println(getLe32(dir->firstCluster)); + pr->print(F("dataLength: ")); + println64(pr, getLe64(dir->dataLength)); +} +//------------------------------------------------------------------------------ +static void printDirUpcase(print_t* pr, DirUpcase_t* dir) { + pr->print(F("dirUpcase: 0x")); + pr->println(dir->type, HEX); + pr->print(F("checksum: 0x")); + pr->println(getLe32(dir->checksum), HEX); + pr->print(F("firstCluster: ")); + pr->println(getLe32(dir->firstCluster)); + pr->print(F("size: ")); + println64(pr, getLe64(dir->size)); +} +//------------------------------------------------------------------------------ +static void printExFatBoot(print_t* pr, pbs_t* pbs) { + BpbExFat_t* ebs = reinterpret_cast(pbs->bpb); + pr->print(F("bpbSig: 0x")); + pr->println(getLe16(pbs->signature), HEX); + pr->print(F("FileSystemName: ")); + pr->write(reinterpret_cast(pbs->oemName), 8); + pr->println(); + for (size_t i = 0; i < sizeof(ebs->mustBeZero); i++) { + if (ebs->mustBeZero[i]) { + pr->println(F("mustBeZero error")); + break; + } + } + pr->print(F("PartitionOffset: 0x")); + printHex64(pr, getLe64(ebs->partitionOffset)); + pr->print(F("VolumeLength: ")); + println64(pr, getLe64(ebs->volumeLength)); + pr->print(F("FatOffset: 0x")); + pr->println(getLe32(ebs->fatOffset), HEX); + pr->print(F("FatLength: ")); + pr->println(getLe32(ebs->fatLength)); + pr->print(F("ClusterHeapOffset: 0x")); + pr->println(getLe32(ebs->clusterHeapOffset), HEX); + pr->print(F("ClusterCount: ")); + pr->println(getLe32(ebs->clusterCount)); + pr->print(F("RootDirectoryCluster: ")); + pr->println(getLe32(ebs->rootDirectoryCluster)); + pr->print(F("VolumeSerialNumber: 0x")); + pr->println(getLe32(ebs->volumeSerialNumber), HEX); + pr->print(F("FileSystemRevision: 0x")); + pr->println(getLe32(ebs->fileSystemRevision), HEX); + pr->print(F("VolumeFlags: 0x")); + pr->println(getLe16(ebs->volumeFlags) , HEX); + pr->print(F("BytesPerSectorShift: ")); + pr->println(ebs->bytesPerSectorShift); + pr->print(F("SectorsPerClusterShift: ")); + pr->println(ebs->sectorsPerClusterShift); + pr->print(F("NumberOfFats: ")); + pr->println(ebs->numberOfFats); + pr->print(F("DriveSelect: 0x")); + pr->println(ebs->driveSelect, HEX); + pr->print(F("PercentInUse: ")); + pr->println(ebs->percentInUse); +} +//------------------------------------------------------------------------------ +static void printHex(print_t* pr, uint8_t h) { + if (h < 16) { + pr->write('0'); + } + pr->print(h, HEX); +} +//------------------------------------------------------------------------------ +static void printHex(print_t* pr, uint16_t val) { + bool space = true; + for (uint8_t i = 0; i < 4; i++) { + uint8_t h = (val >> (12 - 4*i)) & 15; + if (h || i == 3) { + space = false; + } + if (space) { + pr->write(' '); + } else { + pr->print(h, HEX); + } + } +} +//------------------------------------------------------------------------------ +static void printHex(print_t* pr, uint32_t val) { + bool space = true; + for (uint8_t i = 0; i < 8; i++) { + uint8_t h = (val >> (28 - 4*i)) & 15; + if (h || i == 7) { + space = false; + } + if (space) { + pr->write(' '); + } else { + pr->print(h, HEX); + } + } +} +//------------------------------------------------------------------------------ +static void printHex64(print_t* pr, uint64_t n) { + char buf[17]; + char *str = &buf[sizeof(buf) - 1]; + *str = '\0'; + do { + uint8_t h = n & 15; + *--str = h < 10 ? h + '0' : h + 'A' - 10; + n >>= 4; + } while (n); + pr->println(str); +} +//------------------------------------------------------------------------------ +static void println64(print_t* pr, uint64_t n) { + char buf[21]; + char *str = &buf[sizeof(buf) - 1]; + *str = '\0'; + do { + uint64_t m = n; + n /= 10; + *--str = m - 10*n + '0'; + } while (n); + pr->println(str); +} +//------------------------------------------------------------------------------ +static void printMbr(print_t* pr, MbrSector_t* mbr) { + pr->print(F("mbrSig: 0x")); + pr->println(getLe16(mbr->signature), HEX); + for (int i = 0; i < 4; i++) { + printHex(pr, mbr->part[i].boot); + pr->write(' '); + for (int k = 0; k < 3; k++) { + printHex(pr, mbr->part[i].beginCHS[k]); + pr->write(' '); + } + printHex(pr, mbr->part[i].type); + pr->write(' '); + for (int k = 0; k < 3; k++) { + printHex(pr, mbr->part[i].endCHS[k]); + pr->write(' '); + } + pr->print(getLe32(mbr->part[i].relativeSectors), HEX); + pr->print(' '); + pr->println(getLe32(mbr->part[i].totalSectors), HEX); + } +} +//============================================================================== +void ExFatPartition::checkUpcase(print_t* pr) { + bool skip = false; + uint16_t u = 0; + uint8_t* upcase = nullptr; + uint32_t size = 0; + uint32_t sector = clusterStartSector(m_rootDirectoryCluster); + uint8_t* cache = dataCacheGet(sector, FsCache::CACHE_FOR_READ); + if (!cache) { + pr->println(F("read root failed")); + return; + } + DirUpcase_t* dir = reinterpret_cast(cache); + + pr->println(F("\nChecking upcase table")); + for (size_t i = 0; i < 16; i++) { + if (dir[i].type == EXFAT_TYPE_UPCASE) { + sector = clusterStartSector(getLe32(dir[i].firstCluster)); + size = getLe64(dir[i].size); + break; + } + } + if (!size) { + pr->println(F("upcase not found")); + return; + } + for (size_t i = 0; i < size/2; i++) { + if ((i%256) == 0) { + upcase = dataCacheGet(sector++, FsCache::CACHE_FOR_READ); + if (!upcase) { + pr->println(F("read upcase failed")); + return; + } + } + uint16_t v = getLe16(&upcase[2*(i & 0XFF)]); + if (skip) { + pr->print("skip "); + pr->print(u); + pr->write(' '); + pr->println(v); + } + if (v == 0XFFFF) { + skip = true; + } else if (skip) { + for (uint16_t k = 0; k < v; k++) { + uint16_t x = toUpcase(u + k); + if (x != (u + k)) { + printHex(pr, (uint16_t)(u+k)); + pr->write(','); + printHex(pr, x); + pr->println("<<<<<<<<<<<<<<<<<<<<"); + } + } + u += v; + skip = false; + } else { + uint16_t x = toUpcase(u); + if (v != x) { + printHex(pr, u); + pr->write(','); + printHex(pr, x); + pr->write(','); + printHex(pr, v); + pr->println(); + } + u++; + } + } + pr->println(F("Done checkUpcase")); +} +//------------------------------------------------------------------------------ +void ExFatPartition::dmpBitmap(print_t* pr) { + pr->println(F("bitmap:")); + dmpSector(pr, m_clusterHeapStartSector); +} +//------------------------------------------------------------------------------ +void ExFatPartition::dmpCluster(print_t* pr, uint32_t cluster, + uint32_t offset, uint32_t count) { + uint32_t sector = clusterStartSector(cluster) + offset; + for (uint32_t i = 0; i < count; i++) { + pr->print(F("\nSector: ")); + pr->println(sector + i, HEX); + dmpSector(pr, sector + i); + } +} +//------------------------------------------------------------------------------ +void ExFatPartition::dmpFat(print_t* pr, uint32_t start, uint32_t count) { + uint32_t sector = m_fatStartSector + start; + uint32_t cluster = 128*start; + pr->println(F("FAT:")); + for (uint32_t i = 0; i < count; i++) { + uint8_t* cache = dataCacheGet(sector + i, FsCache::CACHE_FOR_READ); + if (!cache) { + pr->println(F("cache read failed")); + return; + } + uint32_t* fat = reinterpret_cast(cache); + for (size_t k = 0; k < 128; k++) { + if (0 == cluster%8) { + if (k) { + pr->println(); + } + printHex(pr, cluster); + } + cluster++; + pr->write(' '); + printHex(pr, fat[k]); + } + pr->println(); + } +} +//------------------------------------------------------------------------------ +void ExFatPartition::dmpSector(print_t* pr, uint32_t sector) { + uint8_t* cache = dataCacheGet(sector, FsCache::CACHE_FOR_READ); + if (!cache) { + pr->println(F("dmpSector failed")); + return; + } + for (uint16_t i = 0; i < 512; i++) { + if (i%32 == 0) { + if (i) { + pr->println(); + } + printHex(pr, i); + } + pr->write(' '); + printHex(pr, cache[i]); + } + pr->println(); +} +//------------------------------------------------------------------------------ +bool ExFatPartition::printDir(print_t* pr, ExFatFile* file) { + DirGeneric_t* dir = nullptr; + DirFile_t* dirFile; + DirStream_t* dirStream; + DirName_t* dirName; + uint16_t calcHash = 0; + uint16_t nameHash = 0; + uint16_t setChecksum = 0; + uint16_t calcChecksum = 0; + uint8_t nameLength = 0; + uint8_t setCount = 0; + uint8_t nUnicode; + +#define RAW_ROOT +#ifndef RAW_ROOT + while (1) { + uint8_t buf[32]; + if (file->read(buf, 32) != 32) { + break; + } + dir = reinterpret_cast(buf); +#else // RAW_ROOT + (void)file; + uint32_t nDir = 1UL << (m_sectorsPerClusterShift + 4); + uint32_t sector = clusterStartSector(m_rootDirectoryCluster); + for (uint32_t iDir = 0; iDir < nDir; iDir++) { + size_t i = iDir%16; + if (i == 0) { + uint8_t* cache = dataCacheGet(sector++, FsCache::CACHE_FOR_READ); + if (!cache) { + return false; + } + dir = reinterpret_cast(cache); + } else { + dir++; + } +#endif // RAW_ROOT + if (dir->type == 0) { + break; + } + pr->println(); + + switch (dir->type) { + case EXFAT_TYPE_BITMAP: + printDirBitmap(pr, reinterpret_cast(dir)); + break; + + case EXFAT_TYPE_UPCASE: + printDirUpcase(pr, reinterpret_cast(dir)); + break; + + case EXFAT_TYPE_LABEL: + printDirLabel(pr, reinterpret_cast(dir)); + break; + + case EXFAT_TYPE_FILE: + dirFile = reinterpret_cast(dir); + printDirFile(pr, dirFile); + setCount = dirFile->setCount; + setChecksum = getLe16(dirFile->setChecksum); + calcChecksum = exFatDirChecksum(dir, 0); + break; + + case EXFAT_TYPE_STREAM: + dirStream = reinterpret_cast(dir); + printDirStream(pr, dirStream); + nameLength = dirStream->nameLength; + nameHash = getLe16(dirStream->nameHash); + calcChecksum = exFatDirChecksum(dir, calcChecksum); + setCount--; + calcHash = 0; + break; + + case EXFAT_TYPE_NAME: + dirName = reinterpret_cast(dir); + printDirName(pr, dirName); + calcChecksum = exFatDirChecksum(dir, calcChecksum); + nUnicode = nameLength > 15 ? 15 : nameLength; + calcHash = exFatHashName(reinterpret_cast + (dirName->unicode), nUnicode, calcHash); + nameLength -= nUnicode; + setCount--; + if (nameLength == 0 || setCount == 0) { + pr->print(F("setChecksum: 0x")); + pr->print(setChecksum, HEX); + if (setChecksum != calcChecksum) { + pr->print(F(" != calcChecksum: 0x")); + } else { + pr->print(F(" == calcChecksum: 0x")); + } + pr->println(calcChecksum, HEX); + pr->print(F("nameHash: 0x")); + pr->print(nameHash, HEX); + if (nameHash != calcHash) { + pr->print(F(" != calcHash: 0x")); + } else { + pr->print(F(" == calcHash: 0x")); + } + pr->println(calcHash, HEX); + } + break; + + default: + if (dir->type & 0x80) { + pr->print(F("Unknown dirType: 0x")); + } else { + pr->print(F("Unused dirType: 0x")); + } + pr->println(dir->type, HEX); + dmpDirData(pr, dir); + break; + } + } + pr->println(F("Done")); + return true; +} +//------------------------------------------------------------------------------ +void ExFatPartition::printFat(print_t* pr) { + uint32_t next; + int8_t status; + pr->println(F("FAT:")); + for (uint32_t cluster = 0; cluster < 16; cluster++) { + status = fatGet(cluster, &next); + pr->print(cluster, HEX); + pr->write(' '); + if (status == 0) { + next = EXFAT_EOC; + } + pr->println(next, HEX); + } +} +//------------------------------------------------------------------------------ +void ExFatPartition::printUpcase(print_t* pr) { + uint8_t* upcase = nullptr; + uint32_t sector; + uint32_t size = 0; + uint32_t checksum = 0; + DirUpcase_t* dir; + sector = clusterStartSector(m_rootDirectoryCluster); + upcase = dataCacheGet(sector, FsCache::CACHE_FOR_READ); + dir = reinterpret_cast(upcase); + if (!dir) { + pr->println(F("read root dir failed")); + return; + } + for (size_t i = 0; i < 16; i++) { + if (dir[i].type == EXFAT_TYPE_UPCASE) { + sector = clusterStartSector(getLe32(dir[i].firstCluster)); + size = getLe64(dir[i].size); + break; + } + } + if (!size) { + pr->println(F("upcase not found")); + return; + } + for (uint16_t i = 0; i < size/2; i++) { + if ((i%256) == 0) { + upcase = dataCacheGet(sector++, FsCache::CACHE_FOR_READ); + if (!upcase) { + pr->println(F("read upcase failed")); + return; + } + } + if (i%16 == 0) { + pr->println(); + printHex(pr, i); + } + pr->write(' '); + uint16_t uc = getLe16(&upcase[2*(i & 0XFF)]); + printHex(pr, uc); + checksum = upcaseChecksum(uc, checksum); + } + pr->println(); + pr->print(F("checksum: ")); + printHex(pr, checksum); + pr->println(); +} +//------------------------------------------------------------------------------ +bool ExFatPartition::printVolInfo(print_t* pr) { + uint8_t* cache = dataCacheGet(0, FsCache::CACHE_FOR_READ); + if (!cache) { + pr->println(F("read mbr failed")); + return false; + } + MbrSector_t* mbr = reinterpret_cast(cache); + printMbr(pr, mbr); + uint32_t volStart = getLe32(mbr->part->relativeSectors); + uint32_t volSize = getLe32(mbr->part->totalSectors); + if (volSize == 0) { + pr->print(F("bad partition size")); + return false; + } + cache = dataCacheGet(volStart, FsCache::CACHE_FOR_READ); + if (!cache) { + pr->println(F("read pbs failed")); + return false; + } + printExFatBoot(pr, reinterpret_cast(cache)); + return true; +} +#endif // DOXYGEN_SHOULD_SKIP_THIS diff --git a/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatFile.cpp b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatFile.cpp new file mode 100644 index 0000000..7b7832d --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatFile.cpp @@ -0,0 +1,743 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define DBG_FILE "ExFatFile.cpp" +#include "../common/DebugMacros.h" +#include "ExFatFile.h" +#include "ExFatVolume.h" +#include "upcase.h" +//------------------------------------------------------------------------------ +bool ExFatFile::close() { + bool rtn = sync(); + m_attributes = FILE_ATTR_CLOSED; + m_flags = 0; + return rtn; +} +//------------------------------------------------------------------------------ +bool ExFatFile::contiguousRange(uint32_t* bgnSector, uint32_t* endSector) { + if (!isContiguous()) { + return false; + } + if (bgnSector) { + *bgnSector = firstSector(); + } + if (endSector) { + *endSector = firstSector() + + ((m_validLength - 1) >> m_vol->bytesPerSectorShift()); + } + return true; +} +//------------------------------------------------------------------------------ +void ExFatFile::fgetpos(fspos_t* pos) const { + pos->position = m_curPosition; + pos->cluster = m_curCluster; +} +//------------------------------------------------------------------------------ +int ExFatFile::fgets(char* str, int num, char* delim) { + char ch; + int n = 0; + int r = -1; + while ((n + 1) < num && (r = read(&ch, 1)) == 1) { + // delete CR + if (ch == '\r') { + continue; + } + str[n++] = ch; + if (!delim) { + if (ch == '\n') { + break; + } + } else { + if (strchr(delim, ch)) { + break; + } + } + } + if (r < 0) { + // read error + return -1; + } + str[n] = '\0'; + return n; +} +//------------------------------------------------------------------------------ +uint32_t ExFatFile::firstSector() const { + return m_firstCluster ? m_vol->clusterStartSector(m_firstCluster) : 0; +} +//------------------------------------------------------------------------------ +void ExFatFile::fsetpos(const fspos_t* pos) { + m_curPosition = pos->position; + m_curCluster = pos->cluster; +} +//------------------------------------------------------------------------------ +bool ExFatFile::getAccessDateTime(uint16_t* pdate, uint16_t* ptime) { + DirFile_t* df = reinterpret_cast + (m_vol->dirCache(&m_dirPos, FsCache::CACHE_FOR_READ)); + if (!df) { + DBG_FAIL_MACRO; + goto fail; + } + *pdate = getLe16(df->accessDate); + *ptime = getLe16(df->accessTime); + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::getCreateDateTime(uint16_t* pdate, uint16_t* ptime) { + DirFile_t* df = reinterpret_cast + (m_vol->dirCache(&m_dirPos, FsCache::CACHE_FOR_READ)); + if (!df) { + DBG_FAIL_MACRO; + goto fail; + } + *pdate = getLe16(df->createDate); + *ptime = getLe16(df->createTime); + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::getModifyDateTime(uint16_t* pdate, uint16_t* ptime) { + DirFile_t* df = reinterpret_cast + (m_vol->dirCache(&m_dirPos, FsCache::CACHE_FOR_READ)); + if (!df) { + DBG_FAIL_MACRO; + goto fail; + } + *pdate = getLe16(df->modifyDate); + *ptime = getLe16(df->modifyTime); + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +size_t ExFatFile::getName(ExChar_t* name, size_t length) { + DirName_t* dn; + DirPos_t pos = m_dirPos; + size_t n = 0; + if (!isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t is = 1; is < m_setCount; is++) { + if (m_vol->dirSeek(&pos, is == 1 ? 64: 32) != 1) { + DBG_FAIL_MACRO; + goto fail; + } + dn = reinterpret_cast + (m_vol->dirCache(&pos, FsCache::CACHE_FOR_READ)); + if (!dn || dn->type != EXFAT_TYPE_NAME) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t in = 0; in < 15; in++) { + uint16_t c = getLe16(dn->unicode + 2*in); + if (c == 0 || (n + 1) >= length) { + goto done; + } + name[n++] = sizeof(ExChar_t) > 1 || c < 0X7F ? c : '?'; + } + } + done: + name[n] = 0; + return n; + + fail: + *name = 0; + return 0; +} +//------------------------------------------------------------------------------ +bool ExFatFile::isBusy() { + return m_vol->isBusy(); +} +//------------------------------------------------------------------------------ +bool ExFatFile::open(const ExChar_t* path, int oflag) { + return open(ExFatVolume::cwv(), path, oflag); +} +//------------------------------------------------------------------------------ +bool ExFatFile::open(ExFatVolume* vol, const ExChar_t* path, int oflag) { + return vol && open(vol->vwd(), path, oflag); +} +//------------------------------------------------------------------------------ +bool ExFatFile::open(ExFatFile* dirFile, const ExChar_t* path, oflag_t oflag) { + ExFatFile tmpDir; + ExName_t fname; + // error if already open + if (isOpen() || !dirFile->isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + if (isDirSeparator(*path)) { + while (isDirSeparator(*path)) { + path++; + } + if (*path == 0) { + return openRoot(dirFile->m_vol); + } + if (!tmpDir.openRoot(dirFile->m_vol)) { + DBG_FAIL_MACRO; + goto fail; + } + dirFile = &tmpDir; + } + while (1) { + if (!parsePathName(path, &fname, &path)) { + DBG_FAIL_MACRO; + goto fail; + } + if (*path == 0) { + break; + } + if (!open(dirFile, &fname, O_RDONLY)) { + DBG_FAIL_MACRO; + goto fail; + } + tmpDir = *this; + dirFile = &tmpDir; + close(); + } + return open(dirFile, &fname, oflag); + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::open(ExFatFile* dirFile, uint32_t index, oflag_t oflag) { + if (dirFile->seekSet(32*index) && openNext(dirFile, oflag)) { + if (dirIndex() == index) { + return true; + } + close(); + DBG_FAIL_MACRO; + } + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::openNext(ExFatFile* dir, oflag_t oflag) { + if (isOpen() || !dir->isDir() || (dir->curPosition() & 0X1F)) { + DBG_FAIL_MACRO; + goto fail; + } + return openRootFile(dir, nullptr, 0, oflag); + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::openRootFile(ExFatFile* dir, const ExChar_t* name, + uint8_t nameLength, oflag_t oflag) { + int n; + uint8_t nameOffset = 0; + uint8_t nCmp; + uint8_t modeFlags; + uint16_t nameHash = 0; + uint32_t curCluster __attribute__((unused)); + uint8_t* cache __attribute__((unused)); + DirPos_t freePos __attribute__((unused)); + + DirFile_t* dirFile; + DirStream_t* dirStream; + DirName_t* dirName; + uint8_t buf[32]; + uint8_t freeCount = 0; + uint8_t freeNeed; + bool inSet = false; + + // error if already open + if (isOpen() || !dir->isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + switch (oflag & O_ACCMODE) { + case O_RDONLY: + modeFlags = FILE_FLAG_READ; + break; + case O_WRONLY: + modeFlags = FILE_FLAG_WRITE; + break; + case O_RDWR: + modeFlags = FILE_FLAG_READ | FILE_FLAG_WRITE; + break; + default: + DBG_FAIL_MACRO; + goto fail; + } + modeFlags |= oflag & O_APPEND ? FILE_FLAG_APPEND : 0; + if (name) { + nameHash = exFatHashName(name, nameLength, 0); + dir->rewind(); + } + freeNeed = 2 + (nameLength + 14)/15; + + while (1) { + n = dir->read(buf, 32); + if (n == 0) { + goto create; + } + if (n != 32) { + DBG_FAIL_MACRO; + goto fail; + } + if (!(buf[0] & 0x80)) { + if (freeCount == 0) { + freePos.position = dir->curPosition() - 32; + freePos.cluster = dir->curCluster(); + } + if (freeCount < freeNeed) { + freeCount++; + } + if (!buf[0]) { + goto create; + } + } else if (!inSet) { + if (freeCount < freeNeed) { + freeCount = 0; + } + if (buf[0] != EXFAT_TYPE_FILE) { + continue; + } + inSet = true; + } + switch (buf[0]) { + case EXFAT_TYPE_FILE: + memset(this, 0, sizeof(ExFatFile)); + dirFile = reinterpret_cast(buf); + m_setCount = dirFile->setCount; + m_attributes = getLe16(dirFile->attributes) & FILE_ATTR_COPY; + if (!(m_attributes & EXFAT_ATTRIB_DIRECTORY)) { + m_attributes |= FILE_ATTR_FILE; + } + m_vol = dir->volume(); + + m_dirPos.cluster = dir->curCluster(); + m_dirPos.position = dir->curPosition() - 32; + m_dirPos.isContiguous = dir->isContiguous(); + break; + + case EXFAT_TYPE_STREAM: + dirStream = reinterpret_cast(buf); + m_flags = modeFlags; + if (dirStream->flags & EXFAT_FLAG_CONTIGUOUS) { + m_flags |= FILE_FLAG_CONTIGUOUS; + } + nameOffset = 0; + m_validLength = getLe64(dirStream->validLength); + m_firstCluster = getLe32(dirStream->firstCluster); + m_dataLength = getLe64(dirStream->dataLength); + if (!name) { + goto found; + } + if (nameLength != dirStream->nameLength || + nameHash != getLe16(dirStream->nameHash)) { + inSet = false; + break; + } + break; + + case EXFAT_TYPE_NAME: + dirName = reinterpret_cast(buf); + nCmp = nameLength - nameOffset; + if (nCmp > 15) { + nCmp = 15; + } + if (!exFatCmpName(dirName, name, nameOffset, nCmp)) { + inSet = false; + break; + } + nameOffset += nCmp; + + if (nameOffset == nameLength) { + goto found; + } + break; + + default: + break; + } + } + + found: + // Don't open if create only. + if (oflag & O_EXCL) { + DBG_FAIL_MACRO; + goto fail; + } + // Write, truncate, or at end is an error for a directory or read-only file. + if ((oflag & (O_TRUNC | O_AT_END)) || (m_flags & FILE_FLAG_WRITE)) { + if (isSubDir() || isReadOnly() || READ_ONLY) { + DBG_FAIL_MACRO; + goto fail; + } + } + +#if !READ_ONLY + if (oflag & O_TRUNC) { + if (!(m_flags & FILE_FLAG_WRITE)) { + DBG_FAIL_MACRO; + goto fail; + } + if (!truncate(0)) { + DBG_FAIL_MACRO; + goto fail; + } + } else if ((oflag & O_AT_END) && !seekSet(fileSize())) { + DBG_FAIL_MACRO; + goto fail; + } +#endif // READ_ONLY + return true; + + create: +#if READ_ONLY + DBG_FAIL_MACRO; + goto fail; +#else // READ_ONLY + // don't create unless O_CREAT and write + if (!(oflag & O_CREAT) || !(modeFlags & FILE_FLAG_WRITE) || !name) { + DBG_FAIL_MACRO; + goto fail; + } + while (freeCount < freeNeed) { + n = dir->read(buf, 32); + if (n == 0) { + curCluster = dir->m_curCluster; + if (!dir->addDirCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + dir->m_curCluster = curCluster; + continue; + } + if (n != 32) { + DBG_FAIL_MACRO; + goto fail; + } + if (freeCount == 0) { + freePos.position = dir->curPosition() - 32; + freePos.cluster = dir->curCluster(); + } + freeCount++; + } + + freePos.isContiguous = dir->isContiguous(); + memset(this, 0, sizeof(ExFatFile)); + m_vol = dir->volume(); + m_attributes = FILE_ATTR_FILE; + m_dirPos = freePos; + for (uint8_t i = 0; i < freeNeed; i++) { + if (i) { + if (1 != m_vol->dirSeek(&freePos, 32)) { + DBG_FAIL_MACRO; + goto fail; + } + } + cache = m_vol->dirCache(&freePos, FsCache::CACHE_FOR_WRITE); + if (!cache || (cache[0] & 0x80)) { + DBG_FAIL_MACRO; + goto fail; + } + memset(cache, 0 , 32); + if (i == 0) { + dirFile = reinterpret_cast(cache); + dirFile->type = EXFAT_TYPE_FILE; + m_setCount = freeNeed - 1; + dirFile->setCount = m_setCount; + + if (FsDateTime::callback) { + uint16_t date, time; + uint8_t ms10; + FsDateTime::callback(&date, &time, &ms10); + setLe16(dirFile->createDate, date); + setLe16(dirFile->createTime, time); + dirFile->createTimeMs = ms10; + } else { + setLe16(dirFile->createDate, FS_DEFAULT_DATE); + setLe16(dirFile->modifyDate, FS_DEFAULT_DATE); + setLe16(dirFile->accessDate, FS_DEFAULT_DATE); + if (FS_DEFAULT_TIME) { + setLe16(dirFile->createTime, FS_DEFAULT_TIME); + setLe16(dirFile->modifyTime, FS_DEFAULT_TIME); + setLe16(dirFile->accessTime, FS_DEFAULT_TIME); + } + } + } else if (i == 1) { + dirStream = reinterpret_cast(cache); + dirStream->type = EXFAT_TYPE_STREAM; + dirStream->flags = EXFAT_FLAG_ALWAYS1 | EXFAT_FLAG_CONTIGUOUS; + m_flags = modeFlags | FILE_FLAG_CONTIGUOUS | FILE_FLAG_DIR_DIRTY; + + dirStream->nameLength = nameLength; + setLe16(dirStream->nameHash, nameHash); + } else { + dirName = reinterpret_cast(cache); + dirName->type = EXFAT_TYPE_NAME; + nameOffset = 15*(i - 2); + nCmp = nameLength - nameOffset; + if (nCmp > 15) { + nCmp = 15; + } + for (size_t k = 0; k < nCmp; k++) { + setLe16(dirName->unicode + 2*k, name[k + nameOffset]); + } + } + } + return sync(); +#endif // READ_ONLY + fail: + + // close file + m_attributes = FILE_ATTR_CLOSED; + m_flags = 0; + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::openRoot(ExFatVolume* vol) { + if (isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + memset(this, 0, sizeof(ExFatFile)); + m_attributes = FILE_ATTR_ROOT; + m_vol = vol; + m_flags = FILE_FLAG_READ; + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::parsePathName(const ExChar_t* path, + ExName_t* fname, const ExChar_t** ptr) { + ExChar_t c; + int end; + int len = 0; + + // Skip leading spaces. + while (*path == ' ') { + path++; + } + fname->lfn = path; + + for (len = 0; ; len++) { + c = path[len]; + if (c == 0 || isDirSeparator(c)) { + break; + } + if (!lfnLegalChar(c)) { + return false; + } + } + // Advance to next path component. + for (end = len; path[end] == ' ' || isDirSeparator(path[end]); end++) {} + *ptr = &path[end]; + + // Back over spaces and dots. + while (len) { + c = path[len - 1]; + if (c != '.' && c != ' ') { + break; + } + len--; + } + // Max length of LFN is 255. + if (len > EXFAT_MAX_NAME_LENGTH) { + return false; + } + fname->len = len; + return true; +} +//------------------------------------------------------------------------------ +int ExFatFile::peek() { + uint64_t curPosition = m_curPosition; + uint32_t curCluster = m_curCluster; + int c = read(); + m_curPosition = curPosition; + m_curCluster = curCluster; + return c; +} +//------------------------------------------------------------------------------ +int ExFatFile::read(void* buf, size_t count) { + uint8_t* dst = reinterpret_cast(buf); + int8_t fg; + size_t toRead = count; + size_t n; + uint8_t* cache; + uint16_t sectorOffset; + uint32_t sector; + uint32_t clusterOffset; + + if (!isReadable()) { + DBG_FAIL_MACRO; + goto fail; + } + if (isContiguous() || isFile()) { + if ((m_curPosition + count) > m_validLength) { + count = toRead = m_validLength - m_curPosition; + } + } + while (toRead) { + clusterOffset = m_curPosition & m_vol->clusterMask(); + sectorOffset = clusterOffset & m_vol->sectorMask(); + if (clusterOffset == 0) { + if (m_curPosition == 0) { + m_curCluster = isRoot() + ? m_vol->rootDirectoryCluster() : m_firstCluster; + } else if (isContiguous()) { + m_curCluster++; + } else { + fg = m_vol->fatGet(m_curCluster, &m_curCluster); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (fg == 0) { + // EOF if directory. + if (isDir()) { + break; + } + DBG_FAIL_MACRO; + goto fail; + } + } + } + sector = m_vol->clusterStartSector(m_curCluster) + + (clusterOffset >> m_vol->bytesPerSectorShift()); + if (sectorOffset != 0 || toRead < m_vol->bytesPerSector() + || sector == m_vol->dataCacheSector()) { + n = m_vol->bytesPerSector() - sectorOffset; + if (n > toRead) { + n = toRead; + } + // read sector to cache and copy data to caller + cache = m_vol->dataCacheGet(sector, FsCache::CACHE_FOR_READ); + if (!cache) { + DBG_FAIL_MACRO; + goto fail; + } + uint8_t* src = cache + sectorOffset; + memcpy(dst, src, n); +#if USE_MULTI_SECTOR_IO + } else if (toRead >= 2*m_vol->bytesPerSector()) { + uint32_t ns = toRead >> m_vol->bytesPerSectorShift(); + // Limit reads to current cluster. + uint32_t maxNs = m_vol->sectorsPerCluster() + - (clusterOffset >> m_vol->bytesPerSectorShift()); + if (ns > maxNs) { + ns = maxNs; + } + n = ns << m_vol->bytesPerSectorShift(); + if (!m_vol->cacheSafeRead(sector, dst, ns)) { + DBG_FAIL_MACRO; + goto fail; + } +#endif // USE_MULTI_SECTOR_IO + } else { + // read single sector + n = m_vol->bytesPerSector(); + if (!m_vol->cacheSafeRead(sector, dst)) { + DBG_FAIL_MACRO; + goto fail; + } + } + dst += n; + m_curPosition += n; + toRead -= n; + } + return count - toRead; + + fail: + m_error |= READ_ERROR; + return -1; +} +//------------------------------------------------------------------------------ +bool ExFatFile::remove(const ExChar_t* path) { + ExFatFile file; + if (!file.open(this, path, O_WRONLY)) { + DBG_FAIL_MACRO; + goto fail; + } + return file.remove(); + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::seekSet(uint64_t pos) { + uint32_t nCur; + uint32_t nNew; + uint32_t tmp = m_curCluster; + // error if file not open + if (!isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + // Optimize O_APPEND writes. + if (pos == m_curPosition) { + return true; + } + if (pos == 0) { + // set position to start of file + m_curCluster = 0; + goto done; + } + if (isFile()) { + if (pos > m_validLength) { + DBG_FAIL_MACRO; + goto fail; + } + } + // calculate cluster index for new position + nNew = (pos - 1) >> m_vol->bytesPerClusterShift(); + if (isContiguous()) { + m_curCluster = m_firstCluster + nNew; + goto done; + } + // calculate cluster index for current position + nCur = (m_curPosition - 1) >> m_vol->bytesPerClusterShift(); + if (nNew < nCur || m_curPosition == 0) { + // must follow chain from first cluster + m_curCluster = isRoot() ? m_vol->rootDirectoryCluster() : m_firstCluster; + } else { + // advance from curPosition + nNew -= nCur; + } + while (nNew--) { + if (m_vol->fatGet(m_curCluster, &m_curCluster) <= 0) { + DBG_FAIL_MACRO; + goto fail; + } + } + + done: + m_curPosition = pos; + return true; + + fail: + m_curCluster = tmp; + return false; +} diff --git a/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatFile.h b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatFile.h new file mode 100644 index 0000000..629a5b5 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatFile.h @@ -0,0 +1,815 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef ExFatFile_h +#define ExFatFile_h +/** + * \file + * \brief ExFatFile class + */ +#include +#include +#include "ExFatConfig.h" +#include "../common/FsDateTime.h" +#include "../common/FsStructs.h" +#include "../common/FsApiConstants.h" +#include "../common/FmtNumber.h" +#include "ExFatTypes.h" +#include "ExFatPartition.h" + +class ExFatVolume; + +//------------------------------------------------------------------------------ +/** Expression for path name separator. */ +#define isDirSeparator(c) ((c) == '/') +//------------------------------------------------------------------------------ +/** test for legal character. + * + * \param[in] c character to be tested. + * + * \return true for legal character else false. + */ +inline bool lfnLegalChar(ExChar_t c) { + if (c == '/' || c == '\\' || c == '"' || c == '*' || + c == ':' || c == '<' || c == '>' || c == '?' || c == '|') { + return false; + } +#if USE_EXFAT_UNICODE_NAMES + return 0X1F < c; +#else // USE_EXFAT_UNICODE_NAMES + return 0X1F < c && c < 0X7F; +#endif // USE_EXFAT_UNICODE_NAMES +} +//------------------------------------------------------------------------------ +/** + * \struct ExName_t + * \brief Internal type for file name - do not use in user apps. + */ +struct ExName_t { + /** length of Long File Name */ + size_t len; + /** Long File Name start. */ + const ExChar_t* lfn; +}; +//------------------------------------------------------------------------------ +/** + * \struct ExFatPos_t + * \brief Internal type for file position - do not use in user apps. + */ +struct ExFatPos_t { + /** stream position */ + uint64_t position; + /** cluster for position */ + uint32_t cluster; +}; +//------------------------------------------------------------------------------ +/** + * \class ExFatFile + * \brief Basic file class. + */ +class ExFatFile { + public: + /** Create an instance. */ + ExFatFile() {} + /** Create a file object and open it in the current working directory. + * + * \param[in] path A path for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive + * OR of open flags. see FatFile::open(FatFile*, const char*, uint8_t). + */ + ExFatFile(const char* path, oflag_t oflag) { + open(path, oflag); + } + +#if DESTRUCTOR_CLOSES_FILE + ~ExFatFile() { + if (isOpen()) { + close(); + } + } +#endif // DESTRUCTOR_CLOSES_FILE + + /** The parenthesis operator. + * + * \return true if a file is open. + */ + operator bool() { + return isOpen(); + } + /** \return The number of bytes available from the current position + * to EOF for normal files. INT_MAX is returned for very large files. + * + * available64() is recommended for very large files. + * + * Zero is returned for directory files. + * + */ + int available() { + uint64_t n = available64(); + return n > INT_MAX ? INT_MAX : n; + } + /** \return The number of bytes available from the current position + * to EOF for normal files. Zero is returned for directory files. + */ + uint64_t available64() { + return isFile() ? fileSize() - curPosition() : 0; + } + /** Clear all error bits. */ + void clearError() { + m_error = 0; + } + /** Clear writeError. */ + void clearWriteError() { + m_error &= ~WRITE_ERROR; + } + /** Close a file and force cached data and directory information + * to be written to the storage device. + * + * \return true for success or false for failure. + */ + bool close(); + /** Check for contiguous file and return its raw sector range. + * + * \param[out] bgnSector the first sector address for the file. + * \param[out] endSector the last sector address for the file. + * + * Parameters may be nullptr. + * + * \return true for success or false for failure. + */ + bool contiguousRange(uint32_t* bgnSector, uint32_t* endSector); + /** \return The current position for a file or directory. */ + uint64_t curPosition() const {return m_curPosition;} + + /** \return Total data length for file. */ + uint64_t dataLength() const {return m_dataLength;} + /** \return Directory entry index. */ + uint32_t dirIndex() const {return m_dirPos.position/32;} + /** Test for the existence of a file in a directory + * + * \param[in] path Path of the file to be tested for. + * + * The calling instance must be an open directory file. + * + * dirFile.exists("TOFIND.TXT") searches for "TOFIND.TXT" in the directory + * dirFile. + * + * \return true if the file exists else false. + */ + bool exists(const ExChar_t* path) { + ExFatFile file; + return file.open(this, path, O_RDONLY); + } + /** get position for streams + * \param[out] pos struct to receive position + */ + void fgetpos(fspos_t* pos) const; + /** + * Get a string from a file. + * + * fgets() reads bytes from a file into the array pointed to by \a str, until + * \a num - 1 bytes are read, or a delimiter is read and transferred to + * \a str, or end-of-file is encountered. The string is then terminated + * with a null byte. + * + * fgets() deletes CR, '\\r', from the string. This insures only a '\\n' + * terminates the string for Windows text files which use CRLF for newline. + * + * \param[out] str Pointer to the array where the string is stored. + * \param[in] num Maximum number of characters to be read + * (including the final null byte). Usually the length + * of the array \a str is used. + * \param[in] delim Optional set of delimiters. The default is "\n". + * + * \return For success fgets() returns the length of the string in \a str. + * If no data is read, fgets() returns zero for EOF or -1 if an error + * occurred. + */ + int fgets(char* str, int num, char* delim = nullptr); + /** \return The total number of bytes in a file. */ + uint64_t fileSize() const {return m_validLength;} + /** \return Address of first sector or zero for empty file. */ + uint32_t firstSector() const; + /** Set position for streams + * \param[in] pos struct with value for new position + */ + void fsetpos(const fspos_t* pos); + /** Arduino name for sync() */ + void flush() {sync();} + /** Get a file's access date and time. + * + * \param[out] pdate Packed date for directory entry. + * \param[out] ptime Packed time for directory entry. + * + * \return true for success or false for failure. + */ + bool getAccessDateTime(uint16_t* pdate, uint16_t* ptime); + /** Get a file's create date and time. + * + * \param[out] pdate Packed date for directory entry. + * \param[out] ptime Packed time for directory entry. + * + * \return true for success or false for failure. + */ + bool getCreateDateTime(uint16_t* pdate, uint16_t* ptime); + /** \return All error bits. */ + uint8_t getError() const { + return isOpen() ? m_error : 0XFF; + } + /** Get a file's modify date and time. + * + * \param[out] pdate Packed date for directory entry. + * \param[out] ptime Packed time for directory entry. + * + * \return true for success or false for failure. + */ + bool getModifyDateTime(uint16_t* pdate, uint16_t* ptime); + /** + * Get a file's name followed by a zero byte. + * + * \param[out] name An array of characters for the file's name. + * \param[in] size The size of the array in characters. + * \return the name length. + */ + size_t getName(ExChar_t* name, size_t size); + /** \return value of writeError */ + bool getWriteError() const { + return isOpen() ? m_error & WRITE_ERROR : true; + } + /** + * Check for BlockDevice busy. + * + * \return true if busy else false. + */ + bool isBusy(); + /** \return True if the file is contiguous. */ + bool isContiguous() const {return m_flags & FILE_FLAG_CONTIGUOUS;} + /** \return True if this is a directory. */ + bool isDir() const {return m_attributes & FILE_ATTR_DIR;} + /** \return True if this is a normal file. */ + bool isFile() const {return m_attributes & FILE_ATTR_FILE;} + /** \return True if this is a hidden. */ + bool isHidden() const {return m_attributes & FILE_ATTR_HIDDEN;} + /** \return true if the file is open. */ + bool isOpen() const {return m_attributes;} + /** \return True if file is read-only */ + bool isReadOnly() const {return m_attributes & FILE_ATTR_READ_ONLY;} + /** \return True if this is the root directory. */ + bool isRoot() const {return m_attributes & FILE_ATTR_ROOT;} + /** \return True file is readable. */ + bool isReadable() const {return m_flags & FILE_FLAG_READ;} + /** \return True if this is a subdirectory. */ + bool isSubDir() const {return m_attributes & FILE_ATTR_SUBDIR;} + /** \return True file is writable. */ + bool isWritable() const {return m_flags & FILE_FLAG_WRITE;} + /** List directory contents. + * + * \param[in] pr Print stream for list. + * \return true for success or false for failure. + */ + bool ls(print_t* pr); + /** List directory contents. + * + * \param[in] pr Print stream for list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \param[in] indent Amount of space before file name. Used for recursive + * list to indicate subdirectory level. + * + * \return true for success or false for failure. + */ + bool ls(print_t* pr, uint8_t flags, uint8_t indent = 0); + /** Make a new directory. + * + * \param[in] parent An open directory file that will + * contain the new directory. + * + * \param[in] path A path with a valid name for the new directory. + * + * \param[in] pFlag Create missing parent directories if true. + * + * \return true for success or false for failure. + */ + bool mkdir(ExFatFile* parent, const ExChar_t* path, bool pFlag = true); + /** Open a file or directory by name. + * + * \param[in] dirFile An open directory containing the file to be opened. + * + * \param[in] path The path for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a + * bitwise-inclusive OR of flags from the following list. + * Only one of O_RDONLY, O_READ, O_WRONLY, O_WRITE, or + * O_RDWR is allowed. + * + * O_RDONLY - Open for reading. + * + * O_READ - Same as O_RDONLY. + * + * O_WRONLY - Open for writing. + * + * O_WRITE - Same as O_WRONLY. + * + * O_RDWR - Open for reading and writing. + * + * O_APPEND - If set, the file offset shall be set to the end of the + * file prior to each write. + * + * O_AT_END - Set the initial position at the end of the file. + * + * O_CREAT - If the file exists, this flag has no effect except as noted + * under O_EXCL below. Otherwise, the file shall be created + * + * O_EXCL - If O_CREAT and O_EXCL are set, open() shall fail if the file + * exists. + * + * O_TRUNC - If the file exists and is a regular file, and the file is + * successfully opened and is not read only, its length shall be truncated + * to 0. + * + * WARNING: A given file must not be opened by more than one file object + * or file corruption may occur. + * + * \note Directory files must be opened read only. Write and truncation is + * not allowed for directory files. + * + * \return true for success or false for failure. + */ + bool open(ExFatFile* dirFile, const ExChar_t* path, oflag_t oflag); + /** Open a file in the volume working directory. + * + * \param[in] vol Volume where the file is located. + * + * \param[in] path with a valid name for a file to be opened. + * + * \param[in] oflag bitwise-inclusive OR of open flags. + * See see open(ExFatFile*, const char*, uint8_t). + * + * \return true for success or false for failure. + */ + bool open(ExFatVolume* vol, const ExChar_t* path, int oflag); + /** Open a file by index. + * + * \param[in] dirFile An open ExFatFile instance for the directory. + * + * \param[in] index The \a index of the directory entry for the file to be + * opened. The value for \a index is (directory file position)/32. + * + * \param[in] oflag bitwise-inclusive OR of open flags. + * See see ExFatFile::open(ExFatFile*, const ExChar_t*, uint8_t). + * + * See open() by path for definition of flags. + * \return true for success or false for failure. + */ + bool open(ExFatFile* dirFile, uint32_t index, oflag_t oflag); + /** Open a file in the current working directory. + * + * \param[in] path A path with a valid name for a file to be opened. + * + * \param[in] oflag bitwise-inclusive OR of open flags. + * See see ExFatFile::open(ExFatFile*, const char*, uint8_t). + * + * \return true for success or false for failure. + */ + bool open(const ExChar_t* path, int oflag = O_RDONLY); + /** Open the next file or subdirectory in a directory. + * + * \param[in] dirFile An open instance for the directory + * containing the file to be opened. + * + * \param[in] oflag bitwise-inclusive OR of open flags. + * See see open(ExFatFile*, const char*, uint8_t). + * + * \return true for success or false for failure. + */ + bool openNext(ExFatFile* dirFile, oflag_t oflag = O_RDONLY); + /** Open a volume's root directory. + * + * \param[in] vol The FAT volume containing the root directory to be opened. + * + * \return true for success or false for failure. + */ + bool openRoot(ExFatVolume* vol); + /** Return the next available byte without consuming it. + * + * \return The byte if no error and not at eof else -1; + */ + int peek(); + /** Allocate contiguous clusters to an empty file. + * + * The file must be empty with no clusters allocated. + * + * The file will have zero validLength and dataLength + * will equal the requested length. + * + * \param[in] length size of allocated space in bytes. + * \return true for success or false for failure. + */ + bool preAllocate(uint64_t length); + /** Print a file's access date and time + * + * \param[in] pr Print stream for output. + * + * \return true for success or false for failure. + */ + size_t printAccessDateTime(print_t* pr); + /** Print a file's creation date and time + * + * \param[in] pr Print stream for output. + * + * \return true for success or false for failure. + */ + size_t printCreateDateTime(print_t* pr); + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + size_t printField(double value, char term, uint8_t prec = 2) { + char buf[24]; + char* str = buf + sizeof(buf); + if (term) { + *--str = term; + if (term == '\n') { + *--str = '\r'; + } + } + str = fmtDouble(str, value, prec, false); + return write(str, buf + sizeof(buf) - str); + } + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + size_t printField(float value, char term, uint8_t prec = 2) { + return printField(static_cast(value), term, prec); + } + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \return The number of bytes written or -1 if an error occurs. + */ + template + size_t printField(Type value, char term) { + char sign = 0; + char buf[3*sizeof(Type) + 3]; + char* str = buf + sizeof(buf); + + if (term) { + *--str = term; + if (term == '\n') { + *--str = '\r'; + } + } + if (value < 0) { + value = -value; + sign = '-'; + } + if (sizeof(Type) < 4) { + str = fmtBase10(str, (uint16_t)value); + } else { + str = fmtBase10(str, (uint32_t)value); + } + if (sign) { + *--str = sign; + } + return write(str, &buf[sizeof(buf)] - str); + } + /** Print a file's size in bytes. + * \param[in] pr Prtin stream for the output. + * \return The number of bytes printed. + */ + size_t printFileSize(print_t* pr); + /** Print a file's modify date and time + * + * \param[in] pr Print stream for output. + * + * \return true for success or false for failure. + */ + size_t printModifyDateTime(print_t* pr); + /** Print a file's name + * + * \param[in] pr Print stream for output. + * + * \return true for success or false for failure. + */ + size_t printName(print_t* pr); + /** Read the next byte from a file. + * + * \return For success read returns the next byte in the file as an int. + * If an error occurs or end of file is reached -1 is returned. + */ + int read() { + uint8_t b; + return read(&b, 1) == 1 ? b : -1; + } + /** Read data from a file starting at the current position. + * + * \param[out] buf Pointer to the location that will receive the data. + * + * \param[in] count Maximum number of bytes to read. + * + * \return For success read() returns the number of bytes read. + * A value less than \a nbyte, including zero, will be returned + * if end of file is reached. + * If an error occurs, read() returns -1. + */ + int read(void* buf, size_t count); + /** Remove a file. + * + * The directory entry and all data for the file are deleted. + * + * \note This function should not be used to delete the 8.3 version of a + * file that has a long name. For example if a file has the long name + * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT". + * + * \return true for success or false for failure. + */ + bool remove(); + /** Remove a file. + * + * The directory entry and all data for the file are deleted. + * + * \param[in] path Path for the file to be removed. + * + * Example use: dirFile.remove(filenameToRemove); + * + * \note This function should not be used to delete the 8.3 version of a + * file that has a long name. For example if a file has the long name + * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT". + * + * \return true for success or false for failure. + */ + bool remove(const ExChar_t* path); + /** Rename a file or subdirectory. + * + * \param[in] newPath New path name for the file/directory. + * + * \return true for success or false for failure. + */ + bool rename(const ExChar_t* newPath); + /** Rename a file or subdirectory. + * + * \param[in] dirFile Directory for the new path. + * \param[in] newPath New path name for the file/directory. + * + * \return true for success or false for failure. + */ + bool rename(ExFatFile* dirFile, const ExChar_t* newPath); + /** Set the file's current position to zero. */ + void rewind() { + seekSet(0); + } + /** Remove a directory file. + * + * The directory file will be removed only if it is empty and is not the + * root directory. rmdir() follows DOS and Windows and ignores the + * read-only attribute for the directory. + * + * \note This function should not be used to delete the 8.3 version of a + * directory that has a long name. For example if a directory has the + * long name "New folder" you should not delete the 8.3 name "NEWFOL~1". + * + * \return true for success or false for failure. + */ + bool rmdir(); + /** Set the files position to current position + \a pos. See seekSet(). + * \param[in] offset The new position in bytes from the current position. + * \return true for success or false for failure. + */ + bool seekCur(int64_t offset) { + return seekSet(m_curPosition + offset); + } + /** Set the files position to end-of-file + \a offset. See seekSet(). + * Can't be used for directory files since file size is not defined. + * \param[in] offset The new position in bytes from end-of-file. + * \return true for success or false for failure. + */ + bool seekEnd(int64_t offset = 0) { + return isFile() ? seekSet(m_validLength + offset) : false; + } + /** Sets a file's position. + * + * \param[in] pos The new position in bytes from the beginning of the file. + * + * \return true for success or false for failure. + */ + bool seekSet(uint64_t pos); + /** The sync() call causes all modified data and directory fields + * to be written to the storage device. + * + * \return true for success or false for failure. + */ + bool sync(); + /** Truncate a file at the current file position. + * + * \return true for success or false for failure. + */ + /** Set a file's timestamps in its directory entry. + * + * \param[in] flags Values for \a flags are constructed by a + * bitwise-inclusive OR of flags from the following list + * + * T_ACCESS - Set the file's last access date and time. + * + * T_CREATE - Set the file's creation date and time. + * + * T_WRITE - Set the file's last write/modification date and time. + * + * \param[in] year Valid range 1980 - 2107 inclusive. + * + * \param[in] month Valid range 1 - 12 inclusive. + * + * \param[in] day Valid range 1 - 31 inclusive. + * + * \param[in] hour Valid range 0 - 23 inclusive. + * + * \param[in] minute Valid range 0 - 59 inclusive. + * + * \param[in] second Valid range 0 - 59 inclusive + * + * \note It is possible to set an invalid date since there is no check for + * the number of days in a month. + * + * \note + * Modify and access timestamps may be overwritten if a date time callback + * function has been set by dateTimeCallback(). + * + * \return true for success or false for failure. + */ + bool timestamp(uint8_t flags, uint16_t year, uint8_t month, uint8_t day, + uint8_t hour, uint8_t minute, uint8_t second); + /** Truncate a file at the current file position. + * will be maintained if it is less than or equal to \a length otherwise + * it will be set to end of file. + * + * \return true for success or false for failure. + */ + bool truncate(); + /** Truncate a file to a specified length. The current file position + * will be set to end of file. + * + * \param[in] length The desired length for the file. + * + * \return true for success or false for failure. + */ + bool truncate(uint64_t length) { + return seekSet(length) && truncate(); + } + + /** \return The valid number of bytes in a file. */ + uint64_t validLength() const {return m_validLength;} + /** Write a string to a file. Used by the Arduino Print class. + * \param[in] str Pointer to the string. + * Use getWriteError to check for errors. + * \return count of characters written for success or -1 for failure. + */ + size_t write(const char* str) { + return write(str, strlen(str)); + } + /** Write a single byte. + * \param[in] b The byte to be written. + * \return +1 for success or zero for failure. + */ + size_t write(uint8_t b) {return write(&b, 1);} + /** Write data to an open file. + * + * \note Data is moved to the cache but may not be written to the + * storage device until sync() is called. + * + * \param[in] buf Pointer to the location of the data to be written. + * + * \param[in] count Number of bytes to write. + * + * \return For success write() returns the number of bytes written, always + * \a count. + */ + size_t write(const void* buf, size_t count); + //============================================================================ +#if USE_EXFAT_UNICODE_NAMES + // Not Implemented when Unicode is selected. + bool exists(const char* path); + size_t getName(char *name, size_t size); + bool mkdir(ExFatFile* parent, const char* path, bool pFlag = true); + bool open(ExFatVolume* vol, const char* path, int oflag); + bool open(ExFatFile* dir, const char* path, int oflag); + bool open(const char* path, int oflag = O_RDONLY); + bool remove(const char* path); + bool rename(const char* newPath); + bool rename(ExFatFile* dirFile, const char* newPath); +#endif // USE_EXFAT_UNICODE_NAMES + + private: + /** ExFatVolume allowed access to private members. */ + friend class ExFatVolume; + bool addCluster(); + bool addDirCluster(); + uint8_t setCount() const {return m_setCount;} + bool mkdir(ExFatFile* parent, ExName_t* fname); + bool openRootFile(ExFatFile* dir, + const ExChar_t* name, uint8_t nameLength, oflag_t oflag); + bool open(ExFatFile* dirFile, ExName_t* fname, oflag_t oflag) { + return openRootFile(dirFile, fname->lfn, fname->len, oflag); + } + bool parsePathName(const ExChar_t* path, + ExName_t* fname, const ExChar_t** ptr); + uint32_t curCluster() const {return m_curCluster;} + ExFatVolume* volume() const {return m_vol;} + bool syncDir(); + //---------------------------------------------------------------------------- + static const uint8_t WRITE_ERROR = 0X1; + static const uint8_t READ_ERROR = 0X2; + + /** This file has not been opened. */ + static const uint8_t FILE_ATTR_CLOSED = 0; + /** File is read-only. */ + static const uint8_t FILE_ATTR_READ_ONLY = EXFAT_ATTRIB_READ_ONLY; + /** File should be hidden in directory listings. */ + static const uint8_t FILE_ATTR_HIDDEN = EXFAT_ATTRIB_HIDDEN; + /** Entry is for a system file. */ + static const uint8_t FILE_ATTR_SYSTEM = EXFAT_ATTRIB_SYSTEM; + /** Entry for normal data file */ + static const uint8_t FILE_ATTR_FILE = 0X08; + /** Entry is for a subdirectory */ + static const uint8_t FILE_ATTR_SUBDIR = EXFAT_ATTRIB_DIRECTORY; + static const uint8_t FILE_ATTR_ARCHIVE = EXFAT_ATTRIB_ARCHIVE; + /** Root directory */ + static const uint8_t FILE_ATTR_ROOT = 0X40; + /** Directory type bits */ + static const uint8_t FILE_ATTR_DIR = FILE_ATTR_SUBDIR | FILE_ATTR_ROOT; + /** Attributes to copy from directory entry */ + static const uint8_t FILE_ATTR_COPY = EXFAT_ATTRIB_READ_ONLY | + EXFAT_ATTRIB_HIDDEN | EXFAT_ATTRIB_SYSTEM | + EXFAT_ATTRIB_DIRECTORY | EXFAT_ATTRIB_ARCHIVE; + + static const uint8_t FILE_FLAG_READ = 0X01; + static const uint8_t FILE_FLAG_WRITE = 0X02; + static const uint8_t FILE_FLAG_APPEND = 0X08; + static const uint8_t FILE_FLAG_CONTIGUOUS = 0X40; + static const uint8_t FILE_FLAG_DIR_DIRTY = 0X80; + + + uint64_t m_curPosition; + uint64_t m_dataLength; + uint64_t m_validLength; + uint32_t m_curCluster; + uint32_t m_firstCluster; + ExFatVolume* m_vol; + DirPos_t m_dirPos; + uint8_t m_setCount; + uint8_t m_attributes = FILE_ATTR_CLOSED; + uint8_t m_error = 0; + uint8_t m_flags = 0; +}; + +#include "../common/ArduinoFiles.h" +/** + * \class ExFile + * \brief exFAT file with Arduino Stream. + */ +class ExFile : public StreamFile { + public: + /** Opens the next file or folder in a directory. + * + * \param[in] oflag open flags. + * \return a FatStream object. + */ + ExFile openNextFile(oflag_t oflag = O_RDONLY) { + ExFile tmpFile; + tmpFile.openNext(this, oflag); + return tmpFile; + } +}; +#endif // ExFatFile_h diff --git a/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatFilePrint.cpp b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatFilePrint.cpp new file mode 100644 index 0000000..8b301c1 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatFilePrint.cpp @@ -0,0 +1,181 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define DBG_FILE "ExFatFilePrint.cpp" +#include "../common/DebugMacros.h" +#include "ExFatFile.h" +#include "upcase.h" +#include "ExFatVolume.h" +//------------------------------------------------------------------------------ +bool ExFatFile::ls(print_t* pr) { + ExFatFile file; + if (!isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + rewind(); + while (file.openNext(this, O_RDONLY)) { + if (!file.isHidden()) { + file.printName(pr); + if (file.isDir()) { + pr->write('/'); + } + pr->write('\r'); + pr->write('\n'); + } + file.close(); + } + if (getError()) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::ls(print_t* pr, uint8_t flags, uint8_t indent) { + ExFatFile file; + if (!isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + rewind(); + while (file.openNext(this, O_RDONLY)) { + // indent for dir level + if (!file.isHidden() || (flags & LS_A)) { + for (uint8_t i = 0; i < indent; i++) { + pr->write(' '); + } + if (flags & LS_DATE) { + file.printModifyDateTime(pr); + pr->write(' '); + } + if (flags & LS_SIZE) { + file.printFileSize(pr); + pr->write(' '); + } + file.printName(pr); + if (file.isDir()) { + pr->write('/'); + } + pr->write('\r'); + pr->write('\n'); + if ((flags & LS_R) && file.isDir()) { + file.ls(pr, flags, indent + 2); + } + } + file.close(); + } + if (getError()) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +size_t ExFatFile::printAccessDateTime(print_t* pr) { + uint16_t date; + uint16_t time; + if (getAccessDateTime(&date, &time)) { + return fsPrintDateTime(pr, date, time); + } + return 0; +} +//------------------------------------------------------------------------------ +size_t ExFatFile::printCreateDateTime(print_t* pr) { + uint16_t date; + uint16_t time; + if (getCreateDateTime(&date, &time)) { + return fsPrintDateTime(pr, date, time); + } + return 0; +} +//------------------------------------------------------------------------------ +size_t ExFatFile::printFileSize(print_t* pr) { + uint64_t n = m_validLength; + char buf[21]; + char *str = &buf[sizeof(buf) - 1]; + char *bgn = str - 12; + *str = '\0'; + do { + uint64_t m = n; + n /= 10; + *--str = m - 10*n + '0'; + } while (n); + while (str > bgn) { + *--str = ' '; + } + return pr->write(str); +} +//------------------------------------------------------------------------------ +size_t ExFatFile::printModifyDateTime(print_t* pr) { + uint16_t date; + uint16_t time; + if (getModifyDateTime(&date, &time)) { + return fsPrintDateTime(pr, date, time); + } + return 0; +} +//------------------------------------------------------------------------------ +size_t ExFatFile::printName(print_t* pr) { + DirName_t* dn; + DirPos_t pos = m_dirPos; + size_t n = 0; + uint8_t in; + uint8_t buf[15]; + if (!isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t is = 1; is < m_setCount; is++) { + if (m_vol->dirSeek(&pos, is == 1 ? 64: 32) != 1) { + DBG_FAIL_MACRO; + goto fail; + } + dn = reinterpret_cast + (m_vol->dirCache(&pos, FsCache::CACHE_FOR_READ)); + if (!dn || dn->type != EXFAT_TYPE_NAME) { + DBG_FAIL_MACRO; + goto fail; + } + for (in = 0; in < 15; in++) { + uint16_t c = getLe16(dn->unicode + 2*in); + if (!c) { + break; + } + buf[in] = c < 0X7f ? c : '?'; + n++; + } + pr->write(buf, in); + } + return n; + + fail: + return 0; +} diff --git a/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatFileWrite.cpp b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatFileWrite.cpp new file mode 100644 index 0000000..88731ea --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatFileWrite.cpp @@ -0,0 +1,785 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define DBG_FILE "ExFatFileWrite.cpp" +#include "../common/DebugMacros.h" +#include "ExFatFile.h" +#include "ExFatVolume.h" +#include "upcase.h" +//============================================================================== +#if READ_ONLY +bool ExFatFile::mkdir(ExFatFile* parent, const ExChar_t* path, bool pFlag) { + (void) parent; + (void)path; + (void)pFlag; + return false; +} +bool ExFatFile::preAllocate(uint64_t length) { + (void)length; + return false; +} +bool ExFatFile::rename(const ExChar_t* newPath) { + (void)newPath; + return false; +} +bool ExFatFile::rename(ExFatFile* dirFile, const ExChar_t* newPath) { + (void)dirFile; + (void)newPath; + return false; +} +bool ExFatFile::sync() { + return false; +} +bool ExFatFile::truncate() { + return false; +} +size_t ExFatFile::write(const void* buf, size_t nbyte) { + (void)buf; + (void)nbyte; + return false; +} +//============================================================================== +#else // READ_ONLY +//------------------------------------------------------------------------------ +static uint16_t exFatDirChecksum(const uint8_t* data, uint16_t checksum) { + bool skip = data[0] == EXFAT_TYPE_FILE; + for (size_t i = 0; i < 32; i += i == 1 && skip ? 3 : 1) { + checksum = ((checksum << 15) | (checksum >> 1)) + data[i]; + } + return checksum; +} +//------------------------------------------------------------------------------ +bool ExFatFile::addCluster() { + uint32_t find = m_vol->bitmapFind(m_curCluster ? m_curCluster + 1 : 0, 1); + if (find < 2) { + DBG_FAIL_MACRO; + goto fail; + } + if (!m_vol->bitmapModify(find, 1, 1)) { + DBG_FAIL_MACRO; + goto fail; + } + if (m_curCluster == 0) { + m_flags |= FILE_FLAG_CONTIGUOUS; + goto done; + } + if (isContiguous()) { + if (find == (m_curCluster + 1)) { + goto done; + } + // No longer contiguous so make FAT chain. + m_flags &= ~FILE_FLAG_CONTIGUOUS; + + for (uint32_t c = m_firstCluster; c < m_curCluster; c++) { + if (!m_vol->fatPut(c, c + 1)) { + DBG_FAIL_MACRO; + goto fail; + } + } + } + // New cluster is EOC. + if (!m_vol->fatPut(find, EXFAT_EOC)) { + DBG_FAIL_MACRO; + goto fail; + } + // Connect new cluster to existing chain. + if (m_curCluster) { + if (!m_vol->fatPut(m_curCluster, find)) { + DBG_FAIL_MACRO; + goto fail; + } + } + + done: + m_curCluster = find; + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::addDirCluster() { + uint32_t sector; + uint32_t dl = isRoot() ? m_vol->rootLength() : m_dataLength; + uint8_t* cache; + dl += m_vol->bytesPerCluster(); + if (dl >= 0X4000000) { + DBG_FAIL_MACRO; + goto fail; + } + if (!addCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + cache = m_vol->cacheClear(); + if (!cache) { + DBG_FAIL_MACRO; + goto fail; + } + memset(cache, 0, m_vol->bytesPerSector()); + sector = m_vol->clusterStartSector(m_curCluster); + for (uint32_t i = 0; i < m_vol->sectorsPerCluster(); i++) { + if (!m_vol->writeSector(sector + i, cache)) { + DBG_FAIL_MACRO; + goto fail; + } + } + if (!isRoot()) { + m_flags |= FILE_FLAG_DIR_DIRTY; + m_dataLength += m_vol->bytesPerCluster(); + m_validLength += m_vol->bytesPerCluster(); + } + return sync(); + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::mkdir(ExFatFile* parent, const ExChar_t* path, bool pFlag) { + ExName_t fname; + ExFatFile tmpDir; + + if (isOpen() || !parent->isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + if (isDirSeparator(*path)) { + while (isDirSeparator(*path)) { + path++; + } + if (!tmpDir.openRoot(parent->m_vol)) { + DBG_FAIL_MACRO; + goto fail; + } + parent = &tmpDir; + } + while (1) { + if (!parsePathName(path, &fname, &path)) { + DBG_FAIL_MACRO; + goto fail; + } + if (!*path) { + break; + } + if (!open(parent, &fname, O_RDONLY)) { + if (!pFlag || !mkdir(parent, &fname)) { + DBG_FAIL_MACRO; + goto fail; + } + } + tmpDir = *this; + parent = &tmpDir; + close(); + } + return mkdir(parent, &fname); + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::mkdir(ExFatFile* parent, ExName_t* fname) { + if (!parent->isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + // create a normal file + if (!open(parent, fname, O_CREAT | O_EXCL | O_RDWR)) { + DBG_FAIL_MACRO; + goto fail; + } + // convert file to directory + + m_attributes = FILE_ATTR_SUBDIR; + + // allocate and zero first cluster + if (!addDirCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + m_firstCluster = m_curCluster; + + // Set to start of dir + rewind(); + m_flags = FILE_FLAG_READ | FILE_FLAG_CONTIGUOUS | FILE_FLAG_DIR_DIRTY; + return sync(); + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::preAllocate(uint64_t length) { + uint32_t find; + uint32_t need; + if (!length || !isWritable() || m_firstCluster) { + DBG_FAIL_MACRO; + goto fail; + } + need = 1 + ((length - 1) >> m_vol->bytesPerClusterShift()); + find = m_vol->bitmapFind(0, need); + if (find < 2) { + DBG_FAIL_MACRO; + goto fail; + } + if (!m_vol->bitmapModify(find, need, 1)) { + DBG_FAIL_MACRO; + goto fail; + } + m_dataLength = length; + m_firstCluster = find; + m_flags |= FILE_FLAG_DIR_DIRTY | FILE_FLAG_CONTIGUOUS; + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::remove() { + DirPos_t pos = m_dirPos; + uint8_t* cache; + if (!isWritable()) { + DBG_FAIL_MACRO; + goto fail; + } + // Free any clusters. + if (m_firstCluster) { + if (isContiguous()) { + uint32_t nc = 1 + ((m_dataLength - 1) >> m_vol->bytesPerClusterShift()); + if (!m_vol->bitmapModify(m_firstCluster, nc, 0)) { + DBG_FAIL_MACRO; + goto fail; + } + } else { + if (!m_vol->freeChain(m_firstCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + } + } + + for (uint8_t i = 0; i <= m_setCount; i++) { + if (i && m_vol->dirSeek(&pos, 32) != 1) { + DBG_FAIL_MACRO; + goto fail; + } + cache = m_vol->dirCache(&pos, FsCache::CACHE_FOR_WRITE); + if (!cache) { + DBG_FAIL_MACRO; + goto fail; + } + // Mark entry not used. + cache[0] &= 0x7F; + } + // Set this file closed. + m_attributes = FILE_ATTR_CLOSED; + m_flags = 0; + + // Write entry to device. + return m_vol->cacheSync(); + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::rename(const ExChar_t* newPath) { + return rename(m_vol->vwd(), newPath); +} +//------------------------------------------------------------------------------ +bool ExFatFile::rename(ExFatFile* dirFile, const ExChar_t* newPath) { + ExFatFile file; + ExFatFile oldFile; + + // Must be an open file or subdirectory. + if (!(isFile() || isSubDir())) { + DBG_FAIL_MACRO; + goto fail; + } + // Can't move file to new volume. + if (m_vol != dirFile->m_vol) { + DBG_FAIL_MACRO; + goto fail; + } + if (!file.open(dirFile, newPath, O_CREAT | O_EXCL | O_WRONLY)) { + DBG_FAIL_MACRO; + goto fail; + } + oldFile = *this; + m_dirPos = file.m_dirPos; + m_setCount = file.m_setCount; + m_flags |= FILE_FLAG_DIR_DIRTY; + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + // Remove old directory entry; + oldFile.m_firstCluster = 0; + oldFile.m_flags = FILE_FLAG_WRITE; + oldFile.m_attributes = FILE_ATTR_FILE; + return oldFile.remove(); + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::rmdir() { + int n; + uint8_t dir[32]; + // must be open subdirectory + if (!isSubDir()) { + DBG_FAIL_MACRO; + goto fail; + } + rewind(); + + // make sure directory is empty + while (1) { + n = read(dir, 32); + if (n == 0) { + break; + } + if (n != 32 || dir[0] & 0X80) { + DBG_FAIL_MACRO; + goto fail; + } + if (dir[0] == 0) { + break; + } + } + // convert empty directory to normal file for remove + m_attributes = FILE_ATTR_FILE; + m_flags |= FILE_FLAG_WRITE; + return remove(); + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::sync() { + if (!isOpen()) { + return true; + } + if (m_flags & FILE_FLAG_DIR_DIRTY) { + // clear directory dirty + m_flags &= ~FILE_FLAG_DIR_DIRTY; + return syncDir(); + } + if (!m_vol->cacheSync()) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + + fail: + m_error |= WRITE_ERROR; + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::syncDir() { + DirFile_t* df; + DirStream_t* ds; + uint8_t* cache; + uint16_t checksum = 0; + uint8_t setCount = 0; + + DirPos_t pos = m_dirPos; + + for (uint8_t i = 0;; i++) { + cache = m_vol->dirCache(&pos, FsCache::CACHE_FOR_READ); + if (!cache) { + DBG_FAIL_MACRO; + goto fail; + } + switch (cache[0]) { + case EXFAT_TYPE_FILE: + df = reinterpret_cast(cache); + setCount = df->setCount; + setLe16(df->attributes, m_attributes & FILE_ATTR_COPY); + if (FsDateTime::callback) { + uint16_t date, time; + uint8_t ms10; + FsDateTime::callback(&date, &time, &ms10); + df->modifyTimeMs = ms10; + setLe16(df->modifyTime, time); + setLe16(df->modifyDate, date); + setLe16(df->accessTime, time); + setLe16(df->accessDate, date); + } + m_vol->dataCacheDirty(); + break; + + case EXFAT_TYPE_STREAM: + ds = reinterpret_cast(cache); + if (isContiguous()) { + ds->flags |= EXFAT_FLAG_CONTIGUOUS; + } else { + ds->flags &= ~EXFAT_FLAG_CONTIGUOUS; + } + setLe64(ds->validLength, m_validLength); + setLe32(ds->firstCluster, m_firstCluster); + setLe64(ds->dataLength, m_dataLength); + m_vol->dataCacheDirty(); + break; + + case EXFAT_TYPE_NAME: + break; + + default: + DBG_FAIL_MACRO; + goto fail; + break; + } + checksum = exFatDirChecksum(cache, checksum); + if (i == setCount) break; + if (m_vol->dirSeek(&pos, 32) != 1) { + DBG_FAIL_MACRO; + goto fail; + } + } + df = reinterpret_cast + (m_vol->dirCache(&m_dirPos, FsCache::CACHE_FOR_WRITE)); + if (!df) { + DBG_FAIL_MACRO; + goto fail; + } + setLe16(df->setChecksum, checksum); + if (!m_vol->cacheSync()) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + + fail: + m_error |= WRITE_ERROR; + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::timestamp(uint8_t flags, uint16_t year, uint8_t month, + uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) { + DirFile_t* df; + uint8_t* cache; + uint16_t checksum = 0; + uint8_t setCount = 0; + uint16_t date; + uint16_t time; + uint8_t ms10; + DirPos_t pos; + + if (!isFile() + || year < 1980 + || year > 2107 + || month < 1 + || month > 12 + || day < 1 + || day > 31 + || hour > 23 + || minute > 59 + || second > 59) { + DBG_FAIL_MACRO; + goto fail; + } + // update directory entry + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + + date = FS_DATE(year, month, day); + time = FS_TIME(hour, minute, second); + ms10 = second & 1 ? 100 : 0; + pos = m_dirPos; + + for (uint8_t i = 0;; i++) { + cache = m_vol->dirCache(&pos, FsCache::CACHE_FOR_READ); + if (!cache) { + DBG_FAIL_MACRO; + goto fail; + } + switch (cache[0]) { + case EXFAT_TYPE_FILE: + df = reinterpret_cast(cache); + setCount = df->setCount; + setLe16(df->attributes, m_attributes & FILE_ATTR_COPY); + m_vol->dataCacheDirty(); + if (flags & T_ACCESS) { + setLe16(df->accessTime, time); + setLe16(df->accessDate, date); + } + if (flags & T_CREATE) { + df->createTimeMs = ms10; + setLe16(df->createTime, time); + setLe16(df->createDate, date); + } + if (flags & T_WRITE) { + df->modifyTimeMs = ms10; + setLe16(df->modifyTime, time); + setLe16(df->modifyDate, date); + } + break; + + case EXFAT_TYPE_STREAM: + break; + + case EXFAT_TYPE_NAME: + break; + + default: + DBG_FAIL_MACRO; + goto fail; + break; + } + checksum = exFatDirChecksum(cache, checksum); + if (i == setCount) break; + if (m_vol->dirSeek(&pos, 32) != 1) { + DBG_FAIL_MACRO; + goto fail; + } + } + df = reinterpret_cast + (m_vol->dirCache(&m_dirPos, FsCache::CACHE_FOR_WRITE)); + if (!df) { + DBG_FAIL_MACRO; + goto fail; + } + setLe16(df->setChecksum, checksum); + if (!m_vol->cacheSync()) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFile::truncate() { + uint32_t toFree; + // error if not a normal file or read-only + if (!isWritable()) { + DBG_FAIL_MACRO; + goto fail; + } + if (m_firstCluster == 0) { + return true; + } + if (isContiguous()) { + uint32_t nc = 1 + ((m_dataLength - 1) >> m_vol->bytesPerClusterShift()); + if (m_curCluster) { + toFree = m_curCluster + 1; + nc -= 1 + m_curCluster - m_firstCluster; + } else { + toFree = m_firstCluster; + m_firstCluster = 0; + } + if (nc && !m_vol->bitmapModify(toFree, nc, 0)) { + DBG_FAIL_MACRO; + goto fail; + } + } else { + // need to free chain + if (m_curCluster) { + toFree = 0; + int8_t fg = m_vol->fatGet(m_curCluster, &toFree); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (fg) { + // current cluster is end of chain + if (!m_vol->fatPut(m_curCluster, EXFAT_EOC)) { + DBG_FAIL_MACRO; + goto fail; + } + } + } else { + toFree = m_firstCluster; + m_firstCluster = 0; + } + if (toFree) { + if (!m_vol->freeChain(toFree)) { + DBG_FAIL_MACRO; + goto fail; + } + } + } + m_dataLength = m_curPosition; + m_validLength = m_curPosition; + m_flags |= FILE_FLAG_DIR_DIRTY; + return sync(); + + fail: + return false; +} +//------------------------------------------------------------------------------ +size_t ExFatFile::write(const void* buf, size_t nbyte) { + // convert void* to uint8_t* - must be before goto statements + const uint8_t* src = reinterpret_cast(buf); + uint8_t* cache; + uint8_t cacheOption; + uint16_t sectorOffset; + uint32_t sector; + uint32_t clusterOffset; + + // number of bytes left to write - must be before goto statements + size_t toWrite = nbyte; + size_t n; + // error if not an open file or is read-only + if (!isWritable()) { + DBG_FAIL_MACRO; + goto fail; + } + // seek to end of file if append flag + if ((m_flags & FILE_FLAG_APPEND)) { + if (!seekSet(m_validLength)) { + DBG_FAIL_MACRO; + goto fail; + } + } + while (toWrite) { + clusterOffset = m_curPosition & m_vol->clusterMask(); + sectorOffset = clusterOffset & m_vol->sectorMask(); + if (clusterOffset == 0) { + // start of new cluster + if (m_curCluster != 0) { + int fg; + + if (isContiguous()) { + uint32_t lc = m_firstCluster; + lc += (m_dataLength - 1) >> m_vol->bytesPerClusterShift(); + if (m_curCluster < lc) { + m_curCluster++; + fg = 1; + } else { + fg = 0; + } + } else { + fg = m_vol->fatGet(m_curCluster, &m_curCluster); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + } + if (fg == 0) { + // add cluster if at end of chain + if (!addCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + } + } else { + if (m_firstCluster == 0) { + // allocate first cluster of file + if (!addCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + m_firstCluster = m_curCluster; + } else { + m_curCluster = m_firstCluster; + } + } + } + // sector for data write + sector = m_vol->clusterStartSector(m_curCluster) + + (clusterOffset >> m_vol->bytesPerSectorShift()); + + if (sectorOffset != 0 || toWrite < m_vol->bytesPerSector()) { + // partial sector - must use cache + // max space in sector + n = m_vol->bytesPerSector() - sectorOffset; + // lesser of space and amount to write + if (n > toWrite) { + n = toWrite; + } + + if (sectorOffset == 0 && m_curPosition >= m_validLength) { + // start of new sector don't need to read into cache + cacheOption = FsCache::CACHE_RESERVE_FOR_WRITE; + } else { + // rewrite part of sector + cacheOption = FsCache::CACHE_FOR_WRITE; + } + cache = m_vol->dataCacheGet(sector, cacheOption); + if (!cache) { + DBG_FAIL_MACRO; + goto fail; + } + uint8_t* dst = cache + sectorOffset; + memcpy(dst, src, n); + if (m_vol->bytesPerSector() == (n + sectorOffset)) { + // Force write if sector is full - improves large writes. + if (!m_vol->dataCacheSync()) { + DBG_FAIL_MACRO; + goto fail; + } + } +#if USE_MULTI_SECTOR_IO + } else if (toWrite >= 2*m_vol->bytesPerSector()) { + // use multiple sector write command + uint32_t ns = toWrite >> m_vol->bytesPerSectorShift(); + // Limit writes to current cluster. + uint32_t maxNs = m_vol->sectorsPerCluster() + - (clusterOffset >> m_vol->bytesPerSectorShift()); + if (ns > maxNs) { + ns = maxNs; + } + n = ns << m_vol->bytesPerSectorShift(); + if (!m_vol->cacheSafeWrite(sector, src, ns)) { + DBG_FAIL_MACRO; + goto fail; + } +#endif // USE_MULTI_SECTOR_IO + } else { + n = m_vol->bytesPerSector(); + if (!m_vol->cacheSafeWrite(sector, src)) { + DBG_FAIL_MACRO; + goto fail; + } + } + m_curPosition += n; + src += n; + toWrite -= n; + if (m_curPosition > m_validLength) { + m_flags |= FILE_FLAG_DIR_DIRTY; + m_validLength = m_curPosition; + } + } + if (m_curPosition > m_dataLength) { + m_dataLength = m_curPosition; + // update fileSize and insure sync will update dir entry + m_flags |= FILE_FLAG_DIR_DIRTY; + } else if (FsDateTime::callback) { + // insure sync will update modified date and time + m_flags |= FILE_FLAG_DIR_DIRTY; + } + return nbyte; + + fail: + // return for write error + m_error |= WRITE_ERROR; + return -1; +} +#endif // READ_ONLY diff --git a/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatFormatter.cpp b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatFormatter.cpp new file mode 100644 index 0000000..e19282b --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatFormatter.cpp @@ -0,0 +1,362 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define DBG_FILE "ExFatFormatter.cpp" +#include "../common/DebugMacros.h" +#include "ExFatFormatter.h" +//------------------------------------------------------------------------------ +// Formatter assumes 512 byte sectors. +const uint32_t BOOT_BACKUP_OFFSET = 12; +const uint16_t BYTES_PER_SECTOR = 512; +const uint16_t SECTOR_MASK = BYTES_PER_SECTOR - 1; +const uint8_t BYTES_PER_SECTOR_SHIFT = 9; +const uint16_t MINIMUM_UPCASE_SKIP = 512; +const uint32_t BITMAP_CLUSTER = 2; +const uint32_t UPCASE_CLUSTER = 3; +const uint32_t ROOT_CLUSTER = 4; +//------------------------------------------------------------------------------ +#define PRINT_FORMAT_PROGRESS 1 +#if !PRINT_FORMAT_PROGRESS +#define writeMsg(pr, str) +#elif defined(__AVR__) +#define writeMsg(pr, str) if (pr) pr->print(F(str)) +#else // PRINT_FORMAT_PROGRESS +#define writeMsg(pr, str) if (pr) pr->write(str) +#endif // PRINT_FORMAT_PROGRESS +//------------------------------------------------------------------------------ +bool ExFatFormatter::format(BlockDevice* dev, uint8_t* secBuf, print_t* pr) { +#if !PRINT_FORMAT_PROGRESS +(void)pr; +#endif // !PRINT_FORMAT_PROGRESS + MbrSector_t* mbr; + ExFatPbs_t* pbs; + DirUpcase_t* dup; + DirBitmap_t* dbm; + DirLabel_t* label; + uint32_t bitmapSize; + uint32_t checksum = 0; + uint32_t clusterCount; + uint32_t clusterHeapOffset; + uint32_t fatLength; + uint32_t fatOffset; + uint32_t m; + uint32_t ns; + uint32_t partitionOffset; + uint32_t sector; + uint32_t sectorsPerCluster; + uint32_t volumeLength; + uint32_t sectorCount; + uint8_t sectorsPerClusterShift; + uint8_t vs; + + m_dev = dev; + m_secBuf = secBuf; + sectorCount = dev->sectorCount(); + // Min size is 512 MB + if (sectorCount < 0X100000) { + writeMsg(pr, "Device is too small\r\n"); + DBG_FAIL_MACRO; + goto fail; + } + // Determine partition layout. + for (m = 1, vs = 0; m && sectorCount > m; m <<= 1, vs++) {} + sectorsPerClusterShift = vs < 29 ? 8 : (vs - 11)/2; + sectorsPerCluster = 1UL << sectorsPerClusterShift; + fatLength = 1UL << (vs < 27 ? 13 : (vs + 1)/2); + fatOffset = fatLength; + partitionOffset = 2*fatLength; + clusterHeapOffset = 2*fatLength; + clusterCount = (sectorCount - 4*fatLength) >> sectorsPerClusterShift; + volumeLength = clusterHeapOffset + (clusterCount << sectorsPerClusterShift); + + // make Master Boot Record. Use fake CHS. + memset(secBuf, 0, BYTES_PER_SECTOR); + mbr = reinterpret_cast(secBuf); + mbr->part->beginCHS[0] = 1; + mbr->part->beginCHS[1] = 1; + mbr->part->beginCHS[2] = 0; + mbr->part->type = 7; + mbr->part->endCHS[0] = 0XFE; + mbr->part->endCHS[1] = 0XFF; + mbr->part->endCHS[2] = 0XFF; + setLe32(mbr->part->relativeSectors, partitionOffset); + setLe32(mbr->part->totalSectors, volumeLength); + setLe16(mbr->signature, MBR_SIGNATURE); + if (!dev->writeSector(0, secBuf)) { + DBG_FAIL_MACRO; + goto fail; + } + // Partition Boot sector. + memset(secBuf, 0, BYTES_PER_SECTOR); + pbs = reinterpret_cast(secBuf); + pbs->jmpInstruction[0] = 0XEB; + pbs->jmpInstruction[1] = 0X76; + pbs->jmpInstruction[2] = 0X90; + pbs->oemName[0] = 'E'; + pbs->oemName[1] = 'X'; + pbs->oemName[2] = 'F'; + pbs->oemName[3] = 'A'; + pbs->oemName[4] = 'T'; + pbs->oemName[5] = ' '; + pbs->oemName[6] = ' '; + pbs->oemName[7] = ' '; + setLe64(pbs->bpb.partitionOffset, partitionOffset); + setLe64(pbs->bpb.volumeLength, volumeLength); + setLe32(pbs->bpb.fatOffset, fatOffset); + setLe32(pbs->bpb.fatLength, fatLength); + setLe32(pbs->bpb.clusterHeapOffset, clusterHeapOffset); + setLe32(pbs->bpb.clusterCount, clusterCount); + setLe32(pbs->bpb.rootDirectoryCluster, ROOT_CLUSTER); + setLe32(pbs->bpb.volumeSerialNumber, sectorCount); + setLe16(pbs->bpb.fileSystemRevision, 0X100); + setLe16(pbs->bpb.volumeFlags, 0); + pbs->bpb.bytesPerSectorShift = BYTES_PER_SECTOR_SHIFT; + pbs->bpb.sectorsPerClusterShift = sectorsPerClusterShift; + pbs->bpb.numberOfFats = 1; + pbs->bpb.driveSelect = 0X80; + pbs->bpb.percentInUse = 0; + + // Fill boot code like official SDFormatter. + for (size_t i = 0; i < sizeof(pbs->bootCode); i++) { + pbs->bootCode[i] = 0XF4; + } + setLe16(pbs->signature, PBR_SIGNATURE); + for (size_t i = 0; i < BYTES_PER_SECTOR; i++) { + if (i == offsetof(ExFatPbs_t, bpb.volumeFlags[0]) || + i == offsetof(ExFatPbs_t, bpb.volumeFlags[1]) || + i == offsetof(ExFatPbs_t, bpb.percentInUse)) { + continue; + } + checksum = exFatChecksum(checksum, secBuf[i]); + } + sector = partitionOffset; + if (!dev->writeSector(sector, secBuf) || + !dev->writeSector(sector + BOOT_BACKUP_OFFSET , secBuf)) { + DBG_FAIL_MACRO; + goto fail; + } + sector++; + // Write eight Extended Boot Sectors. + memset(secBuf, 0, BYTES_PER_SECTOR); + setLe16(pbs->signature, PBR_SIGNATURE); + for (int j = 0; j < 8; j++) { + for (size_t i = 0; i < BYTES_PER_SECTOR; i++) { + checksum = exFatChecksum(checksum, secBuf[i]); + } + if (!dev->writeSector(sector, secBuf) || + !dev->writeSector(sector + BOOT_BACKUP_OFFSET , secBuf)) { + DBG_FAIL_MACRO; + goto fail; + } + sector++; + } + // Write OEM Parameter Sector and reserved sector. + memset(secBuf, 0, BYTES_PER_SECTOR); + for (int j = 0; j < 2; j++) { + for (size_t i = 0; i < BYTES_PER_SECTOR; i++) { + checksum = exFatChecksum(checksum, secBuf[i]); + } + if (!dev->writeSector(sector, secBuf) || + !dev->writeSector(sector + BOOT_BACKUP_OFFSET , secBuf)) { + DBG_FAIL_MACRO; + goto fail; + } + sector++; + } + // Write Boot CheckSum Sector. + for (size_t i = 0; i < BYTES_PER_SECTOR; i += 4) { + setLe32(secBuf + i, checksum); + } + if (!dev->writeSector(sector, secBuf) || + !dev->writeSector(sector + BOOT_BACKUP_OFFSET , secBuf)) { + DBG_FAIL_MACRO; + goto fail; + } + // Initialize FAT. + writeMsg(pr, "Writing FAT "); + sector = partitionOffset + fatOffset; + ns = ((clusterCount + 2)*4 + BYTES_PER_SECTOR - 1)/BYTES_PER_SECTOR; + + memset(secBuf, 0, BYTES_PER_SECTOR); + // Allocate two reserved clusters, bitmap, upcase, and root clusters. + secBuf[0] = 0XF8; + for (size_t i = 1; i < 20; i++) { + secBuf[i] = 0XFF; + } + for (uint32_t i = 0; i < ns; i++) { + if (i%(ns/32) == 0) { + writeMsg(pr, "."); + } + if (!dev->writeSector(sector + i, secBuf)) { + DBG_FAIL_MACRO; + goto fail; + } + if (i == 0) { + memset(secBuf, 0, BYTES_PER_SECTOR); + } + } + writeMsg(pr, "\r\n"); + // Write cluster two, bitmap. + sector = partitionOffset + clusterHeapOffset; + bitmapSize = (clusterCount + 7)/8; + ns = (bitmapSize + BYTES_PER_SECTOR - 1)/BYTES_PER_SECTOR; + if (ns > sectorsPerCluster) { + DBG_FAIL_MACRO; + goto fail; + } + memset(secBuf, 0, BYTES_PER_SECTOR); + // Allocate clusters for bitmap, upcase, and root. + secBuf[0] = 0X7; + for (uint32_t i = 0; i < ns; i++) { + if (!dev->writeSector(sector + i, secBuf)) { + DBG_FAIL_MACRO; + goto fail; + } + if (i == 0) { + secBuf[0] = 0; + } + } + // Write cluster three, upcase table. + writeMsg(pr, "Writing upcase table\r\n"); + if (!writeUpcase(partitionOffset + clusterHeapOffset + sectorsPerCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + if (m_upcaseSize > BYTES_PER_SECTOR*sectorsPerCluster) { + DBG_FAIL_MACRO; + goto fail; + } + // Initialize first sector of root. + writeMsg(pr, "Writing root\r\n"); + ns = sectorsPerCluster; + sector = partitionOffset + clusterHeapOffset + 2*sectorsPerCluster; + memset(secBuf, 0, BYTES_PER_SECTOR); + + // Unused Label entry. + label = reinterpret_cast(secBuf); + label->type = EXFAT_TYPE_LABEL & 0X7F; + + // bitmap directory entry. + dbm = reinterpret_cast(secBuf + 32); + dbm->type = EXFAT_TYPE_BITMAP; + setLe32(dbm->firstCluster, BITMAP_CLUSTER); + setLe64(dbm->size, bitmapSize); + + // upcase directory entry. + dup = reinterpret_cast(secBuf +64); + dup->type = EXFAT_TYPE_UPCASE; + setLe32(dup->checksum, m_upcaseChecksum); + setLe32(dup->firstCluster, UPCASE_CLUSTER); + setLe64(dup->size, m_upcaseSize); + + // Write root, cluster four. + for (uint32_t i = 0; i < ns; i++) { + if (!dev->writeSector(sector + i, secBuf)) { + DBG_FAIL_MACRO; + goto fail; + } + if (i == 0) { + memset(secBuf, 0, BYTES_PER_SECTOR); + } + } + writeMsg(pr, "Format done\r\n"); + return true; + + fail: + writeMsg(pr, "Format failed\r\n"); + return false; +} +//------------------------------------------------------------------------------ +bool ExFatFormatter::syncUpcase() { + uint16_t index = m_upcaseSize & SECTOR_MASK; + if (!index) { + return true; + } + for (size_t i = index; i < BYTES_PER_SECTOR; i++) { + m_secBuf[i] = 0; + } + return m_dev->writeSector(m_upcaseSector, m_secBuf); +} +//------------------------------------------------------------------------------ +bool ExFatFormatter::writeUpcaseByte(uint8_t b) { + uint16_t index = m_upcaseSize & SECTOR_MASK; + m_secBuf[index] = b; + m_upcaseChecksum = exFatChecksum(m_upcaseChecksum, b); + m_upcaseSize++; + if (index == SECTOR_MASK) { + return m_dev->writeSector(m_upcaseSector++, m_secBuf); + } + return true; +} +//------------------------------------------------------------------------------ +bool ExFatFormatter::writeUpcaseUnicode(uint16_t unicode) { + return writeUpcaseByte(unicode) && writeUpcaseByte(unicode >> 8); +} +//------------------------------------------------------------------------------ +bool ExFatFormatter::writeUpcase(uint32_t sector) { + uint32_t n; + uint32_t ns; + uint32_t ch = 0; + uint16_t uc; + + m_upcaseSize = 0; + m_upcaseChecksum = 0; + m_upcaseSector = sector; + + while (ch < 0X10000) { + uc = toUpcase(ch); + if (uc != ch) { + if (!writeUpcaseUnicode(uc)) { + DBG_FAIL_MACRO; + goto fail; + } + ch++; + } else { + for (n = ch + 1; n < 0X10000 && n == toUpcase(n); n++) {} + ns = n - ch; + if (ns >= MINIMUM_UPCASE_SKIP) { + if (!writeUpcaseUnicode(0XFFFF) || !writeUpcaseUnicode(ns)) { + DBG_FAIL_MACRO; + goto fail; + } + ch = n; + } else { + while (ch < n) { + if (!writeUpcaseUnicode(ch++)) { + DBG_FAIL_MACRO; + goto fail; + } + } + } + } + } + if (!syncUpcase()) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + + fail: + return false; +} diff --git a/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatFormatter.h b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatFormatter.h new file mode 100644 index 0000000..5b65c70 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatFormatter.h @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef ExFatFormatter_h +#define ExFatFormatter_h +#include "ExFatConfig.h" +#include "../common/SysCall.h" +#include "../common/BlockDevice.h" +#include "upcase.h" +/** + * \class ExFatFormatter + * \brief Format an exFAT volume. + */ +class ExFatFormatter { + public: + /** + * Format an exFAT volume. + * + * \param[in] dev Block device for volume. + * \param[in] secBuf buffer for writing to volume. + * \param[in] pr Print device for progress output. + * + * \return true for success or false for failure. + */ + bool format(BlockDevice* dev, uint8_t* secBuf, print_t* pr = nullptr); + private: + bool syncUpcase(); + bool writeUpcase(uint32_t sector); + bool writeUpcaseByte(uint8_t b); + bool writeUpcaseUnicode(uint16_t unicode); + uint32_t m_upcaseSector; + uint32_t m_upcaseChecksum; + uint32_t m_upcaseSize; + BlockDevice* m_dev; + uint8_t* m_secBuf; +}; +#endif // ExFatFormatter_h diff --git a/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatLib.h b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatLib.h new file mode 100644 index 0000000..a004180 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatLib.h @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef ExFatLib_h +#define ExFatLib_h +#include "ExFatVolume.h" +#include "ExFatFile.h" +#include "ExFatFormatter.h" +#endif // ExFatLib_h diff --git a/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatPartition.cpp b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatPartition.cpp new file mode 100644 index 0000000..b7b3d57 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatPartition.cpp @@ -0,0 +1,331 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define DBG_FILE "ExFatPartition.cpp" +#include "../common/DebugMacros.h" +#include "ExFatVolume.h" +#include "../common/FsStructs.h" +//------------------------------------------------------------------------------ +// return 0 if error, 1 if no space, else start cluster. +uint32_t ExFatPartition::bitmapFind(uint32_t cluster, uint32_t count) { + uint32_t start = cluster ? cluster - 2 : m_bitmapStart; + if (start >= m_clusterCount) { + start = 0; + } + uint32_t endAlloc = start; + uint32_t bgnAlloc = start; + uint16_t sectorSize = 1 << m_bytesPerSectorShift; + size_t i = (start >> 3) & (sectorSize - 1); + uint8_t* cache; + uint8_t mask = 1 << (start & 7); + while (true) { + uint32_t sector = m_clusterHeapStartSector + + (endAlloc >> (m_bytesPerSectorShift + 3)); + cache = bitmapCacheGet(sector, FsCache::CACHE_FOR_READ); + if (!cache) { + return 0; + } + for (; i < sectorSize; i++) { + for (; mask; mask <<= 1) { + endAlloc++; + if (!(mask & cache[i])) { + if ((endAlloc - bgnAlloc) == count) { + if (cluster == 0 && count == 1) { + // Start at found sector. bitmapModify may increase this. + m_bitmapStart = bgnAlloc; + } + return bgnAlloc + 2; + } + } else { + bgnAlloc = endAlloc; + } + if (endAlloc == start) { + return 1; + } + if (endAlloc >= m_clusterCount) { + endAlloc = bgnAlloc = 0; + i = sectorSize; + break; + } + } + mask = 1; + } + i = 0; + } + return 0; +} +//------------------------------------------------------------------------------ +bool ExFatPartition::bitmapModify(uint32_t cluster, + uint32_t count, bool value) { + uint32_t sector; + uint32_t start = cluster - 2; + size_t i; + uint8_t* cache; + uint8_t mask; + cluster -= 2; + if ((start + count) > m_clusterCount) { + DBG_FAIL_MACRO; + goto fail; + } + if (value) { + if (start <= m_bitmapStart && m_bitmapStart < (start + count)) { + m_bitmapStart = (start + count) < m_clusterCount ? start + count : 0; + } + } else { + if (start < m_bitmapStart) { + m_bitmapStart = start; + } + } + mask = 1 << (start & 7); + sector = m_clusterHeapStartSector + + (start >> (m_bytesPerSectorShift + 3)); + i = (start >> 3) & m_sectorMask; + while (true) { + cache = bitmapCacheGet(sector++, FsCache::CACHE_FOR_WRITE); + if (!cache) { + DBG_FAIL_MACRO; + goto fail; + } + for (; i < m_bytesPerSector; i++) { + for (; mask; mask <<= 1) { + if (value == static_cast(cache[i] & mask)) { + DBG_FAIL_MACRO; + goto fail; + } + cache[i] ^= mask; + if (--count == 0) { + return true; + } + } + mask = 1; + } + i = 0; + } + + fail: + return false; +} +//------------------------------------------------------------------------------ +uint32_t ExFatPartition::chainSize(uint32_t cluster) { + uint32_t n = 0; + int8_t status; + do { + status = fatGet(cluster, & cluster); + if (status < 0) return 0; + n++; + } while (status); + return n; +} +//------------------------------------------------------------------------------ +uint8_t* ExFatPartition::dirCache(DirPos_t* pos, uint8_t options) { + uint32_t sector = clusterStartSector(pos->cluster); + sector += (m_clusterMask & pos->position) >> m_bytesPerSectorShift; + uint8_t* cache = dataCacheGet(sector, options); + return cache ? cache + (pos->position & m_sectorMask) : nullptr; +} +//------------------------------------------------------------------------------ +// return -1 error, 0 EOC, 1 OK +int8_t ExFatPartition::dirSeek(DirPos_t* pos, uint32_t offset) { + int8_t status; + uint32_t tmp = (m_clusterMask & pos->position) + offset; + pos->position += offset; + tmp >>= bytesPerClusterShift(); + while (tmp--) { + if (pos->isContiguous) { + pos->cluster++; + } else { + status = fatGet(pos->cluster, &pos->cluster); + if (status != 1) { + return status; + } + } + } + return 1; +} +//------------------------------------------------------------------------------ +uint8_t ExFatPartition::fatGet(uint32_t cluster, uint32_t* value) { + uint8_t* cache; + uint32_t next; + uint32_t sector; + + if (cluster > (m_clusterCount + 1)) { + DBG_FAIL_MACRO; + return -1; + } + sector = m_fatStartSector + (cluster >> (m_bytesPerSectorShift - 2)); + + cache = dataCacheGet(sector, FsCache::CACHE_FOR_READ); + if (!cache) { + return -1; + } + next = getLe32(cache + ((cluster << 2) & m_sectorMask)); + + if (next == EXFAT_EOC) { + return 0; + } + *value = next; + return 1; +} +//------------------------------------------------------------------------------ +bool ExFatPartition::fatPut(uint32_t cluster, uint32_t value) { + uint32_t sector; + uint8_t* cache; + if (cluster < 2 || cluster > (m_clusterCount + 1)) { + DBG_FAIL_MACRO; + goto fail; + } + sector = m_fatStartSector + (cluster >> (m_bytesPerSectorShift - 2)); + cache = dataCacheGet(sector, FsCache::CACHE_FOR_WRITE); + if (!cache) { + DBG_FAIL_MACRO; + goto fail; + } + setLe32(cache + ((cluster << 2) & m_sectorMask), value); + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool ExFatPartition::freeChain(uint32_t cluster) { + uint32_t next = 0; + uint32_t start = cluster; + int8_t status; + do { + status = fatGet(cluster, &next); + if (status < 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (!fatPut(cluster, 0)) { + DBG_FAIL_MACRO; + goto fail; + } + if ((cluster + 1) != next || status == 0) { + if (!bitmapModify(start, cluster - start + 1, 0)) { + DBG_FAIL_MACRO; + goto fail; + } + start = next; + } + cluster = next; + } while (status); + + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +uint32_t ExFatPartition::freeClusterCount() { + uint32_t nc = 0; + uint32_t sector = m_clusterHeapStartSector; + uint32_t usedCount = 0; + uint8_t* cache; + + while (true) { + cache = dataCacheGet(sector++, FsCache::CACHE_FOR_READ); + if (!cache) { + return 0; + } + for (size_t i = 0; i < m_bytesPerSector; i++) { + if (cache[i] == 0XFF) { + usedCount+= 8; + } else if (cache[i]) { + for (uint8_t mask = 1; mask ; mask <<=1) { + if ((mask & cache[i])) { + usedCount++; + } + } + } + nc += 8; + if (nc >= m_clusterCount) { + return m_clusterCount - usedCount; + } + } + } +} +//------------------------------------------------------------------------------ +bool ExFatPartition::init(BlockDevice* dev, uint8_t part) { + uint32_t volStart = 0; + uint8_t* cache; + pbs_t* pbs; + BpbExFat_t* bpb; + MbrSector_t* mbr; + MbrPart_t* mp; + + m_fatType = 0; + m_blockDev = dev; + cacheInit(m_blockDev); + cache = dataCacheGet(0, FsCache::CACHE_FOR_READ); + if (part > 4 || !cache) { + DBG_FAIL_MACRO; + goto fail; + } + if (part >= 1) { + mbr = reinterpret_cast(cache); + mp = &mbr->part[part - 1]; + if ((mp->boot != 0 && mp->boot != 0X80) || mp->type == 0) { + DBG_FAIL_MACRO; + goto fail; + } + volStart = getLe32(mp->relativeSectors); + cache = dataCacheGet(volStart, FsCache::CACHE_FOR_READ); + if (!cache) { + DBG_FAIL_MACRO; + goto fail; + } + } + pbs = reinterpret_cast(cache); + if (strncmp(pbs->oemName, "EXFAT", 5)) { + DBG_FAIL_MACRO; + goto fail; + } + bpb = reinterpret_cast(pbs->bpb); + if (bpb->bytesPerSectorShift != m_bytesPerSectorShift) { + DBG_FAIL_MACRO; + goto fail; + } + m_fatStartSector = volStart + getLe32(bpb->fatOffset); + m_fatLength = getLe32(bpb->fatLength); + m_clusterHeapStartSector = volStart + getLe32(bpb->clusterHeapOffset); + m_clusterCount = getLe32(bpb->clusterCount); + m_rootDirectoryCluster = getLe32(bpb->rootDirectoryCluster); + m_sectorsPerClusterShift = bpb->sectorsPerClusterShift; + m_bytesPerCluster = 1UL << (m_bytesPerSectorShift + m_sectorsPerClusterShift); + m_clusterMask = m_bytesPerCluster - 1; + // Set m_bitmapStart to first free cluster. + m_bitmapStart = 0; + bitmapFind(0, 1); + m_fatType = FAT_TYPE_EXFAT; + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +uint32_t ExFatPartition::rootLength() { + uint32_t nc = chainSize(m_rootDirectoryCluster); + return nc << bytesPerClusterShift(); +} diff --git a/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatPartition.h b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatPartition.h new file mode 100644 index 0000000..e2f0bcf --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatPartition.h @@ -0,0 +1,209 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef ExFatPartition_h +#define ExFatPartition_h +/** + * \file + * \brief ExFatPartition include file. + */ +#include "../common/SysCall.h" +#include "../common/BlockDevice.h" +#include "../common/FsCache.h" +#include "ExFatConfig.h" +#include "ExFatTypes.h" +/** Type for exFAT partition */ +const uint8_t FAT_TYPE_EXFAT = 64; + +class ExFatFile; + +//============================================================================== +/** + * \class ExFatPartition + * \brief Access exFat partitions on raw file devices. + */ +class ExFatPartition { + public: + ExFatPartition() {} + /** \return the number of bytes in a cluster. */ + uint32_t bytesPerCluster() const {return m_bytesPerCluster;} + /** \return the power of two for bytesPerCluster. */ + uint8_t bytesPerClusterShift() const { + return m_bytesPerSectorShift + m_sectorsPerClusterShift; + } + /** \return the number of bytes in a sector. */ + uint16_t bytesPerSector() const {return m_bytesPerSector;} + /** \return the power of two for bytesPerSector. */ + uint8_t bytesPerSectorShift() const {return m_bytesPerSectorShift;} + + /** Clear the cache and returns a pointer to the cache. Not for normal apps. + * \return A pointer to the cache buffer or zero if an error occurs. + */ + uint8_t* cacheClear() { + return m_dataCache.clear(); + } + /** \return the cluster count for the partition. */ + uint32_t clusterCount() const {return m_clusterCount;} + /** \return the cluster heap start sector. */ + uint32_t clusterHeapStartSector() const {return m_clusterHeapStartSector;} + /** \return the FAT length in sectors */ + uint32_t fatLength() const {return m_fatLength;} + /** \return the FAT start sector number. */ + uint32_t fatStartSector() const {return m_fatStartSector;} + /** \return Type FAT_TYPE_EXFAT for exFAT partition or zero for error. */ + uint8_t fatType() const {return m_fatType;} + /** \return the free cluster count. */ + uint32_t freeClusterCount(); + /** Initialize a exFAT partition. + * \param[in] dev The blockDevice for the partition. + * \param[in] part The partition to be used. Legal values for \a part are + * 1-4 to use the corresponding partition on a device formatted with + * a MBR, Master Boot Record, or zero if the device is formatted as + * a super floppy with the FAT boot sector in sector zero. + * + * \return true for success or false for failure. + */ + bool init(BlockDevice* dev, uint8_t part); + /** + * Check for BlockDevice busy. + * + * \return true if busy else false. + */ + bool isBusy() {return m_blockDev->isBusy();} + /** \return the root directory start cluster number. */ + uint32_t rootDirectoryCluster() const {return m_rootDirectoryCluster;} + /** \return the root directory length. */ + uint32_t rootLength(); + /** \return the number of sectors in a cluster. */ + uint32_t sectorsPerCluster() const {return 1UL << m_sectorsPerClusterShift;} +#ifndef DOXYGEN_SHOULD_SKIP_THIS + // Use sectorsPerCluster(). blocksPerCluster() will be removed in the future. + uint32_t blocksPerCluster() __attribute__ ((deprecated)) {return sectorsPerCluster();} //NOLINT +#endif // DOXYGEN_SHOULD_SKIP_THIS + /** \return the power of two for sectors per cluster. */ + uint8_t sectorsPerClusterShift() const {return m_sectorsPerClusterShift;} + //---------------------------------------------------------------------------- +#ifndef DOXYGEN_SHOULD_SKIP_THIS + void checkUpcase(print_t* pr); + bool printDir(print_t* pr, ExFatFile* file); + void dmpBitmap(print_t* pr); + void dmpCluster(print_t* pr, uint32_t cluster, + uint32_t offset, uint32_t count); + void dmpFat(print_t* pr, uint32_t start, uint32_t count); + void dmpSector(print_t* pr, uint32_t sector); + bool printVolInfo(print_t* pr); + void printFat(print_t* pr); + void printUpcase(print_t* pr); +#endif // DOXYGEN_SHOULD_SKIP_THIS + //---------------------------------------------------------------------------- + private: + /** ExFatFile allowed access to private members. */ + friend class ExFatFile; + uint32_t bitmapFind(uint32_t cluster, uint32_t count); + bool bitmapModify(uint32_t cluster, uint32_t count, bool value); + //---------------------------------------------------------------------------- + // Cache functions. + uint8_t* bitmapCacheGet(uint32_t sector, uint8_t option) { +#if USE_EXFAT_BITMAP_CACHE + return m_bitmapCache.get(sector, option); +#else // USE_EXFAT_BITMAP_CACHE + return m_dataCache.get(sector, option); +#endif // USE_EXFAT_BITMAP_CACHE + } + void cacheInit(BlockDevice* dev) { +#if USE_EXFAT_BITMAP_CACHE + m_bitmapCache.init(dev); +#endif // USE_EXFAT_BITMAP_CACHE + m_dataCache.init(dev); + } + bool cacheSync() { +#if USE_EXFAT_BITMAP_CACHE + return m_bitmapCache.sync() && m_dataCache.sync() && syncDevice(); +#else // USE_EXFAT_BITMAP_CACHE + return m_dataCache.sync() && syncDevice(); +#endif // USE_EXFAT_BITMAP_CACHE + } + void dataCacheDirty() {m_dataCache.dirty();} + void dataCacheInvalidate() {m_dataCache.invalidate();} + uint8_t* dataCacheGet(uint32_t sector, uint8_t option) { + return m_dataCache.get(sector, option); + } + uint32_t dataCacheSector() {return m_dataCache.sector();} + bool dataCacheSync() {return m_dataCache.sync();} + //---------------------------------------------------------------------------- + uint32_t clusterMask() const {return m_clusterMask;} + uint32_t clusterStartSector(uint32_t cluster) { + return m_clusterHeapStartSector + + ((cluster - 2) << m_sectorsPerClusterShift); + } + uint8_t* dirCache(DirPos_t* pos, uint8_t options); + int8_t dirSeek(DirPos_t* pos, uint32_t offset); + uint8_t fatGet(uint32_t cluster, uint32_t* value); + bool fatPut(uint32_t cluster, uint32_t value); + uint32_t chainSize(uint32_t cluster); + bool freeChain(uint32_t cluster); + uint16_t sectorMask() const {return m_sectorMask;} + bool syncDevice() { + return m_blockDev->syncDevice(); + } + bool cacheSafeRead(uint32_t sector, uint8_t* dst) { + return m_dataCache.cacheSafeRead(sector, dst); + } + bool cacheSafeWrite(uint32_t sector, const uint8_t* src) { + return m_dataCache.cacheSafeWrite(sector, src); + } + bool cacheSafeRead(uint32_t sector, uint8_t* dst, size_t count) { + return m_dataCache.cacheSafeRead(sector, dst, count); + } + bool cacheSafeWrite(uint32_t sector, const uint8_t* src, size_t count) { + return m_dataCache.cacheSafeWrite(sector, src, count); + } + bool readSector(uint32_t sector, uint8_t* dst) { + return m_blockDev->readSector(sector, dst); + } + bool writeSector(uint32_t sector, const uint8_t* src) { + return m_blockDev->writeSector(sector, src); + } + //---------------------------------------------------------------------------- + static const uint8_t m_bytesPerSectorShift = 9; + static const uint16_t m_bytesPerSector = 512; + static const uint16_t m_sectorMask = 0x1FF; + //---------------------------------------------------------------------------- +#if USE_EXFAT_BITMAP_CACHE + FsCache m_bitmapCache; +#endif // USE_EXFAT_BITMAP_CACHE + FsCache m_dataCache; + uint32_t m_bitmapStart; + uint32_t m_fatStartSector; + uint32_t m_fatLength; + uint32_t m_clusterHeapStartSector; + uint32_t m_clusterCount; + uint32_t m_rootDirectoryCluster; + uint32_t m_clusterMask; + uint32_t m_bytesPerCluster; + BlockDevice* m_blockDev; + uint8_t m_fatType = 0; + uint8_t m_sectorsPerClusterShift; +}; +#endif // ExFatPartition_h diff --git a/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatTypes.h b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatTypes.h new file mode 100644 index 0000000..8724756 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatTypes.h @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef ExFatTypes_h +#define ExFatTypes_h +#include "ExFatConfig.h" + +#if __cplusplus < 201103 +#warning no char16_t +typedef uint16_t ExChar16_t; +// #error C++11 Support required +#else // __cplusplus < 201103 +typedef char16_t ExChar16_t; +#endif // __cplusplus < 201103 + +#if USE_EXFAT_UNICODE_NAMES +/** exFAT API character type */ +typedef ExChar16_t ExChar_t; +#else // USE_EXFAT_UNICODE_NAMES +/** exFAT API character type */ +typedef char ExChar_t; +#endif // USE_EXFAT_UNICODE_NAMES +/** + * \struct DirPos_t + * \brief Internal type for position in directory file. + */ +struct DirPos_t { + /** current cluster */ + uint32_t cluster; + /** offset */ + uint32_t position; + /** directory is contiguous */ + bool isContiguous; +}; +#endif // ExFatTypes_h diff --git a/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatVolume.cpp b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatVolume.cpp new file mode 100644 index 0000000..0cb015a --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatVolume.cpp @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "ExFatVolume.h" +ExFatVolume* ExFatVolume::m_cwv = nullptr; +//----------------------------------------------------------------------------- +bool ExFatVolume::chdir(const ExChar_t* path) { + ExFatFile dir; + if (!dir.open(vwd(), path, O_RDONLY)) { + goto fail; + } + if (!dir.isDir()) { + goto fail; + } + m_vwd = dir; + return true; + + fail: + return false; +} diff --git a/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatVolume.h b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatVolume.h new file mode 100644 index 0000000..254bd32 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/ExFatLib/ExFatVolume.h @@ -0,0 +1,347 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef ExFatVolume_h +#define ExFatVolume_h +#include "ExFatPartition.h" +#include "ExFatFile.h" +//============================================================================== +/** + * \class ExFatVolume + * \brief exFAT volume. + */ +class ExFatVolume : public ExFatPartition { + public: + ExFatVolume() {} + /** + * Initialize an FatVolume object. + * \param[in] dev Device block driver. + * \param[in] setCwv Set current working volume if true. + * \param[in] part partition to initialize. + * \return true for success or false for failure. + */ + bool begin(BlockDevice* dev, bool setCwv = true, uint8_t part = 1) { + if (!init(dev, part)) { + return false; + } + if (!chdir()) { + return false; + } + if (setCwv || !m_cwv) { + m_cwv = this; + } + return true; + } + /** + * Set volume working directory to root. + * \return true for success or false for failure. + */ + bool chdir() { + m_vwd.close(); + return m_vwd.openRoot(this); + } + /** + * Set volume working directory. + * \param[in] path Path for volume working directory. + * \return true for success or false for failure. + */ + bool chdir(const ExChar_t* path); + + /** Change global working volume to this volume. */ + void chvol() {m_cwv = this;} + + /** + * Test for the existence of a file. + * + * \param[in] path Path of the file to be tested for. + * + * \return true if the file exists else false. + */ + bool exists(const ExChar_t* path) { + ExFatFile tmp; + return tmp.open(this, path, O_RDONLY); + } + + //---------------------------------------------------------------------------- + /** List the directory contents of the root directory. + * + * \param[in] pr Print stream for list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(print_t* pr, uint8_t flags = 0) { + return m_vwd.ls(pr, flags); + } + /** List the contents of a directory. + * + * \param[in] pr Print stream for list. + * + * \param[in] path directory to list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(print_t* pr, const ExChar_t* path, uint8_t flags) { + ExFatFile dir; + return dir.open(this, path, O_RDONLY) && dir.ls(pr, flags); + } + /** Make a subdirectory in the volume root directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the subdirectory. + * + * \param[in] pFlag Create missing parent directories if true. + * + * \return true for success or false for failure. + */ + bool mkdir(const ExChar_t* path, bool pFlag = true) { + ExFatFile sub; + return sub.mkdir(vwd(), path, pFlag); + } + /** open a file + * + * \param[in] path location of file to be opened. + * \param[in] oflag open flags. + * \return a ExFile object. + */ + ExFile open(const ExChar_t* path, oflag_t oflag = O_RDONLY) { + ExFile tmpFile; + tmpFile.open(this, path, oflag); + return tmpFile; + } + /** Remove a file from the volume root directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the file. + * + * \return true for success or false for failure. + */ + bool remove(const ExChar_t* path) { + ExFatFile tmp; + return tmp.open(this, path, O_WRONLY) && tmp.remove(); + } + /** Rename a file or subdirectory. + * + * \param[in] oldPath Path name to the file or subdirectory to be renamed. + * + * \param[in] newPath New path name of the file or subdirectory. + * + * The \a newPath object must not exist before the rename call. + * + * The file to be renamed must not be open. The directory entry may be + * moved and file system corruption could occur if the file is accessed by + * a file object that was opened before the rename() call. + * + * \return true for success or false for failure. + */ + bool rename(const ExChar_t* oldPath, const ExChar_t* newPath) { + ExFatFile file; + return file.open(vwd(), oldPath, O_RDONLY) && file.rename(vwd(), newPath); + } + /** Remove a subdirectory from the volume's working directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the subdirectory. + * + * The subdirectory file will be removed only if it is empty. + * + * \return true for success or false for failure. + */ + bool rmdir(const ExChar_t* path) { + ExFatFile sub; + return sub.open(this, path, O_RDONLY) && sub.rmdir(); + } + /** Truncate a file to a specified length. The current file position + * will be at the new EOF. + * + * \param[in] path A path with a valid 8.3 DOS name for the file. + * \param[in] length The desired length for the file. + * + * \return true for success or false for failure. + */ + bool truncate(const ExChar_t* path, uint64_t length) { + ExFatFile file; + if (!file.open(this, path, O_WRONLY)) { + return false; + } + return file.truncate(length); + } +#if ENABLE_ARDUINO_SERIAL + /** List the directory contents of the root directory to Serial. + * + * \return true for success or false for failure. + */ + bool ls() { + return ls(&Serial); + } + /** List the directory contents of the volume root to Serial. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(uint8_t flags) { + return ls(&Serial, flags); + } + /** List the directory contents of a directory to Serial. + * + * \param[in] path directory to list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(const ExChar_t* path, uint8_t flags = 0) { + return ls(&Serial, path, flags); + } +#endif // ENABLE_ARDUINO_SERIAL +#if ENABLE_ARDUINO_STRING + /** + * Set volume working directory. + * \param[in] path Path for volume working directory. + * \return true for success or false for failure. + */ + bool chdir(const String& path) { + return chdir(path.c_str()); + } + /** Test for the existence of a file in a directory + * + * \param[in] path Path of the file to be tested for. + * + * \return true if the file exists else false. + */ + bool exists(const String &path) { + return exists(path.c_str()); + } + /** Make a subdirectory in the volume root directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the subdirectory. + * + * \param[in] pFlag Create missing parent directories if true. + * + * \return true for success or false for failure. + */ + bool mkdir(const String &path, bool pFlag = true) { + return mkdir(path.c_str(), pFlag); + } + /** open a file + * + * \param[in] path location of file to be opened. + * \param[in] oflag open oflag flags. + * \return a ExFile object. + */ + ExFile open(const String &path, oflag_t oflag = O_RDONLY) { + return open(path.c_str(), oflag); + } + /** Remove a file from the volume root directory. + * + * \param[in] path A path with a valid name for the file. + * + * \return true for success or false for failure. + */ + bool remove(const String& path) { + return remove(path.c_str()); + } + /** Rename a file or subdirectory. + * + * \param[in] oldPath Path name to the file or subdirectory to be renamed. + * + * \param[in] newPath New path name of the file or subdirectory. + * + * The \a newPath object must not exist before the rename call. + * + * The file to be renamed must not be open. The directory entry may be + * moved and file system corruption could occur if the file is accessed by + * a file object that was opened before the rename() call. + * + * \return true for success or false for failure. + */ + bool rename(const String& oldPath, const String& newPath) { + return rename(oldPath.c_str(), newPath.c_str()); + } + /** Remove a subdirectory from the volume's working directory. + * + * \param[in] path A path with a valid name for the subdirectory. + * + * The subdirectory file will be removed only if it is empty. + * + * \return true for success or false for failure. + */ + bool rmdir(const String& path) { + return rmdir(path.c_str()); + } + /** Truncate a file to a specified length. The current file position + * will be at the new EOF. + * + * \param[in] path A path with a valid name for the file. + * \param[in] length The desired length for the file. + * + * \return true for success or false for failure. + */ + bool truncate(const String& path, uint64_t length) { + return truncate(path.c_str(), length); + } +#endif // ENABLE_ARDUINO_STRING + //============================================================================ +#if USE_EXFAT_UNICODE_NAMES + // Not implemented when Unicode is selected. + bool exists(const char* path); + bool mkdir(const char* path, bool pFlag = true); + bool remove(const char* path); + bool rename(const char* oldPath, const char* newPath); + bool rmdir(const char* path); +#endif // USE_EXFAT_UNICODE_NAMES + + private: + friend ExFatFile; + static ExFatVolume* cwv() {return m_cwv;} + ExFatFile* vwd() {return &m_vwd;} + static ExFatVolume* m_cwv; + ExFatFile m_vwd; +}; +#endif // ExFatVolume_h diff --git a/Firmware_V3/lib/SdFat/src/ExFatLib/upcase.cpp b/Firmware_V3/lib/SdFat/src/ExFatLib/upcase.cpp new file mode 100644 index 0000000..5edd9d8 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/ExFatLib/upcase.cpp @@ -0,0 +1,277 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "upcase.h" + +#ifdef __AVR__ +#include +#define TABLE_MEM PROGMEM +#define readTable8(sym) pgm_read_byte(&sym) +#define readTable16(sym) pgm_read_word(&sym) +#else // __AVR__ +#define TABLE_MEM +#define readTable8(sym) (sym) +#define readTable16(sym) (sym) +#endif // __AVR__ + +struct map16 { + uint16_t base; + int8_t off; + uint8_t count; +}; +typedef struct map16 map16_t; + +struct pair16 { + uint16_t key; + uint16_t val; +}; +typedef struct pair16 pair16_t; +//------------------------------------------------------------------------------ +static const map16_t mapTable[] TABLE_MEM = { + {0X0061, -32, 26}, + {0X00E0, -32, 23}, + {0X00F8, -32, 7 }, + {0X0100, 1, 48}, + {0X0132, 1, 6}, + {0X0139, 1, 16}, + {0X014A, 1, 46}, + {0X0179, 1, 6}, + {0X0182, 1, 4}, + {0X01A0, 1, 6}, + {0X01B3, 1, 4}, + {0X01CD, 1, 16}, + {0X01DE, 1, 18}, + {0X01F8, 1, 40}, + {0X0222, 1, 18}, + {0X0246, 1, 10}, + {0X03AD, -37, 3}, + {0X03B1, -32, 17}, + {0X03C3, -32, 9}, + {0X03D8, 1, 24}, + {0X0430, -32, 32}, + {0X0450, -80, 16}, + {0X0460, 1, 34}, + {0X048A, 1, 54}, + {0X04C1, 1, 14}, + {0X04D0, 1, 68}, + {0X0561, -48, 38}, + {0X1E00, 1, 150}, + {0X1EA0, 1, 90}, + {0X1F00, 8, 8}, + {0X1F10, 8, 6}, + {0X1F20, 8, 8}, + {0X1F30, 8, 8}, + {0X1F40, 8, 6}, + {0X1F60, 8, 8}, + {0X1F70, 74, 2}, + {0X1F72, 86, 4}, + {0X1F76, 100, 2}, + {0X1F7A, 112, 2}, + {0X1F7C, 126, 2}, + {0X1F80, 8, 8}, + {0X1F90, 8, 8}, + {0X1FA0, 8, 8}, + {0X1FB0, 8, 2}, + {0X1FD0, 8, 2}, + {0X1FE0, 8, 2}, + {0X2170, -16, 16}, + {0X24D0, -26, 26}, + {0X2C30, -48, 47}, + {0X2C67, 1, 6}, + {0X2C80, 1, 100}, + {0X2D00, 0, 38}, + {0XFF41, -32, 26}, +}; +const size_t MAP_DIM = sizeof(mapTable)/sizeof(map16_t); +//------------------------------------------------------------------------------ +static const pair16_t lookupTable[] TABLE_MEM = { + {0X00FF, 0X0178}, + {0X0180, 0X0243}, + {0X0188, 0X0187}, + {0X018C, 0X018B}, + {0X0192, 0X0191}, + {0X0195, 0X01F6}, + {0X0199, 0X0198}, + {0X019A, 0X023D}, + {0X019E, 0X0220}, + {0X01A8, 0X01A7}, + {0X01AD, 0X01AC}, + {0X01B0, 0X01AF}, + {0X01B9, 0X01B8}, + {0X01BD, 0X01BC}, + {0X01BF, 0X01F7}, + {0X01C6, 0X01C4}, + {0X01C9, 0X01C7}, + {0X01CC, 0X01CA}, + {0X01DD, 0X018E}, + {0X01F3, 0X01F1}, + {0X01F5, 0X01F4}, + {0X023A, 0X2C65}, + {0X023C, 0X023B}, + {0X023E, 0X2C66}, + {0X0242, 0X0241}, + {0X0253, 0X0181}, + {0X0254, 0X0186}, + {0X0256, 0X0189}, + {0X0257, 0X018A}, + {0X0259, 0X018F}, + {0X025B, 0X0190}, + {0X0260, 0X0193}, + {0X0263, 0X0194}, + {0X0268, 0X0197}, + {0X0269, 0X0196}, + {0X026B, 0X2C62}, + {0X026F, 0X019C}, + {0X0272, 0X019D}, + {0X0275, 0X019F}, + {0X027D, 0X2C64}, + {0X0280, 0X01A6}, + {0X0283, 0X01A9}, + {0X0288, 0X01AE}, + {0X0289, 0X0244}, + {0X028A, 0X01B1}, + {0X028B, 0X01B2}, + {0X028C, 0X0245}, + {0X0292, 0X01B7}, + {0X037B, 0X03FD}, + {0X037C, 0X03FE}, + {0X037D, 0X03FF}, + {0X03AC, 0X0386}, + {0X03C2, 0X03A3}, + {0X03CC, 0X038C}, + {0X03CD, 0X038E}, + {0X03CE, 0X038F}, + {0X03F2, 0X03F9}, + {0X03F8, 0X03F7}, + {0X03FB, 0X03FA}, + {0X04CF, 0X04C0}, + {0X1D7D, 0X2C63}, + {0X1F51, 0X1F59}, + {0X1F53, 0X1F5B}, + {0X1F55, 0X1F5D}, + {0X1F57, 0X1F5F}, + {0X1F78, 0X1FF8}, + {0X1F79, 0X1FF9}, + {0X1FB3, 0X1FBC}, + {0X1FCC, 0X1FC3}, + {0X1FE5, 0X1FEC}, + {0X1FFC, 0X1FF3}, + {0X214E, 0X2132}, + {0X2184, 0X2183}, + {0X2C61, 0X2C60}, + {0X2C76, 0X2C75}, +}; +const size_t LOOKUP_DIM = sizeof(lookupTable)/sizeof(pair16_t); +//------------------------------------------------------------------------------ +static size_t searchPair16(const pair16_t* table, size_t size, uint16_t key) { + size_t left = 0; + size_t right = size; + size_t mid; + while (right - left > 1) { + mid = left + (right - left)/2; + if (readTable16(table[mid].key) <= key) { + left = mid; + } else { + right = mid; + } + } + return left; +} +//------------------------------------------------------------------------------ +static char toUpper(char c) { + return c - ('a' <= c && c <= 'z' ? 'a' - 'A' : 0); +} +//------------------------------------------------------------------------------ +bool exFatCmpName(const DirName_t* unicode, + const ExChar16_t* name, size_t offset, size_t n) { + uint16_t u; + for (size_t i = 0; i < n; i++) { + u = getLe16(unicode->unicode + 2*i); + if (toUpcase(name[i + offset]) != toUpcase(u)) { + return false; + } + } + return true; +} +//------------------------------------------------------------------------------ +bool exFatCmpName(const DirName_t* unicode, + const char* name, size_t offset, size_t n) { + uint16_t u; + for (size_t i = 0; i < n; i++) { + u = getLe16(unicode->unicode + 2*i); + if (u >= 0x7F || toUpper(name[i + offset]) != toUpper(u)) { + return false; + } + } + return true; +} +//------------------------------------------------------------------------------ +uint16_t exFatHashName(const ExChar16_t* name, size_t n, uint16_t hash) { + for (size_t i = 0; i < n; i++) { + uint16_t c = toUpcase(name[i]); + hash = ((hash << 15) | (hash >> 1)) + (c & 0XFF); + hash = ((hash << 15) | (hash >> 1)) + (c >> 8); + } + return hash; +} +//------------------------------------------------------------------------------ +uint16_t exFatHashName(const char* name, size_t n, uint16_t hash) { + for (size_t i = 0; i < n; i++) { + uint8_t c = name[i]; + if ('a' <= c && c <= 'z') { + c -= 'a' - 'A'; + } + hash = ((hash << 15) | (hash >> 1)) + c; + hash = ((hash << 15) | (hash >> 1)); + } + return hash; +} +//------------------------------------------------------------------------------ +uint16_t toUpcase(uint16_t chr) { + uint16_t i, first; + // Optimize for simple ASCII. + if (chr < 127) { + return chr - ('a' <= chr && chr <= 'z' ? 'a' - 'A' : 0); + } + i = searchPair16(reinterpret_cast(mapTable), MAP_DIM, chr); + first = readTable16(mapTable[i].base); + if (first <= chr && (chr - first) < readTable8(mapTable[i].count)) { + int8_t off = readTable8(mapTable[i].off); + if (off == 1) { + return chr - ((chr - first) & 1); + } + return chr + (off ? off : -0x1C60); + } + i = searchPair16(lookupTable, LOOKUP_DIM, chr); + if (readTable16(lookupTable[i].key) == chr) { + return readTable16(lookupTable[i].val); + } + return chr; +} +//------------------------------------------------------------------------------ +uint32_t upcaseChecksum(uint16_t uc, uint32_t sum) { + sum = (sum << 31) + (sum >> 1) + (uc & 0XFF); + sum = (sum << 31) + (sum >> 1) + (uc >> 8); + return sum; +} diff --git a/Firmware_V3/lib/SdFat/src/ExFatLib/upcase.h b/Firmware_V3/lib/SdFat/src/ExFatLib/upcase.h new file mode 100644 index 0000000..214855b --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/ExFatLib/upcase.h @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef upcase_h +#define upcase_h +#include "ExFatFile.h" +bool exFatCmpName(const DirName_t* unicode, + const char* name, size_t offset, size_t n); +bool exFatCmpName(const DirName_t* unicode, + const ExChar16_t* name, size_t offset, size_t n); +uint16_t exFatHashName(const char* name, size_t n, uint16_t hash); +uint16_t exFatHashName(const ExChar16_t* name, size_t n, uint16_t hash); +uint16_t toUpcase(uint16_t chr); +uint32_t upcaseChecksum(uint16_t unicode, uint32_t checksum); +#endif // upcase_h diff --git a/Firmware_V3/lib/SdFat/src/FatLib/FatDbg.cpp b/Firmware_V3/lib/SdFat/src/FatLib/FatDbg.cpp new file mode 100644 index 0000000..164b4bd --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FatLib/FatDbg.cpp @@ -0,0 +1,167 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "FatVolume.h" +#include "FatFile.h" +#ifndef DOXYGEN_SHOULD_SKIP_THIS +//------------------------------------------------------------------------------ +static void printHex(print_t* pr, uint8_t h) { + if (h < 16) { + pr->write('0'); + } + pr->print(h, HEX); +} +//------------------------------------------------------------------------------ +static void printHex(print_t* pr, uint16_t val) { + bool space = true; + for (uint8_t i = 0; i < 4; i++) { + uint8_t h = (val >> (12 - 4*i)) & 15; + if (h || i == 3) { + space = false; + } + if (space) { + pr->write(' '); + } else { + pr->print(h, HEX); + } + } +} +//------------------------------------------------------------------------------ +static void printHex(print_t* pr, uint32_t val) { + bool space = true; + for (uint8_t i = 0; i < 8; i++) { + uint8_t h = (val >> (28 - 4*i)) & 15; + if (h || i == 7) { + space = false; + } + if (space) { + pr->write(' '); + } else { + pr->print(h, HEX); + } + } +} +//------------------------------------------------------------------------------ +static void printDir(print_t* pr, DirFat_t* dir) { + if (!dir->name[0] || dir->name[0] == FAT_NAME_DELETED) { + pr->println(F("Not Used")); + } else if (isFileOrSubdir(dir)) { + pr->print(F("name: ")); + pr->write(dir->name, 11); + pr->println(); + uint32_t fc = ((uint32_t)getLe16(dir->firstClusterHigh) << 16) + | getLe16(dir->firstClusterLow); + pr->print(F("firstCluster: ")); + pr->println(fc, HEX); + pr->print(F("fileSize: ")); + pr->println(getLe32(dir->fileSize)); + } else if (isLongName(dir)) { + pr->println(F("LFN")); + } else { + pr->println(F("Other")); + } +} +//------------------------------------------------------------------------------ +void FatPartition::dmpDirSector(print_t* pr, uint32_t sector) { + DirFat_t dir[16]; + if (!readSector(sector, reinterpret_cast(dir))) { + pr->println(F("dmpDir failed")); + return; + } + for (uint8_t i = 0; i < 16; i++) { + printDir(pr, dir + i); + } +} +//------------------------------------------------------------------------------ +void FatPartition::dmpRootDir(print_t* pr) { + uint32_t sector; + if (fatType() == 16) { + sector = rootDirStart(); + } else if (fatType() == 32) { + sector = clusterStartSector(rootDirStart()); + } else { + pr->println(F("dmpRootDir failed")); + return; + } + dmpDirSector(pr, sector); +} +//------------------------------------------------------------------------------ +void FatPartition::dmpSector(print_t* pr, uint32_t sector, uint8_t bits) { + uint8_t data[512]; + if (!readSector(sector, data)) { + pr->println(F("dmpSector failed")); + return; + } + for (uint16_t i = 0; i < 512;) { + if (i%32 == 0) { + if (i) { + pr->println(); + } + printHex(pr, i); + } + pr->write(' '); + if (bits == 32) { + printHex(pr, *reinterpret_cast(data + i)); + i += 4; + } else if (bits == 16) { + printHex(pr, *reinterpret_cast(data + i)); + i += 2; + } else { + printHex(pr, data[i++]); + } + } + pr->println(); +} +//------------------------------------------------------------------------------ +void FatPartition::dmpFat(print_t* pr, uint32_t start, uint32_t count) { + uint16_t nf = fatType() == 16 ? 256 : fatType() == 32 ? 128 : 0; + if (nf == 0) { + pr->println(F("Invalid fatType")); + return; + } + pr->println(F("FAT:")); + uint32_t sector = m_fatStartSector + start; + uint32_t cluster = nf*start; + for (uint32_t i = 0; i < count; i++) { + cache_t* pc = cacheFetchFat(sector + i, FsCache::CACHE_FOR_READ); + if (!pc) { + pr->println(F("cache read failed")); + return; + } + for (size_t k = 0; k < nf; k++) { + if (0 == cluster%8) { + if (k) { + pr->println(); + } + printHex(pr, cluster); + } + cluster++; + pr->write(' '); + uint32_t v = fatType() == 32 ? pc->fat32[k] : pc->fat16[k]; + printHex(pr, v); + } + pr->println(); + } +} +#endif // DOXYGEN_SHOULD_SKIP_THIS diff --git a/Firmware_V3/lib/SdFat/src/FatLib/FatFile.cpp b/Firmware_V3/lib/SdFat/src/FatLib/FatFile.cpp new file mode 100644 index 0000000..ff9e579 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FatLib/FatFile.cpp @@ -0,0 +1,1477 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define DBG_FILE "FatFile.cpp" +#include "../common/DebugMacros.h" +#include "FatFile.h" +#include "FatVolume.h" +//------------------------------------------------------------------------------ +// Add a cluster to a file. +bool FatFile::addCluster() { +#if USE_FAT_FILE_FLAG_CONTIGUOUS + uint32_t cc = m_curCluster; + if (!m_vol->allocateCluster(m_curCluster, &m_curCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + if (cc == 0) { + m_flags |= FILE_FLAG_CONTIGUOUS; + } else if (m_curCluster != (cc + 1)) { + m_flags &= ~FILE_FLAG_CONTIGUOUS; + } + m_flags |= FILE_FLAG_DIR_DIRTY; + return true; + + fail: + return false; +#else // USE_FAT_FILE_FLAG_CONTIGUOUS + m_flags |= FILE_FLAG_DIR_DIRTY; + return m_vol->allocateCluster(m_curCluster, &m_curCluster); +#endif // USE_FAT_FILE_FLAG_CONTIGUOUS +} +//------------------------------------------------------------------------------ +// Add a cluster to a directory file and zero the cluster. +// Return with first sector of cluster in the cache. +bool FatFile::addDirCluster() { + uint32_t sector; + cache_t* pc; + + if (isRootFixed()) { + DBG_FAIL_MACRO; + goto fail; + } + // max folder size + if (m_curPosition >= 512UL*4095) { + DBG_FAIL_MACRO; + goto fail; + } + if (!addCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + sector = m_vol->clusterStartSector(m_curCluster); + pc = m_vol->cacheFetchData(sector, FsCache::CACHE_RESERVE_FOR_WRITE); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + memset(pc, 0, m_vol->bytesPerSector()); + // zero rest of clusters + for (uint8_t i = 1; i < m_vol->sectorsPerCluster(); i++) { + if (!m_vol->writeSector(sector + i, pc->data)) { + DBG_FAIL_MACRO; + goto fail; + } + } + // Set position to EOF to avoid inconsistent curCluster/curPosition. + m_curPosition += m_vol->bytesPerCluster(); + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +// cache a file's directory entry +// return pointer to cached entry or null for failure +DirFat_t* FatFile::cacheDirEntry(uint8_t action) { + cache_t* pc; + pc = m_vol->cacheFetchData(m_dirSector, action); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + return pc->dir + (m_dirIndex & 0XF); + + fail: + return nullptr; +} +//------------------------------------------------------------------------------ +bool FatFile::close() { + bool rtn = sync(); + m_attributes = FILE_ATTR_CLOSED; + m_flags = 0; + return rtn; +} +//------------------------------------------------------------------------------ +bool FatFile::contiguousRange(uint32_t* bgnSector, uint32_t* endSector) { + // error if no clusters + if (!isFile() || m_firstCluster == 0) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint32_t c = m_firstCluster; ; c++) { + uint32_t next; + int8_t fg = m_vol->fatGet(c, &next); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + // check for contiguous + if (fg == 0 || next != (c + 1)) { + // error if not end of chain + if (fg) { + DBG_FAIL_MACRO; + goto fail; + } +#if USE_FAT_FILE_FLAG_CONTIGUOUS + m_flags |= FILE_FLAG_CONTIGUOUS; +#endif // USE_FAT_FILE_FLAG_CONTIGUOUS + if (bgnSector) { + *bgnSector = m_vol->clusterStartSector(m_firstCluster); + } + if (endSector) { + *endSector = m_vol->clusterStartSector(c) + + m_vol->sectorsPerCluster() - 1; + } + return true; + } + } + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::createContiguous(const char* path, uint32_t size) { + if (!open(FatVolume::cwv(), path, O_CREAT | O_EXCL | O_RDWR)) { + DBG_FAIL_MACRO; + goto fail; + } + if (preAllocate(size)) { + return true; + } + close(); + fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::createContiguous(FatFile* dirFile, + const char* path, uint32_t size) { + if (!open(dirFile, path, O_CREAT | O_EXCL | O_RDWR)) { + DBG_FAIL_MACRO; + goto fail; + } + if (preAllocate(size)) { + return true; + } + close(); + fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::dirEntry(DirFat_t* dst) { + DirFat_t* dir; + // Make sure fields on device are correct. + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + // read entry + dir = cacheDirEntry(FsCache::CACHE_FOR_READ); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // copy to caller's struct + memcpy(dst, dir, sizeof(DirFat_t)); + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +uint32_t FatFile::dirSize() { + int8_t fg; + if (!isDir()) { + return 0; + } + if (isRootFixed()) { + return 32*m_vol->rootDirEntryCount(); + } + uint16_t n = 0; + uint32_t c = isRoot32() ? m_vol->rootDirStart() : m_firstCluster; + do { + fg = m_vol->fatGet(c, &c); + if (fg < 0 || n > 4095) { + return 0; + } + n += m_vol->sectorsPerCluster(); + } while (fg); + return 512UL*n; +} +//------------------------------------------------------------------------------ +int FatFile::fgets(char* str, int num, char* delim) { + char ch; + int n = 0; + int r = -1; + while ((n + 1) < num && (r = read(&ch, 1)) == 1) { + // delete CR + if (ch == '\r') { + continue; + } + str[n++] = ch; + if (!delim) { + if (ch == '\n') { + break; + } + } else { + if (strchr(delim, ch)) { + break; + } + } + } + if (r < 0) { + // read error + return -1; + } + str[n] = '\0'; + return n; +} +//------------------------------------------------------------------------------ +void FatFile::fgetpos(fspos_t* pos) const { + pos->position = m_curPosition; + pos->cluster = m_curCluster; +} +//------------------------------------------------------------------------------ +uint32_t FatFile::firstSector() const { + return m_firstCluster ? m_vol->clusterStartSector(m_firstCluster) : 0; +} +//------------------------------------------------------------------------------ +void FatFile::fsetpos(const fspos_t* pos) { + m_curPosition = pos->position; + m_curCluster = pos->cluster; +} +//------------------------------------------------------------------------------ +bool FatFile::getAccessDate(uint16_t* pdate) { + DirFat_t dir; + if (!dirEntry(&dir)) { + DBG_FAIL_MACRO; + goto fail; + } + *pdate = getLe16(dir.accessDate); + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::getCreateDateTime(uint16_t* pdate, uint16_t* ptime) { + DirFat_t dir; + if (!dirEntry(&dir)) { + DBG_FAIL_MACRO; + goto fail; + } + *pdate = getLe16(dir.createDate); + *ptime = getLe16(dir.createTime); + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::getModifyDateTime(uint16_t* pdate, uint16_t* ptime) { + DirFat_t dir; + if (!dirEntry(&dir)) { + DBG_FAIL_MACRO; + goto fail; + } + *pdate = getLe16(dir.modifyDate); + *ptime = getLe16(dir.modifyTime); + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::isBusy() { + return m_vol->isBusy(); +} +//------------------------------------------------------------------------------ +bool FatFile::mkdir(FatFile* parent, const char* path, bool pFlag) { + fname_t fname; + FatFile tmpDir; + + if (isOpen() || !parent->isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + if (isDirSeparator(*path)) { + while (isDirSeparator(*path)) { + path++; + } + if (!tmpDir.openRoot(parent->m_vol)) { + DBG_FAIL_MACRO; + goto fail; + } + parent = &tmpDir; + } + while (1) { + if (!parsePathName(path, &fname, &path)) { + DBG_FAIL_MACRO; + goto fail; + } + if (!*path) { + break; + } + if (!open(parent, &fname, O_RDONLY)) { + if (!pFlag || !mkdir(parent, &fname)) { + DBG_FAIL_MACRO; + goto fail; + } + } + tmpDir = *this; + parent = &tmpDir; + close(); + } + return mkdir(parent, &fname); + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::mkdir(FatFile* parent, fname_t* fname) { + uint32_t sector; + DirFat_t dot; + DirFat_t* dir; + cache_t* pc; + + if (!parent->isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + // create a normal file + if (!open(parent, fname, O_CREAT | O_EXCL | O_RDWR)) { + DBG_FAIL_MACRO; + goto fail; + } + // convert file to directory + m_flags = FILE_FLAG_READ; + m_attributes = FILE_ATTR_SUBDIR; + + // allocate and zero first cluster + if (!addDirCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + m_firstCluster = m_curCluster; + // Set to start of dir + rewind(); + // force entry to device + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + // cache entry - should already be in cache due to sync() call + dir = cacheDirEntry(FsCache::CACHE_FOR_WRITE); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // change directory entry attribute + dir->attributes = FAT_ATTRIB_DIRECTORY; + + // make entry for '.' + memcpy(&dot, dir, sizeof(dot)); + dot.name[0] = '.'; + for (uint8_t i = 1; i < 11; i++) { + dot.name[i] = ' '; + } + + // cache sector for '.' and '..' + sector = m_vol->clusterStartSector(m_firstCluster); + pc = m_vol->cacheFetchData(sector, FsCache::CACHE_FOR_WRITE); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + // copy '.' to sector + memcpy(&pc->dir[0], &dot, sizeof(dot)); + // make entry for '..' + dot.name[1] = '.'; + setLe16(dot.firstClusterLow, parent->m_firstCluster & 0XFFFF); + setLe16(dot.firstClusterHigh, parent->m_firstCluster >> 16); + // copy '..' to sector + memcpy(&pc->dir[1], &dot, sizeof(dot)); + // write first sector + return m_vol->cacheSync(); + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::open(const char* path, oflag_t oflag) { + return open(FatVolume::cwv(), path, oflag); +} +//------------------------------------------------------------------------------ +bool FatFile::open(FatVolume* vol, const char* path, oflag_t oflag) { + return vol && open(vol->vwd(), path, oflag); +} +//------------------------------------------------------------------------------ +bool FatFile::open(FatFile* dirFile, const char* path, oflag_t oflag) { + FatFile tmpDir; + fname_t fname; + + // error if already open + if (isOpen() || !dirFile->isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + if (isDirSeparator(*path)) { + while (isDirSeparator(*path)) { + path++; + } + if (*path == 0) { + return openRoot(dirFile->m_vol); + } + if (!tmpDir.openRoot(dirFile->m_vol)) { + DBG_FAIL_MACRO; + goto fail; + } + dirFile = &tmpDir; + } + while (1) { + if (!parsePathName(path, &fname, &path)) { + DBG_FAIL_MACRO; + goto fail; + } + if (*path == 0) { + break; + } + if (!open(dirFile, &fname, O_RDONLY)) { + DBG_FAIL_MACRO; + goto fail; + } + tmpDir = *this; + dirFile = &tmpDir; + close(); + } + return open(dirFile, &fname, oflag); + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::open(FatFile* dirFile, uint16_t index, oflag_t oflag) { + if (index) { + // Find start of LFN. + DirLfn_t* ldir; + uint8_t n = index < 20 ? index : 20; + for (uint8_t i = 1; i <= n; i++) { + if (!dirFile->seekSet(32UL*(index - i))) { + DBG_FAIL_MACRO; + goto fail; + } + ldir = reinterpret_cast(dirFile->readDirCache()); + if (!ldir) { + DBG_FAIL_MACRO; + goto fail; + } + if (ldir->attributes != FAT_ATTRIB_LONG_NAME) { + break; + } + if (ldir->order & FAT_ORDER_LAST_LONG_ENTRY) { + if (!dirFile->seekSet(32UL*(index - i))) { + DBG_FAIL_MACRO; + goto fail; + } + break; + } + } + } else { + dirFile->rewind(); + } + if (!openNext(dirFile, oflag)) { + DBG_FAIL_MACRO; + goto fail; + } + if (dirIndex() != index) { + close(); + DBG_FAIL_MACRO; + goto fail; + } + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +// open a cached directory entry. +bool FatFile::openCachedEntry(FatFile* dirFile, uint16_t dirIndex, + oflag_t oflag, uint8_t lfnOrd) { + uint32_t firstCluster; + memset(this, 0, sizeof(FatFile)); + // location of entry in cache + m_vol = dirFile->m_vol; + m_dirIndex = dirIndex; + m_dirCluster = dirFile->m_firstCluster; + DirFat_t* dir = reinterpret_cast(m_vol->cacheAddress()); + dir += 0XF & dirIndex; + + // Must be file or subdirectory. + if (!isFileOrSubdir(dir)) { + DBG_FAIL_MACRO; + goto fail; + } + m_attributes = dir->attributes & FILE_ATTR_COPY; + if (isFileDir(dir)) { + m_attributes |= FILE_ATTR_FILE; + } + m_lfnOrd = lfnOrd; + + switch (oflag & O_ACCMODE) { + case O_RDONLY: + if (oflag & O_TRUNC) { + DBG_FAIL_MACRO; + goto fail; + } + m_flags = FILE_FLAG_READ; + break; + + case O_RDWR: + m_flags = FILE_FLAG_READ | FILE_FLAG_WRITE; + break; + + case O_WRONLY: + m_flags = FILE_FLAG_WRITE; + break; + + default: + DBG_FAIL_MACRO; + goto fail; + } + + if (m_flags & FILE_FLAG_WRITE) { + if (isSubDir() || isReadOnly()) { + DBG_FAIL_MACRO; + goto fail; + } + } + m_flags |= (oflag & O_APPEND ? FILE_FLAG_APPEND : 0); + + m_dirSector = m_vol->cacheSectorNumber(); + + // copy first cluster number for directory fields + firstCluster = ((uint32_t)getLe16(dir->firstClusterHigh) << 16) + | getLe16(dir->firstClusterLow); + + if (oflag & O_TRUNC) { + if (firstCluster && !m_vol->freeChain(firstCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + + // need to update directory entry + m_flags |= FILE_FLAG_DIR_DIRTY; + } else { + m_firstCluster = firstCluster; + m_fileSize = getLe32(dir->fileSize); + } + if ((oflag & O_AT_END) && !seekSet(m_fileSize)) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + + fail: + m_attributes = FILE_ATTR_CLOSED; + m_flags = 0; + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::openNext(FatFile* dirFile, oflag_t oflag) { + uint8_t checksum = 0; + DirLfn_t* ldir; + uint8_t lfnOrd = 0; + uint16_t index; + + // Check for not open and valid directory.. + if (isOpen() || !dirFile->isDir() || (dirFile->curPosition() & 0X1F)) { + DBG_FAIL_MACRO; + goto fail; + } + while (1) { + // read entry into cache + index = dirFile->curPosition()/32; + DirFat_t* dir = dirFile->readDirCache(); + if (!dir) { + if (dirFile->getError()) { + DBG_FAIL_MACRO; + } + goto fail; + } + // done if last entry + if (dir->name[0] == FAT_NAME_FREE) { + goto fail; + } + // skip empty slot or '.' or '..' + if (dir->name[0] == '.' || dir->name[0] == FAT_NAME_DELETED) { + lfnOrd = 0; + } else if (isFileOrSubdir(dir)) { + if (lfnOrd && checksum != lfnChecksum(dir->name)) { + DBG_FAIL_MACRO; + goto fail; + } + if (!openCachedEntry(dirFile, index, oflag, lfnOrd)) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + } else if (isLongName(dir)) { + ldir = reinterpret_cast(dir); + if (ldir->order & FAT_ORDER_LAST_LONG_ENTRY) { + lfnOrd = ldir->order & 0X1F; + checksum = ldir->checksum; + } + } else { + lfnOrd = 0; + } + } + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::openRoot(FatVolume* vol) { + // error if file is already open + if (isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + memset(this, 0, sizeof(FatFile)); + + m_vol = vol; + switch (vol->fatType()) { +#if FAT12_SUPPORT + case 12: +#endif // FAT12_SUPPORT + case 16: + m_attributes = FILE_ATTR_ROOT_FIXED; + break; + + case 32: + m_attributes = FILE_ATTR_ROOT32; + break; + + default: + DBG_FAIL_MACRO; + goto fail; + } + // read only + m_flags = FILE_FLAG_READ; + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::preAllocate(uint32_t length) { + uint32_t need; + if (!length || !isWritable() || m_firstCluster) { + DBG_FAIL_MACRO; + goto fail; + } + need = 1 + ((length - 1) >> m_vol->bytesPerClusterShift()); + // allocate clusters + if (!m_vol->allocContiguous(need, &m_firstCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + m_fileSize = length; + +#if USE_FAT_FILE_FLAG_CONTIGUOUS + // Mark contiguous and insure sync() will update dir entry + m_flags |= FILE_FLAG_PREALLOCATE | FILE_FLAG_CONTIGUOUS | FILE_FLAG_DIR_DIRTY; +#else // USE_FAT_FILE_FLAG_CONTIGUOUS + // insure sync() will update dir entry + m_flags |= FILE_FLAG_DIR_DIRTY; +#endif // USE_FAT_FILE_FLAG_CONTIGUOUS + return sync(); + + fail: + return false; +} +//------------------------------------------------------------------------------ +int FatFile::peek() { + uint32_t curPosition = m_curPosition; + uint32_t curCluster = m_curCluster; + int c = read(); + m_curPosition = curPosition; + m_curCluster = curCluster; + return c; +} +//------------------------------------------------------------------------------ +int FatFile::read(void* buf, size_t nbyte) { + int8_t fg; + uint8_t sectorOfCluster = 0; + uint8_t* dst = reinterpret_cast(buf); + uint16_t offset; + size_t toRead; + uint32_t sector; // raw device sector number + cache_t* pc; + + // error if not open for read + if (!isReadable()) { + DBG_FAIL_MACRO; + goto fail; + } + + if (isFile()) { + uint32_t tmp32 = m_fileSize - m_curPosition; + if (nbyte >= tmp32) { + nbyte = tmp32; + } + } else if (isRootFixed()) { + uint16_t tmp16 = 32*m_vol->m_rootDirEntryCount - (uint16_t)m_curPosition; + if (nbyte > tmp16) { + nbyte = tmp16; + } + } + toRead = nbyte; + while (toRead) { + size_t n; + offset = m_curPosition & m_vol->sectorMask(); // offset in sector + if (isRootFixed()) { + sector = m_vol->rootDirStart() + + (m_curPosition >> m_vol->bytesPerSectorShift()); + } else { + sectorOfCluster = m_vol->sectorOfCluster(m_curPosition); + if (offset == 0 && sectorOfCluster == 0) { + // start of new cluster + if (m_curPosition == 0) { + // use first cluster in file + m_curCluster = isRoot32() ? m_vol->rootDirStart() : m_firstCluster; +#if USE_FAT_FILE_FLAG_CONTIGUOUS + } else if (isFile() && isContiguous()) { + m_curCluster++; +#endif // USE_FAT_FILE_FLAG_CONTIGUOUS + } else { + // get next cluster from FAT + fg = m_vol->fatGet(m_curCluster, &m_curCluster); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (fg == 0) { + if (isDir()) { + break; + } + DBG_FAIL_MACRO; + goto fail; + } + } + } + sector = m_vol->clusterStartSector(m_curCluster) + sectorOfCluster; + } + if (offset != 0 || toRead < m_vol->bytesPerSector() + || sector == m_vol->cacheSectorNumber()) { + // amount to be read from current sector + n = m_vol->bytesPerSector() - offset; + if (n > toRead) { + n = toRead; + } + // read sector to cache and copy data to caller + pc = m_vol->cacheFetchData(sector, FsCache::CACHE_FOR_READ); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + uint8_t* src = pc->data + offset; + memcpy(dst, src, n); +#if USE_MULTI_SECTOR_IO + } else if (toRead >= 2*m_vol->bytesPerSector()) { + uint32_t ns = toRead >> m_vol->bytesPerSectorShift(); + if (!isRootFixed()) { + uint32_t mb = m_vol->sectorsPerCluster() - sectorOfCluster; + if (mb < ns) { + ns = mb; + } + } + n = ns << m_vol->bytesPerSectorShift(); + if (!m_vol->cacheSafeRead(sector, dst, ns)) { + DBG_FAIL_MACRO; + goto fail; + } +#endif // USE_MULTI_SECTOR_IO + } else { + // read single sector + n = m_vol->bytesPerSector(); + if (!m_vol->cacheSafeRead(sector, dst)) { + DBG_FAIL_MACRO; + goto fail; + } + } + dst += n; + m_curPosition += n; + toRead -= n; + } + return nbyte - toRead; + + fail: + m_error |= READ_ERROR; + return -1; +} +//------------------------------------------------------------------------------ +int8_t FatFile::readDir(DirFat_t* dir) { + int16_t n; + // if not a directory file or miss-positioned return an error + if (!isDir() || (0X1F & m_curPosition)) { + return -1; + } + + while (1) { + n = read(dir, sizeof(DirFat_t)); + if (n != sizeof(DirFat_t)) { + return n == 0 ? 0 : -1; + } + // last entry if FAT_NAME_FREE + if (dir->name[0] == FAT_NAME_FREE) { + return 0; + } + // skip empty entries and entry for . and .. + if (dir->name[0] == FAT_NAME_DELETED || dir->name[0] == '.') { + continue; + } + // return if normal file or subdirectory + if (isFileOrSubdir(dir)) { + return n; + } + } +} +//------------------------------------------------------------------------------ +// Read next directory entry into the cache +// Assumes file is correctly positioned +DirFat_t* FatFile::readDirCache(bool skipReadOk) { + uint8_t i = (m_curPosition >> 5) & 0XF; + + if (i == 0 || !skipReadOk) { + int8_t n = read(&n, 1); + if (n != 1) { + if (n != 0) { + DBG_FAIL_MACRO; + } + goto fail; + } + m_curPosition += 31; + } else { + m_curPosition += 32; + } + // return pointer to entry + return reinterpret_cast(m_vol->cacheAddress()) + i; + + fail: + return nullptr; +} +//------------------------------------------------------------------------------ +bool FatFile::remove(const char* path) { + FatFile file; + if (!file.open(this, path, O_WRONLY)) { + DBG_FAIL_MACRO; + goto fail; + } + return file.remove(); + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::rename(const char* newPath) { + return rename(m_vol->vwd(), newPath); +} +//------------------------------------------------------------------------------ +bool FatFile::rename(FatFile* dirFile, const char* newPath) { + DirFat_t entry; + uint32_t dirCluster = 0; + FatFile file; + FatFile oldFile; + cache_t* pc; + DirFat_t* dir; + + // Must be an open file or subdirectory. + if (!(isFile() || isSubDir())) { + DBG_FAIL_MACRO; + goto fail; + } + // Can't rename LFN in 8.3 mode. + if (!USE_LONG_FILE_NAMES && isLFN()) { + DBG_FAIL_MACRO; + goto fail; + } + // Can't move file to new volume. + if (m_vol != dirFile->m_vol) { + DBG_FAIL_MACRO; + goto fail; + } + // sync() and cache directory entry + sync(); + oldFile = *this; + dir = cacheDirEntry(FsCache::CACHE_FOR_READ); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // save directory entry + memcpy(&entry, dir, sizeof(entry)); + // make directory entry for new path + if (isFile()) { + if (!file.open(dirFile, newPath, O_CREAT | O_EXCL | O_WRONLY)) { + DBG_FAIL_MACRO; + goto fail; + } + } else { + // don't create missing path prefix components + if (!file.mkdir(dirFile, newPath, false)) { + DBG_FAIL_MACRO; + goto fail; + } + // save cluster containing new dot dot + dirCluster = file.m_firstCluster; + } + // change to new directory entry + + m_dirSector = file.m_dirSector; + m_dirIndex = file.m_dirIndex; + m_lfnOrd = file.m_lfnOrd; + m_dirCluster = file.m_dirCluster; + // mark closed to avoid possible destructor close call + file.m_attributes = FILE_ATTR_CLOSED; + file.m_flags = 0; + + // cache new directory entry + dir = cacheDirEntry(FsCache::CACHE_FOR_WRITE); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // copy all but name and name flags to new directory entry + memcpy(&dir->createTimeMs, &entry.createTimeMs, + sizeof(entry) - sizeof(dir->name) - 2); + dir->attributes = entry.attributes; + + // update dot dot if directory + if (dirCluster) { + // get new dot dot + uint32_t sector = m_vol->clusterStartSector(dirCluster); + pc = m_vol->cacheFetchData(sector, FsCache::CACHE_FOR_READ); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + memcpy(&entry, &pc->dir[1], sizeof(entry)); + + // free unused cluster + if (!m_vol->freeChain(dirCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + // store new dot dot + sector = m_vol->clusterStartSector(m_firstCluster); + pc = m_vol->cacheFetchData(sector, FsCache::CACHE_FOR_WRITE); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + memcpy(&pc->dir[1], &entry, sizeof(entry)); + } + // Remove old directory entry; + oldFile.m_firstCluster = 0; + oldFile.m_flags = FILE_FLAG_WRITE; + oldFile.m_attributes = FILE_ATTR_FILE; + if (!oldFile.remove()) { + DBG_FAIL_MACRO; + goto fail; + } + return m_vol->cacheSync(); + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::rmdir() { + // must be open subdirectory + if (!isSubDir() || (!USE_LONG_FILE_NAMES && isLFN())) { + DBG_FAIL_MACRO; + goto fail; + } + rewind(); + + // make sure directory is empty + while (1) { + DirFat_t* dir = readDirCache(true); + if (!dir) { + // EOF if no error. + if (!getError()) { + break; + } + DBG_FAIL_MACRO; + goto fail; + } + // done if past last used entry + if (dir->name[0] == FAT_NAME_FREE) { + break; + } + // skip empty slot, '.' or '..' + if (dir->name[0] == FAT_NAME_DELETED || dir->name[0] == '.') { + continue; + } + // error not empty + if (isFileOrSubdir(dir)) { + DBG_FAIL_MACRO; + goto fail; + } + } + // convert empty directory to normal file for remove + m_attributes = FILE_ATTR_FILE; + m_flags |= FILE_FLAG_WRITE; + return remove(); + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::rmRfStar() { + uint16_t index; + FatFile f; + if (!isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + rewind(); + while (1) { + // remember position + index = m_curPosition/32; + + DirFat_t* dir = readDirCache(); + if (!dir) { + // At EOF if no error. + if (!getError()) { + break; + } + DBG_FAIL_MACRO; + goto fail; + } + // done if past last entry + if (dir->name[0] == FAT_NAME_FREE) { + break; + } + + // skip empty slot or '.' or '..' + if (dir->name[0] == FAT_NAME_DELETED || dir->name[0] == '.') { + continue; + } + + // skip if part of long file name or volume label in root + if (!isFileOrSubdir(dir)) { + continue; + } + + if (!f.open(this, index, O_RDONLY)) { + DBG_FAIL_MACRO; + goto fail; + } + if (f.isSubDir()) { + // recursively delete + if (!f.rmRfStar()) { + DBG_FAIL_MACRO; + goto fail; + } + } else { + // ignore read-only + f.m_flags |= FILE_FLAG_WRITE; + if (!f.remove()) { + DBG_FAIL_MACRO; + goto fail; + } + } + // position to next entry if required + if (m_curPosition != (32UL*(index + 1))) { + if (!seekSet(32UL*(index + 1))) { + DBG_FAIL_MACRO; + goto fail; + } + } + } + // don't try to delete root + if (!isRoot()) { + if (!rmdir()) { + DBG_FAIL_MACRO; + goto fail; + } + } + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::seekSet(uint32_t pos) { + uint32_t nCur; + uint32_t nNew; + uint32_t tmp = m_curCluster; + // error if file not open + if (!isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + // Optimize O_APPEND writes. + if (pos == m_curPosition) { + return true; + } + if (pos == 0) { + // set position to start of file + m_curCluster = 0; + goto done; + } + if (isFile()) { + if (pos > m_fileSize) { + DBG_FAIL_MACRO; + goto fail; + } + } else if (isRootFixed()) { + if (pos <= 32*m_vol->rootDirEntryCount()) { + goto done; + } + DBG_FAIL_MACRO; + goto fail; + } + // calculate cluster index for new position + nNew = (pos - 1) >> (m_vol->bytesPerClusterShift()); +#if USE_FAT_FILE_FLAG_CONTIGUOUS + if (isContiguous()) { + m_curCluster = m_firstCluster + nNew; + goto done; + } +#endif // USE_FAT_FILE_FLAG_CONTIGUOUS + // calculate cluster index for current position + nCur = (m_curPosition - 1) >> (m_vol->bytesPerClusterShift()); + + if (nNew < nCur || m_curPosition == 0) { + // must follow chain from first cluster + m_curCluster = isRoot32() ? m_vol->rootDirStart() : m_firstCluster; + } else { + // advance from curPosition + nNew -= nCur; + } + while (nNew--) { + if (m_vol->fatGet(m_curCluster, &m_curCluster) <= 0) { + DBG_FAIL_MACRO; + goto fail; + } + } + + done: + m_curPosition = pos; + m_flags &= ~FILE_FLAG_PREALLOCATE; + return true; + + fail: + m_curCluster = tmp; + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::sync() { + uint16_t date, time; + uint8_t ms10; + if (!isOpen()) { + return true; + } + if (m_flags & FILE_FLAG_DIR_DIRTY) { + DirFat_t* dir = cacheDirEntry(FsCache::CACHE_FOR_WRITE); + // check for deleted by another open file object + if (!dir || dir->name[0] == FAT_NAME_DELETED) { + DBG_FAIL_MACRO; + goto fail; + } + // do not set filesize for dir files + if (isFile()) { + setLe32(dir->fileSize, m_fileSize); + } + + // update first cluster fields + setLe16(dir->firstClusterLow, m_firstCluster & 0XFFFF); + setLe16(dir->firstClusterHigh, m_firstCluster >> 16); + + // set modify time if user supplied a callback date/time function + if (FsDateTime::callback) { + FsDateTime::callback(&date, &time, &ms10); + setLe16(dir->modifyDate, date); + setLe16(dir->accessDate, date); + setLe16(dir->modifyTime, time); + } + // clear directory dirty + m_flags &= ~FILE_FLAG_DIR_DIRTY; + } + if (m_vol->cacheSync()) { + return true; + } + DBG_FAIL_MACRO; + + fail: + m_error |= WRITE_ERROR; + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::timestamp(uint8_t flags, uint16_t year, uint8_t month, + uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) { + uint16_t dirDate; + uint16_t dirTime; + DirFat_t* dir; + + if (!isFile() + || year < 1980 + || year > 2107 + || month < 1 + || month > 12 + || day < 1 + || day > 31 + || hour > 23 + || minute > 59 + || second > 59) { + DBG_FAIL_MACRO; + goto fail; + } + // update directory entry + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + dir = cacheDirEntry(FsCache::CACHE_FOR_WRITE); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + dirDate = FS_DATE(year, month, day); + dirTime = FS_TIME(hour, minute, second); + if (flags & T_ACCESS) { + setLe16(dir->accessDate, dirDate); + } + if (flags & T_CREATE) { + setLe16(dir->createDate, dirDate); + setLe16(dir->createTime, dirTime); + // units of 10 ms + dir->createTimeMs = second & 1 ? 100 : 0; + } + if (flags & T_WRITE) { + setLe16(dir->modifyDate, dirDate); + setLe16(dir->modifyTime, dirTime); + } + return m_vol->cacheSync(); + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::truncate() { + uint32_t toFree; + // error if not a normal file or read-only + if (!isWritable()) { + DBG_FAIL_MACRO; + goto fail; + } + if (m_firstCluster == 0) { + return true; + } + if (m_curCluster) { + toFree = 0; + int8_t fg = m_vol->fatGet(m_curCluster, &toFree); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (fg) { + // current cluster is end of chain + if (!m_vol->fatPutEOC(m_curCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + } + } else { + toFree = m_firstCluster; + m_firstCluster = 0; + } + if (toFree) { + if (!m_vol->freeChain(toFree)) { + DBG_FAIL_MACRO; + goto fail; + } + } + m_fileSize = m_curPosition; + + // need to update directory entry + m_flags |= FILE_FLAG_DIR_DIRTY; + + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +size_t FatFile::write(const void* buf, size_t nbyte) { + // convert void* to uint8_t* - must be before goto statements + const uint8_t* src = reinterpret_cast(buf); + cache_t* pc; + uint8_t cacheOption; + // number of bytes left to write - must be before goto statements + size_t nToWrite = nbyte; + size_t n; + // error if not a normal file or is read-only + if (!isWritable()) { + DBG_FAIL_MACRO; + goto fail; + } + // seek to end of file if append flag + if ((m_flags & FILE_FLAG_APPEND)) { + if (!seekSet(m_fileSize)) { + DBG_FAIL_MACRO; + goto fail; + } + } + // Don't exceed max fileSize. + if (nbyte > (0XFFFFFFFF - m_curPosition)) { + DBG_FAIL_MACRO; + goto fail; + } + while (nToWrite) { + uint8_t sectorOfCluster = m_vol->sectorOfCluster(m_curPosition); + uint16_t sectorOffset = m_curPosition & m_vol->sectorMask(); + if (sectorOfCluster == 0 && sectorOffset == 0) { + // start of new cluster + if (m_curCluster != 0) { +#if USE_FAT_FILE_FLAG_CONTIGUOUS + int8_t fg; + if (isContiguous() && m_fileSize > m_curPosition) { + m_curCluster++; + fg = 1; + } else { + fg = m_vol->fatGet(m_curCluster, &m_curCluster); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + } +#else // USE_FAT_FILE_FLAG_CONTIGUOUS + int8_t fg = m_vol->fatGet(m_curCluster, &m_curCluster); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } +#endif // USE_FAT_FILE_FLAG_CONTIGUOUS + if (fg == 0) { + // add cluster if at end of chain + if (!addCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + } + } else { + if (m_firstCluster == 0) { + // allocate first cluster of file + if (!addCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + m_firstCluster = m_curCluster; + } else { + m_curCluster = m_firstCluster; + } + } + } + // sector for data write + uint32_t sector = m_vol->clusterStartSector(m_curCluster) + + sectorOfCluster; + + if (sectorOffset != 0 || nToWrite < m_vol->bytesPerSector()) { + // partial sector - must use cache + // max space in sector + n = m_vol->bytesPerSector() - sectorOffset; + // lesser of space and amount to write + if (n > nToWrite) { + n = nToWrite; + } + + if (sectorOffset == 0 && + (m_curPosition >= m_fileSize || m_flags & FILE_FLAG_PREALLOCATE)) { + // start of new sector don't need to read into cache + cacheOption = FsCache::CACHE_RESERVE_FOR_WRITE; + } else { + // rewrite part of sector + cacheOption = FsCache::CACHE_FOR_WRITE; + } + pc = m_vol->cacheFetchData(sector, cacheOption); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + uint8_t* dst = pc->data + sectorOffset; + memcpy(dst, src, n); + if (m_vol->bytesPerSector() == (n + sectorOffset)) { + // Force write if sector is full - improves large writes. + if (!m_vol->cacheSyncData()) { + DBG_FAIL_MACRO; + goto fail; + } + } +#if USE_MULTI_SECTOR_IO + } else if (nToWrite >= 2*m_vol->bytesPerSector()) { + // use multiple sector write command + uint32_t maxSectors = m_vol->sectorsPerCluster() - sectorOfCluster; + uint32_t nSector = nToWrite >> m_vol->bytesPerSectorShift(); + if (nSector > maxSectors) { + nSector = maxSectors; + } + n = nSector << m_vol->bytesPerSectorShift(); + if (!m_vol->cacheSafeWrite(sector, src, nSector)) { + DBG_FAIL_MACRO; + goto fail; + } +#endif // USE_MULTI_SECTOR_IO + } else { + // use single sector write command + n = m_vol->bytesPerSector(); + if (!m_vol->cacheSafeWrite(sector, src)) { + DBG_FAIL_MACRO; + goto fail; + } + } + m_curPosition += n; + src += n; + nToWrite -= n; + } + if (m_curPosition > m_fileSize) { + // update fileSize and insure sync will update dir entry + m_fileSize = m_curPosition; + m_flags |= FILE_FLAG_DIR_DIRTY; + } else if (FsDateTime::callback) { + // insure sync will update modified date and time + m_flags |= FILE_FLAG_DIR_DIRTY; + } + return nbyte; + + fail: + // return for write error + m_error |= WRITE_ERROR; + return -1; +} diff --git a/Firmware_V3/lib/SdFat/src/FatLib/FatFile.h b/Firmware_V3/lib/SdFat/src/FatLib/FatFile.h new file mode 100644 index 0000000..0e3b030 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FatLib/FatFile.h @@ -0,0 +1,1012 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef FatFile_h +#define FatFile_h +/** + * \file + * \brief FatFile class + */ +#include +#include +#include +#include "FatLibConfig.h" +#include "../common/FmtNumber.h" +#include "../common/FsApiConstants.h" +#include "../common/FsDateTime.h" +#include "../common/FsStructs.h" +#include "FatPartition.h" +class FatVolume; +//------------------------------------------------------------------------------ +// Stuff to store strings in AVR flash. +#ifdef __AVR__ +#include +#else // __AVR__ +#ifndef PSTR +/** store literal string in flash for ARM */ +#define PSTR(x) (x) +#endif // PSTR +#ifndef pgm_read_byte +/** read 8-bits from flash for ARM */ +#define pgm_read_byte(addr) (*(const unsigned char*)(addr)) +#endif // pgm_read_byte +#ifndef pgm_read_word +/** read 16-bits from flash for ARM */ +#define pgm_read_word(addr) (*(const uint16_t*)(addr)) +#endif // pgm_read_word +#ifndef PROGMEM +/** store in flash for ARM */ +#define PROGMEM +#endif // PROGMEM +#endif // __AVR__ +//------------------------------------------------------------------------------ +/** + * \struct FatPos_t + * \brief Internal type for file position - do not use in user apps. + */ +struct FatPos_t { + /** stream position */ + uint32_t position; + /** cluster for position */ + uint32_t cluster; +}; +//------------------------------------------------------------------------------ +/** Expression for path name separator. */ +#define isDirSeparator(c) ((c) == '/') +//------------------------------------------------------------------------------ +/** + * \struct fname_t + * \brief Internal type for Short File Name - do not use in user apps. + */ +struct fname_t { + /** Flags for base and extension character case and LFN. */ + uint8_t flags; + /** length of Long File Name */ + size_t len; + /** Long File Name start. */ + const char* lfn; + /** position for sequence number */ + uint8_t seqPos; + /** Short File Name */ + uint8_t sfn[11]; +}; +/** Derived from a LFN with loss or conversion of characters. */ +const uint8_t FNAME_FLAG_LOST_CHARS = 0X01; +/** Base-name or extension has mixed case. */ +const uint8_t FNAME_FLAG_MIXED_CASE = 0X02; +/** LFN entries are required for file name. */ +const uint8_t FNAME_FLAG_NEED_LFN = + FNAME_FLAG_LOST_CHARS | FNAME_FLAG_MIXED_CASE; +/** Filename base-name is all lower case */ +const uint8_t FNAME_FLAG_LC_BASE = FAT_CASE_LC_BASE; +/** Filename extension is all lower case. */ +const uint8_t FNAME_FLAG_LC_EXT = FAT_CASE_LC_EXT; +//============================================================================== +/** + * \class FatFile + * \brief Basic file class. + */ +class FatFile { + public: + /** Create an instance. */ + FatFile() {} + /** Create a file object and open it in the current working directory. + * + * \param[in] path A path for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive + * OR of open flags. see FatFile::open(FatFile*, const char*, uint8_t). + */ + FatFile(const char* path, oflag_t oflag) { + open(path, oflag); + } +#if DESTRUCTOR_CLOSES_FILE + /** Destructor */ + ~FatFile() { + if (isOpen()) { + close(); + } + } +#endif // DESTRUCTOR_CLOSES_FILE + /** The parenthesis operator. + * + * \return true if a file is open. + */ + operator bool() const {return isOpen();} + /** \return The number of bytes available from the current position + * to EOF for normal files. INT_MAX is returned for very large files. + * + * available32() is recomended for very large files. + * + * Zero is returned for directory files. + * + */ + int available() const { + uint32_t n = available32(); + return n > INT_MAX ? INT_MAX : n; + } + /** \return The number of bytes available from the current position + * to EOF for normal files. Zero is returned for directory files. + */ + uint32_t available32() const { + return isFile() ? fileSize() - curPosition() : 0; + } + /** Clear all error bits. */ + void clearError() { + m_error = 0; + } + /** Set writeError to zero */ + void clearWriteError() { + m_error &= ~WRITE_ERROR; + } + /** Close a file and force cached data and directory information + * to be written to the storage device. + * + * \return true for success or false for failure. + */ + bool close(); + /** Check for contiguous file and return its raw sector range. + * + * \param[out] bgnSector the first sector address for the file. + * \param[out] endSector the last sector address for the file. + * + * Set the contiguous flag if the file is contiguous. + * The parameters may be nullptr to only set the flag. + * \return true for success or false for failure. + */ + bool contiguousRange(uint32_t* bgnSector, uint32_t* endSector); + /** Create and open a new contiguous file of a specified size. + * + * \param[in] dirFile The directory where the file will be created. + * \param[in] path A path with a valid file name. + * \param[in] size The desired file size. + * + * \return true for success or false for failure. + */ + bool createContiguous(FatFile* dirFile, + const char* path, uint32_t size); + /** Create and open a new contiguous file of a specified size. + * + * \param[in] path A path with a valid file name. + * \param[in] size The desired file size. + * + * \return true for success or false for failure. + */ + bool createContiguous(const char* path, uint32_t size); + /** \return The current cluster number for a file or directory. */ + uint32_t curCluster() const {return m_curCluster;} + + /** \return The current position for a file or directory. */ + uint32_t curPosition() const {return m_curPosition;} + /** Return a file's directory entry. + * + * \param[out] dir Location for return of the file's directory entry. + * + * \return true for success or false for failure. + */ + bool dirEntry(DirFat_t* dir); + /** \return Directory entry index. */ + uint16_t dirIndex() const {return m_dirIndex;} + /** \return The number of bytes allocated to a directory or zero + * if an error occurs. + */ + uint32_t dirSize(); + /** Dump file in Hex + * \param[in] pr Print stream for list. + * \param[in] pos Start position in file. + * \param[in] n number of locations to dump. + */ + void dmpFile(print_t* pr, uint32_t pos, size_t n); + /** Test for the existence of a file in a directory + * + * \param[in] path Path of the file to be tested for. + * + * The calling instance must be an open directory file. + * + * dirFile.exists("TOFIND.TXT") searches for "TOFIND.TXT" in the directory + * dirFile. + * + * \return True if the file exists. + */ + bool exists(const char* path) { + FatFile file; + return file.open(this, path, O_RDONLY); + } + /** get position for streams + * \param[out] pos struct to receive position + */ + void fgetpos(fspos_t* pos) const; + /** + * Get a string from a file. + * + * fgets() reads bytes from a file into the array pointed to by \a str, until + * \a num - 1 bytes are read, or a delimiter is read and transferred to + * \a str, or end-of-file is encountered. The string is then terminated + * with a null byte. + * + * fgets() deletes CR, '\\r', from the string. This insures only a '\\n' + * terminates the string for Windows text files which use CRLF for newline. + * + * \param[out] str Pointer to the array where the string is stored. + * \param[in] num Maximum number of characters to be read + * (including the final null byte). Usually the length + * of the array \a str is used. + * \param[in] delim Optional set of delimiters. The default is "\n". + * + * \return For success fgets() returns the length of the string in \a str. + * If no data is read, fgets() returns zero for EOF or -1 if an error + * occurred. + */ + int fgets(char* str, int num, char* delim = nullptr); + /** \return The total number of bytes in a file. */ + uint32_t fileSize() const {return m_fileSize;} + /** \return first sector of file or zero for empty file. */ + uint32_t firstBlock() const {return firstSector();} + /** \return Address of first sector or zero for empty file. */ + uint32_t firstSector() const; + /** Arduino name for sync() */ + void flush() {sync();} + /** set position for streams + * \param[in] pos struct with value for new position + */ + void fsetpos(const fspos_t* pos); + /** Get a file's access date. + * + * \param[out] pdate Packed date for directory entry. + * + * \return true for success or false for failure. + */ + bool getAccessDate(uint16_t* pdate); + /** Get a file's access date and time. + * + * \param[out] pdate Packed date for directory entry. + * \param[out] ptime return zero since FAT has no time. + * + * This function is for comparability in FsFile. + * + * \return true for success or false for failure. + */ + bool getAccessDateTime(uint16_t* pdate, uint16_t* ptime) { + if (!getAccessDate(pdate)) { + return false; + } + *ptime = 0; + return true; + } + /** Get a file's create date and time. + * + * \param[out] pdate Packed date for directory entry. + * \param[out] ptime Packed time for directory entry. + * + * \return true for success or false for failure. + */ + bool getCreateDateTime(uint16_t* pdate, uint16_t* ptime); + /** \return All error bits. */ + uint8_t getError() const {return m_error;} + /** Get a file's modify date and time. + * + * \param[out] pdate Packed date for directory entry. + * \param[out] ptime Packed time for directory entry. + * + * \return true for success or false for failure. + */ + bool getModifyDateTime(uint16_t* pdate, uint16_t* ptime); + /** + * Get a file's name followed by a zero byte. + * + * \param[out] name An array of characters for the file's name. + * \param[in] size The size of the array in bytes. The array + * must be at least 13 bytes long. The file's name will be + * truncated if the file's name is too long. + * \return length for success or zero for failure. + */ + size_t getName(char* name, size_t size); + /** + * Get a file's Short File Name followed by a zero byte. + * + * \param[out] name An array of characters for the file's name. + * The array must be at least 13 bytes long. + * \return true for success or false for failure. + */ + size_t getSFN(char* name); + /** \return value of writeError */ + bool getWriteError() const { + return isOpen() ? m_error & WRITE_ERROR : true; + } + /** + * Check for BlockDevice busy. + * + * \return true if busy else false. + */ + bool isBusy(); +#if USE_FAT_FILE_FLAG_CONTIGUOUS + /** \return True if the file is contiguous. */ + bool isContiguous() const {return m_flags & FILE_FLAG_CONTIGUOUS;} +#endif // USE_FAT_FILE_FLAG_CONTIGUOUS + /** \return True if this is a directory. */ + bool isDir() const {return m_attributes & FILE_ATTR_DIR;} + /** \return True if this is a normal file. */ + bool isFile() const {return m_attributes & FILE_ATTR_FILE;} + /** \return True if this is a hidden file. */ + bool isHidden() const {return m_attributes & FILE_ATTR_HIDDEN;} + /** \return true if this file has a Long File Name. */ + bool isLFN() const {return m_lfnOrd;} + /** \return True if this is an open file/directory. */ + bool isOpen() const {return m_attributes;} + /** \return True file is readable. */ + bool isReadable() const {return m_flags & FILE_FLAG_READ;} + /** \return True if file is read-only */ + bool isReadOnly() const {return m_attributes & FILE_ATTR_READ_ONLY;} + /** \return True if this is the root directory. */ + bool isRoot() const {return m_attributes & FILE_ATTR_ROOT;} + /** \return True if this is the FAT32 root directory. */ + bool isRoot32() const {return m_attributes & FILE_ATTR_ROOT32;} + /** \return True if this is the FAT12 of FAT16 root directory. */ + bool isRootFixed() const {return m_attributes & FILE_ATTR_ROOT_FIXED;} + /** \return True if this is a subdirectory. */ + bool isSubDir() const {return m_attributes & FILE_ATTR_SUBDIR;} + /** \return True if this is a system file. */ + bool isSystem() const {return m_attributes & FILE_ATTR_SYSTEM;} + /** \return True file is writable. */ + bool isWritable() const {return m_flags & FILE_FLAG_WRITE;} + /** Check for a legal 8.3 character. + * \param[in] c Character to be checked. + * \return true for a legal 8.3 character. + */ + static bool legal83Char(uint8_t c) { + if (c == '"' || c == '|') { + return false; + } + // *+,./ + if (0X2A <= c && c <= 0X2F && c != 0X2D) { + return false; + } + // :;<=>? + if (0X3A <= c && c <= 0X3F) { + return false; + } + // [\] + if (0X5B <= c && c <= 0X5D) { + return false; + } + return 0X20 < c && c < 0X7F; + } + /** List directory contents. + * + * \param[in] pr Print stream for list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \param[in] indent Amount of space before file name. Used for recursive + * list to indicate subdirectory level. + * + * \return true for success or false for failure. + */ + bool ls(print_t* pr, uint8_t flags = 0, uint8_t indent = 0); + /** Make a new directory. + * + * \param[in] dir An open FatFile instance for the directory that will + * contain the new directory. + * + * \param[in] path A path with a valid name for the new directory. + * + * \param[in] pFlag Create missing parent directories if true. + * + * \return true for success or false for failure. + */ + bool mkdir(FatFile* dir, const char* path, bool pFlag = true); + /** No longer implemented due to Long File Names. + * + * Use getName(char* name, size_t size). + * \return a pointer to replacement suggestion. + */ + const char* name() const { + return "use getName()"; + } + /** Open a file in the volume root directory. + * + * \param[in] vol Volume where the file is located. + * + * \param[in] path with a valid name for a file to be opened. + * + * \param[in] oflag bitwise-inclusive OR of open flags. + * See see FatFile::open(FatFile*, const char*, uint8_t). + * + * \return true for success or false for failure. + */ + bool open(FatVolume* vol, const char* path, oflag_t oflag); + /** Open a file by index. + * + * \param[in] dirFile An open FatFile instance for the directory. + * + * \param[in] index The \a index of the directory entry for the file to be + * opened. The value for \a index is (directory file position)/32. + * + * \param[in] oflag bitwise-inclusive OR of open flags. + * See see FatFile::open(FatFile*, const char*, uint8_t). + * + * See open() by path for definition of flags. + * \return true for success or false for failure. + */ + bool open(FatFile* dirFile, uint16_t index, oflag_t oflag); + /** Open a file or directory by name. + * + * \param[in] dirFile An open FatFile instance for the directory containing + * the file to be opened. + * + * \param[in] path A path with a valid name for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a + * bitwise-inclusive OR of flags from the following list. + * Only one of O_RDONLY, O_READ, O_WRONLY, O_WRITE, or + * O_RDWR is allowed. + * + * O_RDONLY - Open for reading. + * + * O_READ - Same as O_RDONLY. + * + * O_WRONLY - Open for writing. + * + * O_WRITE - Same as O_WRONLY. + * + * O_RDWR - Open for reading and writing. + * + * O_APPEND - If set, the file offset shall be set to the end of the + * file prior to each write. + * + * O_AT_END - Set the initial position at the end of the file. + * + * O_CREAT - If the file exists, this flag has no effect except as noted + * under O_EXCL below. Otherwise, the file shall be created + * + * O_EXCL - If O_CREAT and O_EXCL are set, open() shall fail if the file + * exists. + * + * O_TRUNC - If the file exists and is a regular file, and the file is + * successfully opened and is not read only, its length shall be truncated + * to 0. + * + * WARNING: A given file must not be opened by more than one FatFile object + * or file corruption may occur. + * + * \note Directory files must be opened read only. Write and truncation is + * not allowed for directory files. + * + * \return true for success or false for failure. + */ + bool open(FatFile* dirFile, const char* path, oflag_t oflag); + /** Open a file in the current working volume. + * + * \param[in] path A path with a valid name for a file to be opened. + * + * \param[in] oflag bitwise-inclusive OR of open flags. + * See see FatFile::open(FatFile*, const char*, uint8_t). + * + * \return true for success or false for failure. + */ + bool open(const char* path, oflag_t oflag = O_RDONLY); + /** Open the next file or subdirectory in a directory. + * + * \param[in] dirFile An open FatFile instance for the directory + * containing the file to be opened. + * + * \param[in] oflag bitwise-inclusive OR of open flags. + * See see FatFile::open(FatFile*, const char*, uint8_t). + * + * \return true for success or false for failure. + */ + bool openNext(FatFile* dirFile, oflag_t oflag = O_RDONLY); + /** Open a volume's root directory. + * + * \param[in] vol The FAT volume containing the root directory to be opened. + * + * \return true for success or false for failure. + */ + bool openRoot(FatVolume* vol); + /** Return the next available byte without consuming it. + * + * \return The byte if no error and not at eof else -1; + */ + int peek(); + /** Allocate contiguous clusters to an empty file. + * + * The file must be empty with no clusters allocated. + * + * The file will contain uninitialized data. + * + * \param[in] length size of the file in bytes. + * \return true for success or false for failure. + */ + bool preAllocate(uint32_t length); + /** Print a file's access date + * + * \param[in] pr Print stream for output. + * + * \return The number of characters printed. + */ + size_t printAccessDate(print_t* pr); + /** Print a file's access date + * + * \param[in] pr Print stream for output. + * + * \return The number of characters printed. + */ + size_t printAccessDateTime(print_t* pr) { + return printAccessDate(pr); + } + /** Print a file's creation date and time + * + * \param[in] pr Print stream for output. + * + * \return The number of bytes printed. + */ + size_t printCreateDateTime(print_t* pr); + /** %Print a directory date field. + * + * Format is yyyy-mm-dd. + * + * \param[in] pr Print stream for output. + * \param[in] fatDate The date field from a directory entry. + */ + static void printFatDate(print_t* pr, uint16_t fatDate); + /** %Print a directory time field. + * + * Format is hh:mm:ss. + * + * \param[in] pr Print stream for output. + * \param[in] fatTime The time field from a directory entry. + */ + static void printFatTime(print_t* pr, uint16_t fatTime); + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + size_t printField(double value, char term, uint8_t prec = 2) { + char buf[24]; + char* str = buf + sizeof(buf); + if (term) { + *--str = term; + if (term == '\n') { + *--str = '\r'; + } + } + str = fmtDouble(str, value, prec, false); + return write(str, buf + sizeof(buf) - str); + } + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + size_t printField(float value, char term, uint8_t prec = 2) { + return printField(static_cast(value), term, prec); + } + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \return The number of bytes written or -1 if an error occurs. + */ + template + size_t printField(Type value, char term) { + char sign = 0; + char buf[3*sizeof(Type) + 3]; + char* str = buf + sizeof(buf); + + if (term) { + *--str = term; + if (term == '\n') { + *--str = '\r'; + } + } + if (value < 0) { + value = -value; + sign = '-'; + } + if (sizeof(Type) < 4) { + str = fmtBase10(str, (uint16_t)value); + } else { + str = fmtBase10(str, (uint32_t)value); + } + if (sign) { + *--str = sign; + } + return write(str, &buf[sizeof(buf)] - str); + } + /** Print a file's size. + * + * \param[in] pr Print stream for output. + * + * \return The number of characters printed is returned + * for success and zero is returned for failure. + */ + size_t printFileSize(print_t* pr); + /** Print a file's modify date and time + * + * \param[in] pr Print stream for output. + * + * \return The number of characters printed. + */ + size_t printModifyDateTime(print_t* pr); + /** Print a file's name + * + * \param[in] pr Print stream for output. + * + * \return true for success or false for failure. + */ + size_t printName(print_t* pr); + + /** Print a file's Short File Name. + * + * \param[in] pr Print stream for output. + * + * \return The number of characters printed is returned + * for success and zero is returned for failure. + */ + size_t printSFN(print_t* pr); + /** Read the next byte from a file. + * + * \return For success read returns the next byte in the file as an int. + * If an error occurs or end of file is reached -1 is returned. + */ + int read() { + uint8_t b; + return read(&b, 1) == 1 ? b : -1; + } + /** Read data from a file starting at the current position. + * + * \param[out] buf Pointer to the location that will receive the data. + * + * \param[in] count Maximum number of bytes to read. + * + * \return For success read() returns the number of bytes read. + * A value less than \a nbyte, including zero, will be returned + * if end of file is reached. + * If an error occurs, read() returns -1. + */ + int read(void* buf, size_t count); + /** Read the next directory entry from a directory file. + * + * \param[out] dir The DirFat_t struct that will receive the data. + * + * \return For success readDir() returns the number of bytes read. + * A value of zero will be returned if end of file is reached. + * If an error occurs, readDir() returns -1. Possible errors include + * readDir() called before a directory has been opened, this is not + * a directory file or an I/O error occurred. + */ + int8_t readDir(DirFat_t* dir); + /** Remove a file. + * + * The directory entry and all data for the file are deleted. + * + * \note This function should not be used to delete the 8.3 version of a + * file that has a long name. For example if a file has the long name + * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT". + * + * \return true for success or false for failure. + */ + bool remove(); + /** Remove a file. + * + * The directory entry and all data for the file are deleted. + * + * \param[in] path Path for the file to be removed. + * + * Example use: dirFile.remove(filenameToRemove); + * + * \note This function should not be used to delete the 8.3 version of a + * file that has a long name. For example if a file has the long name + * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT". + * + * \return true for success or false for failure. + */ + bool remove(const char* path); + /** Rename a file or subdirectory. + * \note the renamed file will be moved to the current volume working + * directory. + * + * \param[in] newPath New path name for the file/directory. + * + * \return true for success or false for failure. + */ + bool rename(const char* newPath); + /** Rename a file or subdirectory. + * + * \param[in] dirFile Directory for the new path. + * \param[in] newPath New path name for the file/directory. + * + * \return true for success or false for failure. + */ + bool rename(FatFile* dirFile, const char* newPath); + /** Set the file's current position to zero. */ + void rewind() { + seekSet(0); + } + /** Remove a directory file. + * + * The directory file will be removed only if it is empty and is not the + * root directory. rmdir() follows DOS and Windows and ignores the + * read-only attribute for the directory. + * + * \note This function should not be used to delete the 8.3 version of a + * directory that has a long name. For example if a directory has the + * long name "New folder" you should not delete the 8.3 name "NEWFOL~1". + * + * \return true for success or false for failure. + */ + bool rmdir(); + /** Recursively delete a directory and all contained files. + * + * This is like the Unix/Linux 'rm -rf *' if called with the root directory + * hence the name. + * + * Warning - This will remove all contents of the directory including + * subdirectories. The directory will then be removed if it is not root. + * The read-only attribute for files will be ignored. + * + * \note This function should not be used to delete the 8.3 version of + * a directory that has a long name. See remove() and rmdir(). + * + * \return true for success or false for failure. + */ + bool rmRfStar(); + /** Set the files position to current position + \a pos. See seekSet(). + * \param[in] offset The new position in bytes from the current position. + * \return true for success or false for failure. + */ + bool seekCur(int32_t offset) { + return seekSet(m_curPosition + offset); + } + /** Set the files position to end-of-file + \a offset. See seekSet(). + * Can't be used for directory files since file size is not defined. + * \param[in] offset The new position in bytes from end-of-file. + * \return true for success or false for failure. + */ + bool seekEnd(int32_t offset = 0) { + return isFile() ? seekSet(m_fileSize + offset) : false; + } + /** Sets a file's position. + * + * \param[in] pos The new position in bytes from the beginning of the file. + * + * \return true for success or false for failure. + */ + bool seekSet(uint32_t pos); + /** The sync() call causes all modified data and directory fields + * to be written to the storage device. + * + * \return true for success or false for failure. + */ + bool sync(); + /** Set a file's timestamps in its directory entry. + * + * \param[in] flags Values for \a flags are constructed by a bitwise-inclusive + * OR of flags from the following list + * + * T_ACCESS - Set the file's last access date. + * + * T_CREATE - Set the file's creation date and time. + * + * T_WRITE - Set the file's last write/modification date and time. + * + * \param[in] year Valid range 1980 - 2107 inclusive. + * + * \param[in] month Valid range 1 - 12 inclusive. + * + * \param[in] day Valid range 1 - 31 inclusive. + * + * \param[in] hour Valid range 0 - 23 inclusive. + * + * \param[in] minute Valid range 0 - 59 inclusive. + * + * \param[in] second Valid range 0 - 59 inclusive + * + * \note It is possible to set an invalid date since there is no check for + * the number of days in a month. + * + * \note + * Modify and access timestamps may be overwritten if a date time callback + * function has been set by dateTimeCallback(). + * + * \return true for success or false for failure. + */ + bool timestamp(uint8_t flags, uint16_t year, uint8_t month, uint8_t day, + uint8_t hour, uint8_t minute, uint8_t second); + + /** Truncate a file at the current file position. + * will be maintained if it is less than or equal to \a length otherwise + * it will be set to end of file. + * + * \return true for success or false for failure. + */ + bool truncate(); + /** Truncate a file to a specified length. The current file position + * will be set to end of file. + * + * \param[in] length The desired length for the file. + * + * \return true for success or false for failure. + */ + bool truncate(uint32_t length) { + return seekSet(length) && truncate(); + } + /** Write a string to a file. Used by the Arduino Print class. + * \param[in] str Pointer to the string. + * Use getWriteError to check for errors. + * \return count of characters written for success or -1 for failure. + */ + size_t write(const char* str) { + return write(str, strlen(str)); + } + /** Write a single byte. + * \param[in] b The byte to be written. + * \return +1 for success or -1 for failure. + */ + size_t write(uint8_t b) { + return write(&b, 1); + } + /** Write data to an open file. + * + * \note Data is moved to the cache but may not be written to the + * storage device until sync() is called. + * + * \param[in] buf Pointer to the location of the data to be written. + * + * \param[in] count Number of bytes to write. + * + * \return For success write() returns the number of bytes written, always + * \a count. If an error occurs, write() returns -1. Possible errors + * include write() is called before a file has been opened, write is called + * for a read-only file, device is full, a corrupt file system or an I/O + * error. + * + */ + size_t write(const void* buf, size_t count); +//------------------------------------------------------------------------------ +#if ENABLE_ARDUINO_SERIAL + /** List directory contents. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(uint8_t flags = 0) { + return ls(&Serial, flags); + } + /** Print a file's name. + * + * \return true for success or false for failure. + */ + size_t printName() { + return FatFile::printName(&Serial); + } +#endif // ENABLE_ARDUINO_SERIAL + + private: + /** FatVolume allowed access to private members. */ + friend class FatVolume; + + /** This file has not been opened. */ + static const uint8_t FILE_ATTR_CLOSED = 0; + /** File is read-only. */ + static const uint8_t FILE_ATTR_READ_ONLY = FAT_ATTRIB_READ_ONLY; + /** File should be hidden in directory listings. */ + static const uint8_t FILE_ATTR_HIDDEN = FAT_ATTRIB_HIDDEN; + /** Entry is for a system file. */ + static const uint8_t FILE_ATTR_SYSTEM = FAT_ATTRIB_SYSTEM; + /** Entry for normal data file */ + static const uint8_t FILE_ATTR_FILE = 0X08; + /** Entry is for a subdirectory */ + static const uint8_t FILE_ATTR_SUBDIR = FAT_ATTRIB_DIRECTORY; + /** A FAT12 or FAT16 root directory */ + static const uint8_t FILE_ATTR_ROOT_FIXED = 0X20; + /** A FAT32 root directory */ + static const uint8_t FILE_ATTR_ROOT32 = 0X40; + /** Entry is for root. */ + static const uint8_t FILE_ATTR_ROOT = + FILE_ATTR_ROOT_FIXED | FILE_ATTR_ROOT32; + /** Directory type bits */ + static const uint8_t FILE_ATTR_DIR = FILE_ATTR_SUBDIR | FILE_ATTR_ROOT; + /** Attributes to copy from directory entry */ + static const uint8_t FILE_ATTR_COPY = + FAT_ATTRIB_READ_ONLY | FAT_ATTRIB_HIDDEN | + FAT_ATTRIB_SYSTEM | FAT_ATTRIB_DIRECTORY; + + // private functions + bool addCluster(); + bool addDirCluster(); + DirFat_t* cacheDirEntry(uint8_t action); + static uint8_t lfnChecksum(uint8_t* name); + bool lfnUniqueSfn(fname_t* fname); + bool openCluster(FatFile* file); + static bool parsePathName(const char* str, fname_t* fname, const char** ptr); + bool mkdir(FatFile* parent, fname_t* fname); + bool open(FatFile* dirFile, fname_t* fname, oflag_t oflag); + bool openCachedEntry(FatFile* dirFile, uint16_t cacheIndex, oflag_t oflag, + uint8_t lfnOrd); + DirFat_t* readDirCache(bool skipReadOk = false); + + // bits defined in m_flags + static const uint8_t FILE_FLAG_READ = 0X01; + static const uint8_t FILE_FLAG_WRITE = 0X02; + static const uint8_t FILE_FLAG_APPEND = 0X08; + // treat curPosition as valid length. + static const uint8_t FILE_FLAG_PREALLOCATE = 0X20; + // file is contiguous + static const uint8_t FILE_FLAG_CONTIGUOUS = 0X40; + // sync of directory entry required + static const uint8_t FILE_FLAG_DIR_DIRTY = 0X80; + + // private data + static const uint8_t WRITE_ERROR = 0X1; + static const uint8_t READ_ERROR = 0X2; + + uint8_t m_attributes = FILE_ATTR_CLOSED; + uint8_t m_error = 0; // Error bits. + uint8_t m_flags = 0; // See above for definition of m_flags bits + uint8_t m_lfnOrd; + uint16_t m_dirIndex; // index of directory entry in dir file + FatVolume* m_vol; // volume where file is located + uint32_t m_dirCluster; + uint32_t m_curCluster; // cluster for current file position + uint32_t m_curPosition; // current file position + uint32_t m_dirSector; // sector for this files directory entry + uint32_t m_fileSize; // file size in bytes + uint32_t m_firstCluster; // first cluster of file +}; + +#include "../common/ArduinoFiles.h" +/** + * \class File32 + * \brief FAT16/FAT32 file with Arduino Stream. + */ +class File32 : public StreamFile { + public: + /** Opens the next file or folder in a directory. + * + * \param[in] oflag open flags. + * \return a FatStream object. + */ + File32 openNextFile(oflag_t oflag = O_RDONLY) { + File32 tmpFile; + tmpFile.openNext(this, oflag); + return tmpFile; + } +}; +#endif // FatFile_h diff --git a/Firmware_V3/lib/SdFat/src/FatLib/FatFileLFN.cpp b/Firmware_V3/lib/SdFat/src/FatLib/FatFileLFN.cpp new file mode 100644 index 0000000..bd916a1 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FatLib/FatFileLFN.cpp @@ -0,0 +1,704 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define DBG_FILE "FatFileLFN.cpp" +#include "../common/DebugMacros.h" +#include "FatFile.h" +#include "FatVolume.h" +//------------------------------------------------------------------------------ +// +uint8_t FatFile::lfnChecksum(uint8_t* name) { + uint8_t sum = 0; + for (uint8_t i = 0; i < 11; i++) { + sum = (((sum & 1) << 7) | ((sum & 0xfe) >> 1)) + name[i]; + } + return sum; +} +#if USE_LONG_FILE_NAMES +//------------------------------------------------------------------------------ +// Saves about 90 bytes of flash on 328 over tolower(). +inline char lfnToLower(char c) { + return 'A' <= c && c <= 'Z' ? c + 'a' - 'A' : c; +} +//------------------------------------------------------------------------------ +// Daniel Bernstein University of Illinois at Chicago. +// Original had + instead of ^ +static uint16_t Bernstein(uint16_t hash, const char *str, size_t len) { + for (size_t i = 0; i < len; i++) { + // hash = hash * 33 ^ str[i]; + hash = ((hash << 5) + hash) ^ str[i]; + } + return hash; +} +//------------------------------------------------------------------------------ +/** + * Fetch a 16-bit long file name character. + * + * \param[in] ldir Pointer to long file name directory entry. + * \param[in] i Index of character. + * \return The 16-bit character. + */ +static uint16_t lfnGetChar(DirLfn_t* ldir, uint8_t i) { + if (i < 5) { + return getLe16(ldir->unicode1 + 2*i); + } else if (i < 11) { + return getLe16(ldir->unicode2 + 2*i - 10); + } else if (i < 13) { + return getLe16(ldir->unicode3 + 2*i - 22); + } + return 0; +} +//------------------------------------------------------------------------------ +static size_t lfnGetName(DirLfn_t* ldir, char* name, size_t n) { + uint8_t i; + size_t k = 13*((ldir->order & 0X1F) - 1); + for (i = 0; i < 13; i++) { + uint16_t c = lfnGetChar(ldir, i); + if (c == 0 || k >= (n - 1)) { + break; + } + name[k++] = c >= 0X7F ? '?' : c; + } + // Terminate with zero byte. + if (k >= n) { + k = n - 1; + } + name[k] = '\0'; + return k; +} +//------------------------------------------------------------------------------ +inline bool lfnLegalChar(uint8_t c) { + if (c == '/' || c == '\\' || c == '"' || c == '*' || + c == ':' || c == '<' || c == '>' || c == '?' || c == '|') { + return false; + } + return 0X1F < c && c < 0X7F; +} +//------------------------------------------------------------------------------ +/** + * Store a 16-bit long file name character. + * + * \param[in] ldir Pointer to long file name directory entry. + * \param[in] i Index of character. + * \param[in] c The 16-bit character. + */ +static void lfnPutChar(DirLfn_t* ldir, uint8_t i, uint16_t c) { + if (i < 5) { + setLe16(ldir->unicode1 + 2*i, c); + } else if (i < 11) { + setLe16(ldir->unicode2 + 2*i -10, c); + } else if (i < 13) { + setLe16(ldir->unicode3 + 2*i - 22, c); + } +} +//------------------------------------------------------------------------------ +static void lfnPutName(DirLfn_t* ldir, const char* name, size_t n) { + size_t k = 13*((ldir->order & 0X1F) - 1); + for (uint8_t i = 0; i < 13; i++, k++) { + uint16_t c = k < n ? name[k] : k == n ? 0 : 0XFFFF; + lfnPutChar(ldir, i, c); + } +} +//============================================================================== +size_t FatFile::getName(char* name, size_t size) { + size_t n = 0; + FatFile dirFile; + DirLfn_t* ldir; + if (!isOpen() || size < 13) { + DBG_FAIL_MACRO; + goto fail; + } + if (!isLFN()) { + return getSFN(name); + } + if (!dirFile.openCluster(this)) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t order = 1; order <= m_lfnOrd; order++) { + if (!dirFile.seekSet(32UL*(m_dirIndex - order))) { + DBG_FAIL_MACRO; + goto fail; + } + ldir = reinterpret_cast(dirFile.readDirCache()); + if (!ldir) { + DBG_FAIL_MACRO; + goto fail; + } + if (ldir->attributes != FAT_ATTRIB_LONG_NAME) { + DBG_FAIL_MACRO; + goto fail; + } + if (order != (ldir->order & 0X1F)) { + DBG_FAIL_MACRO; + goto fail; + } + n = lfnGetName(ldir, name, size); + if (n == 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (ldir->order & FAT_ORDER_LAST_LONG_ENTRY) { + return n; + } + } + // Fall into fail. + DBG_FAIL_MACRO; + + fail: + name[0] = '\0'; + return 0; +} +//------------------------------------------------------------------------------ +bool FatFile::openCluster(FatFile* file) { + if (file->m_dirCluster == 0) { + return openRoot(file->m_vol); + } + memset(this, 0, sizeof(FatFile)); + m_attributes = FILE_ATTR_SUBDIR; + m_flags = FILE_FLAG_READ; + m_vol = file->m_vol; + m_firstCluster = file->m_dirCluster; + return true; +} +//------------------------------------------------------------------------------ +bool FatFile::parsePathName(const char* path, + fname_t* fname, const char** ptr) { + char c; + bool is83; + uint8_t bit = FAT_CASE_LC_BASE; + uint8_t lc = 0; + uint8_t uc = 0; + uint8_t i = 0; + uint8_t in = 7; + int end; + int len = 0; + int si; + int dot; + + // Skip leading spaces. + while (*path == ' ') { + path++; + } + fname->lfn = path; + + for (len = 0; ; len++) { + c = path[len]; + if (c == 0 || isDirSeparator(c)) { + break; + } + if (!lfnLegalChar(c)) { + DBG_FAIL_MACRO; + goto fail; + } + } + // Advance to next path component. + for (end = len; path[end] == ' ' || isDirSeparator(path[end]); end++) {} + *ptr = &path[end]; + + // Back over spaces and dots. + while (len) { + c = path[len - 1]; + if (c != '.' && c != ' ') { + break; + } + len--; + } + // Max length of LFN is 255. + if (len > 255) { + DBG_FAIL_MACRO; + goto fail; + } + fname->len = len; + // Blank file short name. + for (uint8_t k = 0; k < 11; k++) { + fname->sfn[k] = ' '; + } + // skip leading spaces and dots. + for (si = 0; path[si] == '.' || path[si] == ' '; si++) {} + // Not 8.3 if leading dot or space. + is83 = !si; + + // find last dot. + for (dot = len - 1; dot >= 0 && path[dot] != '.'; dot--) {} + for (; si < len; si++) { + c = path[si]; + if (c == ' ' || (c == '.' && dot != si)) { + is83 = false; + continue; + } + if (!legal83Char(c) && si != dot) { + is83 = false; + c = '_'; + } + if (si == dot || i > in) { + if (in == 10) { + // Done - extension longer than three characters. + is83 = false; + break; + } + if (si != dot) { + is83 = false; + } + // Break if no dot and base-name is longer than eight characters. + if (si > dot) { + break; + } + si = dot; + in = 10; // Max index for full 8.3 name. + i = 8; // Place for extension. + bit = FAT_CASE_LC_EXT; // bit for extension. + } else { + if ('a' <= c && c <= 'z') { + c += 'A' - 'a'; + lc |= bit; + } else if ('A' <= c && c <= 'Z') { + uc |= bit; + } + fname->sfn[i++] = c; + if (i < 7) { + fname->seqPos = i; + } + } + } + if (fname->sfn[0] == ' ') { + DBG_FAIL_MACRO; + goto fail; + } + + if (is83) { + fname->flags = lc & uc ? FNAME_FLAG_MIXED_CASE : lc; + } else { + fname->flags = FNAME_FLAG_LOST_CHARS; + fname->sfn[fname->seqPos] = '~'; + fname->sfn[fname->seqPos + 1] = '1'; + } + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::open(FatFile* dirFile, fname_t* fname, oflag_t oflag) { + bool fnameFound = false; + uint8_t lfnOrd = 0; + uint8_t freeNeed; + uint8_t freeFound = 0; + uint8_t order = 0; + uint8_t checksum = 0; + uint8_t ms10; + uint16_t freeIndex = 0; + uint16_t curIndex; + uint16_t date; + uint16_t time; + DirFat_t* dir; + DirLfn_t* ldir; + size_t len = fname->len; + + if (!dirFile->isDir() || isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + // Number of directory entries needed. + freeNeed = fname->flags & FNAME_FLAG_NEED_LFN ? 1 + (len + 12)/13 : 1; + + dirFile->rewind(); + while (1) { + curIndex = dirFile->m_curPosition/32; + dir = dirFile->readDirCache(true); + if (!dir) { + if (dirFile->getError()) { + DBG_FAIL_MACRO; + goto fail; + } + // At EOF + goto create; + } + if (dir->name[0] == FAT_NAME_DELETED || dir->name[0] == FAT_NAME_FREE) { + if (freeFound == 0) { + freeIndex = curIndex; + } + if (freeFound < freeNeed) { + freeFound++; + } + if (dir->name[0] == FAT_NAME_FREE) { + goto create; + } + } else { + if (freeFound < freeNeed) { + freeFound = 0; + } + } + // skip empty slot or '.' or '..' + if (dir->name[0] == FAT_NAME_DELETED || dir->name[0] == '.') { + lfnOrd = 0; + } else if (isLongName(dir)) { + ldir = reinterpret_cast(dir); + if (!lfnOrd) { + if ((ldir->order & FAT_ORDER_LAST_LONG_ENTRY) == 0) { + continue; + } + order = ldir->order & 0X1F; + if (order != (freeNeed - 1)) { + continue; + } + lfnOrd = order; + checksum = ldir->checksum; + } else if (ldir->order != --order || checksum != ldir->checksum) { + lfnOrd = 0; + continue; + } + size_t k = 13*(order - 1); + if (k >= len) { + // Not found. + lfnOrd = 0; + continue; + } + for (uint8_t i = 0; i < 13; i++) { + uint16_t u = lfnGetChar(ldir, i); + if (k == len) { + if (u != 0) { + // Not found. + lfnOrd = 0; + } + break; + } + if (u > 255 || lfnToLower(u) != lfnToLower(fname->lfn[k++])) { + // Not found. + lfnOrd = 0; + break; + } + } + } else if (isFileOrSubdir(dir)) { + if (lfnOrd) { + if (1 == order && lfnChecksum(dir->name) == checksum) { + goto found; + } + DBG_FAIL_MACRO; + goto fail; + } + if (!memcmp(dir->name, fname->sfn, sizeof(fname->sfn))) { + if (!(fname->flags & FNAME_FLAG_LOST_CHARS)) { + goto found; + } + fnameFound = true; + } + } else { + lfnOrd = 0; + } + } + + found: + // Don't open if create only. + if (oflag & O_EXCL) { + DBG_FAIL_MACRO; + goto fail; + } + goto open; + + create: + // don't create unless O_CREAT and write mode + if (!(oflag & O_CREAT) || !isWriteMode(oflag)) { + DBG_FAIL_MACRO; + goto fail; + } + // If at EOF start in next cluster. + if (freeFound == 0) { + freeIndex = curIndex; + } + + while (freeFound < freeNeed) { + dir = dirFile->readDirCache(); + if (!dir) { + if (dirFile->getError()) { + DBG_FAIL_MACRO; + goto fail; + } + // EOF if no error. + break; + } + freeFound++; + } + while (freeFound < freeNeed) { + // Will fail if FAT16 root. + if (!dirFile->addDirCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + // Done if more than one sector per cluster. Max freeNeed is 21. + if (dirFile->m_vol->sectorsPerCluster() > 1) { + break; + } + freeFound += 16; + } + if (fnameFound) { + if (!dirFile->lfnUniqueSfn(fname)) { + goto fail; + } + } + if (!dirFile->seekSet(32UL*freeIndex)) { + DBG_FAIL_MACRO; + goto fail; + } + lfnOrd = freeNeed - 1; + for (order = lfnOrd ; order ; order--) { + ldir = reinterpret_cast(dirFile->readDirCache()); + if (!ldir) { + DBG_FAIL_MACRO; + goto fail; + } + dirFile->m_vol->cacheDirty(); + ldir->order = order == lfnOrd ? FAT_ORDER_LAST_LONG_ENTRY | order : order; + ldir->attributes = FAT_ATTRIB_LONG_NAME; + ldir->mustBeZero1 = 0; + ldir->checksum = lfnChecksum(fname->sfn); + setLe16(ldir->mustBeZero2, 0); + lfnPutName(ldir, fname->lfn, len); + } + curIndex = dirFile->m_curPosition/32; + dir = dirFile->readDirCache(); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // initialize as empty file + memset(dir, 0, sizeof(DirFat_t)); + memcpy(dir->name, fname->sfn, 11); + + // Set base-name and extension lower case bits. + dir->caseFlags = (FAT_CASE_LC_BASE | FAT_CASE_LC_EXT) & fname->flags; + + // Set timestamps. + if (FsDateTime::callback) { + // call user date/time function + FsDateTime::callback(&date, &time, &ms10); + setLe16(dir->createDate, date); + setLe16(dir->createTime, time); + dir->createTimeMs = ms10; + } else { + setLe16(dir->createDate, FS_DEFAULT_DATE); + setLe16(dir->modifyDate, FS_DEFAULT_DATE); + setLe16(dir->accessDate, FS_DEFAULT_DATE); + if (FS_DEFAULT_TIME) { + setLe16(dir->createTime, FS_DEFAULT_TIME); + setLe16(dir->modifyTime, FS_DEFAULT_TIME); + } + } + // Force write of entry to device. + dirFile->m_vol->cacheDirty(); + + open: + // open entry in cache. + if (!openCachedEntry(dirFile, curIndex, oflag, lfnOrd)) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +size_t FatFile::printName(print_t* pr) { + FatFile dirFile; + DirLfn_t* ldir; + size_t n = 0; + uint16_t u; + uint8_t buf[13]; + uint8_t i; + + if (!isLFN()) { + return printSFN(pr); + } + if (!dirFile.openCluster(this)) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t order = 1; order <= m_lfnOrd; order++) { + if (!dirFile.seekSet(32UL*(m_dirIndex - order))) { + DBG_FAIL_MACRO; + goto fail; + } + ldir = reinterpret_cast(dirFile.readDirCache()); + if (!ldir) { + DBG_FAIL_MACRO; + goto fail; + } + if (ldir->attributes != FAT_ATTRIB_LONG_NAME || + order != (ldir->order & 0X1F)) { + DBG_FAIL_MACRO; + goto fail; + } + for (i = 0; i < 13; i++) { + u = lfnGetChar(ldir, i); + if (u == 0) { + // End of name. + break; + } + buf[i] = u < 0X7F ? u : '?'; + n++; + } + pr->write(buf, i); + } + return n; + + fail: + return 0; +} +//------------------------------------------------------------------------------ +bool FatFile::remove() { + bool last; + uint8_t checksum; + FatFile dirFile; + DirFat_t* dir; + DirLfn_t* ldir; + + // Cant' remove not open for write. + if (!isWritable()) { + DBG_FAIL_MACRO; + goto fail; + } + // Free any clusters. + if (m_firstCluster && !m_vol->freeChain(m_firstCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + // Cache directory entry. + dir = cacheDirEntry(FsCache::CACHE_FOR_WRITE); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + checksum = lfnChecksum(dir->name); + + // Mark entry deleted. + dir->name[0] = FAT_NAME_DELETED; + + // Set this file closed. + m_attributes = FILE_ATTR_CLOSED; + m_flags = 0; + + // Write entry to device. + if (!m_vol->cacheSync()) { + DBG_FAIL_MACRO; + goto fail; + } + if (!isLFN()) { + // Done, no LFN entries. + return true; + } + if (!dirFile.openCluster(this)) { + DBG_FAIL_MACRO; + goto fail; + } + for (uint8_t order = 1; order <= m_lfnOrd; order++) { + if (!dirFile.seekSet(32UL*(m_dirIndex - order))) { + DBG_FAIL_MACRO; + goto fail; + } + ldir = reinterpret_cast(dirFile.readDirCache()); + if (!ldir) { + DBG_FAIL_MACRO; + goto fail; + } + if (ldir->attributes != FAT_ATTRIB_LONG_NAME || + order != (ldir->order & 0X1F) || + checksum != ldir->checksum) { + DBG_FAIL_MACRO; + goto fail; + } + last = ldir->order & FAT_ORDER_LAST_LONG_ENTRY; + ldir->order = FAT_NAME_DELETED; + m_vol->cacheDirty(); + if (last) { + if (!m_vol->cacheSync()) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + } + } + // Fall into fail. + DBG_FAIL_MACRO; + + fail: + return false; +} +//------------------------------------------------------------------------------ +bool FatFile::lfnUniqueSfn(fname_t* fname) { + const uint8_t FIRST_HASH_SEQ = 2; // min value is 2 + uint8_t pos = fname->seqPos; + DirFat_t* dir; + uint16_t hex; + + DBG_HALT_IF(!(fname->flags & FNAME_FLAG_LOST_CHARS)); + DBG_HALT_IF(fname->sfn[pos] != '~' && fname->sfn[pos + 1] != '1'); + + for (uint8_t seq = 2; seq < 100; seq++) { + if (seq < FIRST_HASH_SEQ) { + fname->sfn[pos + 1] = '0' + seq; + } else { + DBG_PRINT_IF(seq > FIRST_HASH_SEQ); + hex = Bernstein(seq + fname->len, fname->lfn, fname->len); + if (pos > 3) { + // Make space in name for ~HHHH. + pos = 3; + } + for (uint8_t i = pos + 4 ; i > pos; i--) { + uint8_t h = hex & 0XF; + fname->sfn[i] = h < 10 ? h + '0' : h + 'A' - 10; + hex >>= 4; + } + } + fname->sfn[pos] = '~'; + rewind(); + while (1) { + dir = readDirCache(true); + if (!dir) { + if (!getError()) { + // At EOF and name not found if no error. + goto done; + } + DBG_FAIL_MACRO; + goto fail; + } + if (dir->name[0] == FAT_NAME_FREE) { + goto done; + } + if (isFileOrSubdir(dir) && !memcmp(fname->sfn, dir->name, 11)) { + // Name found - try another. + break; + } + } + } + // fall inti fail - too many tries. + DBG_FAIL_MACRO; + + fail: + return false; + + done: + return true; +} +#endif // #if USE_LONG_FILE_NAMES diff --git a/Firmware_V3/lib/SdFat/src/FatLib/FatFilePrint.cpp b/Firmware_V3/lib/SdFat/src/FatLib/FatFilePrint.cpp new file mode 100644 index 0000000..158a59b --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FatLib/FatFilePrint.cpp @@ -0,0 +1,158 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include +#define DBG_FILE "FatFilePrint.cpp" +#include "../common/DebugMacros.h" +#include "FatFile.h" +//------------------------------------------------------------------------------ +static void printHex(print_t* pr, uint8_t w, uint16_t h) { + char buf[5]; + char* ptr = buf + sizeof(buf); + *--ptr = 0; + for (uint8_t i = 0; i < w; i++) { + char c = h & 0XF; + *--ptr = c < 10 ? c + '0' : c + 'A' - 10; + h >>= 4; + } + pr->write(ptr); +} +//------------------------------------------------------------------------------ +void FatFile::dmpFile(print_t* pr, uint32_t pos, size_t n) { + char text[17]; + text[16] = 0; + if (n >= 0XFFF0) { + n = 0XFFF0; + } + if (!seekSet(pos)) { + return; + } + for (size_t i = 0; i <= n; i++) { + if ((i & 15) == 0) { + if (i) { + pr->write(' '); + pr->write(text); + if (i == n) { + break; + } + } + pr->write('\r'); + pr->write('\n'); + if (i >= n) { + break; + } + printHex(pr, 4, i); + pr->write(' '); + } + int16_t h = read(); + if (h < 0) { + break; + } + pr->write(' '); + printHex(pr, 2, h); + text[i&15] = ' ' <= h && h < 0X7F ? h : '.'; + } + pr->write('\r'); + pr->write('\n'); +} +//------------------------------------------------------------------------------ +bool FatFile::ls(print_t* pr, uint8_t flags, uint8_t indent) { + FatFile file; + if (!isDir()) { + DBG_FAIL_MACRO; + goto fail; + } + rewind(); + while (file.openNext(this, O_RDONLY)) { + // indent for dir level + if (!file.isHidden() || (flags & LS_A)) { + for (uint8_t i = 0; i < indent; i++) { + pr->write(' '); + } + if (flags & LS_DATE) { + file.printModifyDateTime(pr); + pr->write(' '); + } + if (flags & LS_SIZE) { + file.printFileSize(pr); + pr->write(' '); + } + file.printName(pr); + if (file.isDir()) { + pr->write('/'); + } + pr->write('\r'); + pr->write('\n'); + if ((flags & LS_R) && file.isDir()) { + file.ls(pr, flags, indent + 2); + } + } + file.close(); + } + if (getError()) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +size_t FatFile::printAccessDate(print_t* pr) { + uint16_t date; + if (getAccessDate(&date)) { + return fsPrintDate(pr, date); + } + return 0; +} +//------------------------------------------------------------------------------ +size_t FatFile::printCreateDateTime(print_t* pr) { + uint16_t date; + uint16_t time; + if (getCreateDateTime(&date, &time)) { + return fsPrintDateTime(pr, date, time); + } + return 0; +} +//------------------------------------------------------------------------------ +size_t FatFile::printModifyDateTime(print_t* pr) { + uint16_t date; + uint16_t time; + if (getModifyDateTime(&date, &time)) { + return fsPrintDateTime(pr, date, time); + } + return 0; +} +//------------------------------------------------------------------------------ +size_t FatFile::printFileSize(print_t* pr) { + char buf[11]; + char *ptr = buf + sizeof(buf); + *--ptr = 0; + ptr = fmtBase10(ptr, fileSize()); + while (ptr > buf) { + *--ptr = ' '; + } + return pr->write(buf); +} diff --git a/Firmware_V3/lib/SdFat/src/FatLib/FatFileSFN.cpp b/Firmware_V3/lib/SdFat/src/FatLib/FatFileSFN.cpp new file mode 100644 index 0000000..194bf8b --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FatLib/FatFileSFN.cpp @@ -0,0 +1,307 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define DBG_FILE "FatFileSFN.cpp" +#include "../common/DebugMacros.h" +#include "../common/FsStructs.h" +#include "FatFile.h" +#include "FatVolume.h" +//------------------------------------------------------------------------------ +size_t FatFile::getSFN(char* name) { + uint8_t j = 0; + uint8_t lcBit = FAT_CASE_LC_BASE; + DirFat_t* dir; + + if (!isOpen()) { + DBG_FAIL_MACRO; + goto fail; + } + if (isRoot()) { + name[0] = '/'; + name[1] = '\0'; + return 1; + } + // cache entry + dir = reinterpret_cast(cacheDirEntry(FsCache::CACHE_FOR_READ)); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // format name + for (uint8_t i = 0; i < 11; i++) { + if (dir->name[i] == ' ') { + continue; + } + if (i == 8) { + // Position bit for extension. + lcBit = FAT_CASE_LC_EXT; + name[j++] = '.'; + } + char c = dir->name[i]; + if ('A' <= c && c <= 'Z' && (lcBit & dir->caseFlags)) { + c += 'a' - 'A'; + } + name[j++] = c; + } + name[j] = '\0'; + return j; + + fail: + name[0] = '\0'; + return 0; +} +//------------------------------------------------------------------------------ +size_t FatFile::printSFN(print_t* pr) { + char name[13]; + if (!getSFN(name)) { + DBG_FAIL_MACRO; + goto fail; + } + return pr->write(name); + + fail: + return 0; +} +#if !USE_LONG_FILE_NAMES +//------------------------------------------------------------------------------ +size_t FatFile::getName(char* name, size_t size) { + return size < 13 ? 0 : getSFN(name); +} +//------------------------------------------------------------------------------ +// format directory name field from a 8.3 name string +bool FatFile::parsePathName(const char* path, fname_t* fname, + const char** ptr) { + uint8_t uc = 0; + uint8_t lc = 0; + uint8_t bit = FNAME_FLAG_LC_BASE; + // blank fill name and extension + for (uint8_t i = 0; i < 11; i++) { + fname->sfn[i] = ' '; + } + + for (uint8_t i = 0, n = 7;; path++) { + uint8_t c = *path; + if (c == 0 || isDirSeparator(c)) { + // Done. + break; + } + if (c == '.' && n == 7) { + n = 10; // max index for full 8.3 name + i = 8; // place for extension + + // bit for extension. + bit = FNAME_FLAG_LC_EXT; + } else { + if (!legal83Char(c) || i > n) { + DBG_FAIL_MACRO; + goto fail; + } + if ('a' <= c && c <= 'z') { + c += 'A' - 'a'; + lc |= bit; + } else if ('A' <= c && c <= 'Z') { + uc |= bit; + } + fname->sfn[i++] = c; + } + } + // must have a file name, extension is optional + if (fname->sfn[0] == ' ') { + DBG_FAIL_MACRO; + goto fail; + } + // Set base-name and extension bits. + fname->flags = lc & uc ? 0 : lc; + while (isDirSeparator(*path)) { + path++; + } + *ptr = path; + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +// open with filename in fname +#define SFN_OPEN_USES_CHKSUM 0 +bool FatFile::open(FatFile* dirFile, fname_t* fname, oflag_t oflag) { + uint16_t date; + uint16_t time; + uint8_t ms10; + bool emptyFound = false; +#if SFN_OPEN_USES_CHKSUM + uint8_t checksum; +#endif // SFN_OPEN_USES_CHKSUM + uint8_t lfnOrd = 0; + uint16_t emptyIndex; + uint16_t index = 0; + DirFat_t* dir; + DirLfn_t* ldir; + + dirFile->rewind(); + while (1) { + if (!emptyFound) { + emptyIndex = index; + } + dir = reinterpret_cast(dirFile->readDirCache(true)); + if (!dir) { + if (dirFile->getError()) { + DBG_FAIL_MACRO; + goto fail; + } + // At EOF if no error. + break; + } + if (dir->name[0] == FAT_NAME_FREE) { + emptyFound = true; + break; + } + if (dir->name[0] == FAT_NAME_DELETED) { + lfnOrd = 0; + emptyFound = true; + } else if (isFileOrSubdir(dir)) { + if (!memcmp(fname->sfn, dir->name, 11)) { + // don't open existing file if O_EXCL + if (oflag & O_EXCL) { + DBG_FAIL_MACRO; + goto fail; + } +#if SFN_OPEN_USES_CHKSUM + if (lfnOrd && checksum != lfnChecksum(dir->name)) { + DBG_FAIL_MACRO; + goto fail; + } +#endif // SFN_OPEN_USES_CHKSUM + if (!openCachedEntry(dirFile, index, oflag, lfnOrd)) { + DBG_FAIL_MACRO; + goto fail; + } + return true; + } else { + lfnOrd = 0; + } + } else if (isLongName(dir)) { + ldir = reinterpret_cast(dir); + if (ldir->order & FAT_ORDER_LAST_LONG_ENTRY) { + lfnOrd = ldir->order & 0X1F; +#if SFN_OPEN_USES_CHKSUM + checksum = ldir->checksum; +#endif // SFN_OPEN_USES_CHKSUM + } + } else { + lfnOrd = 0; + } + index++; + } + // don't create unless O_CREAT and write mode + if (!(oflag & O_CREAT) || !isWriteMode(oflag)) { + DBG_FAIL_MACRO; + goto fail; + } + if (emptyFound) { + index = emptyIndex; + } else { + if (!dirFile->addDirCluster()) { + DBG_FAIL_MACRO; + goto fail; + } + } + if (!dirFile->seekSet(32UL*index)) { + DBG_FAIL_MACRO; + goto fail; + } + dir = reinterpret_cast(dirFile->readDirCache()); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // initialize as empty file + memset(dir, 0, sizeof(DirFat_t)); + memcpy(dir->name, fname->sfn, 11); + + // Set base-name and extension lower case bits. + dir->caseFlags = (FAT_CASE_LC_BASE | FAT_CASE_LC_EXT) & fname->flags; + + // Set timestamps. + if (FsDateTime::callback) { + // call user date/time function + FsDateTime::callback(&date, &time, &ms10); + setLe16(dir->createDate, date); + setLe16(dir->createTime, time); + dir->createTimeMs = ms10; + } else { + setLe16(dir->createDate, FS_DEFAULT_DATE); + setLe16(dir->modifyDate, FS_DEFAULT_DATE); + setLe16(dir->accessDate, FS_DEFAULT_DATE); + if (FS_DEFAULT_TIME) { + setLe16(dir->createTime, FS_DEFAULT_TIME); + setLe16(dir->modifyTime, FS_DEFAULT_TIME); + } + } + // Force write of entry to device. + dirFile->m_vol->cacheDirty(); + + // open entry in cache. + return openCachedEntry(dirFile, index, oflag, 0); + + fail: + return false; +} +//------------------------------------------------------------------------------ +size_t FatFile::printName(print_t* pr) { + return printSFN(pr); +} +//------------------------------------------------------------------------------ +bool FatFile::remove() { + DirFat_t* dir; + // Can't remove if LFN or not open for write. + if (!isWritable() || isLFN()) { + DBG_FAIL_MACRO; + goto fail; + } + // Free any clusters. + if (m_firstCluster && !m_vol->freeChain(m_firstCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + // Cache directory entry. + dir = reinterpret_cast(cacheDirEntry(FsCache::CACHE_FOR_WRITE)); + if (!dir) { + DBG_FAIL_MACRO; + goto fail; + } + // Mark entry deleted. + dir->name[0] = FAT_NAME_DELETED; + + // Set this file closed. + m_attributes = FILE_ATTR_CLOSED; + m_flags = 0; + + // Write entry to device. + return m_vol->cacheSync(); + + fail: + return false; +} +#endif // !USE_LONG_FILE_NAMES diff --git a/Firmware_V3/lib/SdFat/src/FatLib/FatFormatter.cpp b/Firmware_V3/lib/SdFat/src/FatLib/FatFormatter.cpp new file mode 100644 index 0000000..0cbd7dc --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FatLib/FatFormatter.cpp @@ -0,0 +1,280 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "FatFormatter.h" +// Set nonzero to use calculated CHS in MBR. Should not be required. +#define USE_LBA_TO_CHS 1 + +// Constants for file system structure optimized for flash. +uint16_t const BU16 = 128; +uint16_t const BU32 = 8192; +// Assume 512 byte sectors. +const uint16_t BYTES_PER_SECTOR = 512; +const uint16_t SECTORS_PER_MB = 0X100000/BYTES_PER_SECTOR; +const uint16_t FAT16_ROOT_ENTRY_COUNT = 512; +const uint16_t FAT16_ROOT_SECTOR_COUNT = + 32*FAT16_ROOT_ENTRY_COUNT/BYTES_PER_SECTOR; +//------------------------------------------------------------------------------ +#define PRINT_FORMAT_PROGRESS 1 +#if !PRINT_FORMAT_PROGRESS +#define writeMsg(str) +#elif defined(__AVR__) +#define writeMsg(str) if (m_pr) m_pr->print(F(str)) +#else // PRINT_FORMAT_PROGRESS +#define writeMsg(str) if (m_pr) m_pr->write(str) +#endif // PRINT_FORMAT_PROGRESS +//------------------------------------------------------------------------------ +bool FatFormatter::format(BlockDevice* dev, uint8_t* secBuf, print_t* pr) { + bool rtn; + m_dev = dev; + m_secBuf = secBuf; + m_pr = pr; + m_sectorCount = m_dev->sectorCount(); + m_capacityMB = (m_sectorCount + SECTORS_PER_MB - 1)/SECTORS_PER_MB; + + if (m_capacityMB <= 6) { + writeMsg("Card is too small.\r\n"); + return false; + } else if (m_capacityMB <= 16) { + m_sectorsPerCluster = 2; + } else if (m_capacityMB <= 32) { + m_sectorsPerCluster = 4; + } else if (m_capacityMB <= 64) { + m_sectorsPerCluster = 8; + } else if (m_capacityMB <= 128) { + m_sectorsPerCluster = 16; + } else if (m_capacityMB <= 1024) { + m_sectorsPerCluster = 32; + } else if (m_capacityMB <= 32768) { + m_sectorsPerCluster = 64; + } else { + // SDXC cards + m_sectorsPerCluster = 128; + } + rtn = m_sectorCount < 0X400000 ? makeFat16() :makeFat32(); + if (rtn) { + writeMsg("Format Done\r\n"); + } else { + writeMsg("Format Failed\r\n"); + } + return rtn; +} +//------------------------------------------------------------------------------ +bool FatFormatter::initFatDir(uint8_t fatType, uint32_t sectorCount) { + size_t n; + memset(m_secBuf, 0, BYTES_PER_SECTOR); + writeMsg("Writing FAT "); + for (uint32_t i = 1; i < sectorCount; i++) { + if (!m_dev->writeSector(m_fatStart + i, m_secBuf)) { + return false; + } + if ((i%(sectorCount/32)) == 0) { + writeMsg("."); + } + } + writeMsg("\r\n"); + // Allocate reserved clusters and root for FAT32. + m_secBuf[0] = 0XF8; + n = fatType == 16 ? 4 : 12; + for (size_t i = 1; i < n; i++) { + m_secBuf[i] = 0XFF; + } + return m_dev->writeSector(m_fatStart, m_secBuf) && + m_dev->writeSector(m_fatStart + m_fatSize, m_secBuf); +} +//------------------------------------------------------------------------------ +void FatFormatter::initPbs() { + PbsFat_t* pbs = reinterpret_cast(m_secBuf); + memset(m_secBuf, 0, BYTES_PER_SECTOR); + pbs->jmpInstruction[0] = 0XEB; + pbs->jmpInstruction[1] = 0X76; + pbs->jmpInstruction[2] = 0X90; + for (uint8_t i = 0; i < sizeof(pbs->oemName); i++) { + pbs->oemName[i] = ' '; + } + setLe16(pbs->bpb.bpb16.bytesPerSector, BYTES_PER_SECTOR); + pbs->bpb.bpb16.sectorsPerCluster = m_sectorsPerCluster; + setLe16(pbs->bpb.bpb16.reservedSectorCount, m_reservedSectorCount); + pbs->bpb.bpb16.fatCount = 2; + // skip rootDirEntryCount + // skip totalSectors16 + pbs->bpb.bpb16.mediaType = 0XF8; + // skip sectorsPerFat16 + // skip sectorsPerTrack + // skip headCount + setLe32(pbs->bpb.bpb16.hidddenSectors, m_relativeSectors); + setLe32(pbs->bpb.bpb16.totalSectors32, m_totalSectors); + // skip rest of bpb + setLe16(pbs->signature, PBR_SIGNATURE); +} +//------------------------------------------------------------------------------ +bool FatFormatter::makeFat16() { + uint32_t nc; + uint32_t r; + PbsFat_t* pbs = reinterpret_cast(m_secBuf); + + for (m_dataStart = 2*BU16; ; m_dataStart += BU16) { + nc = (m_sectorCount - m_dataStart)/m_sectorsPerCluster; + m_fatSize = (nc + 2 + (BYTES_PER_SECTOR/2) - 1)/(BYTES_PER_SECTOR/2); + r = BU16 + 1 + 2*m_fatSize + FAT16_ROOT_SECTOR_COUNT; + if (m_dataStart >= r) { + m_relativeSectors = m_dataStart - r + BU16; + break; + } + } + // check valid cluster count for FAT16 volume + if (nc < 4085 || nc >= 65525) { + writeMsg("Bad cluster count\r\n"); + return false; + } + m_reservedSectorCount = 1; + m_fatStart = m_relativeSectors + m_reservedSectorCount; + m_totalSectors = nc*m_sectorsPerCluster + + 2*m_fatSize + m_reservedSectorCount + 32; + if (m_totalSectors < 65536) { + m_partType = 0X04; + } else { + m_partType = 0X06; + } + // write MBR + if (!writeMbr()) { + return false; + } + initPbs(); + setLe16(pbs->bpb.bpb16.rootDirEntryCount, FAT16_ROOT_ENTRY_COUNT); + setLe16(pbs->bpb.bpb16.sectorsPerFat16, m_fatSize); + pbs->bpb.bpb16.physicalDriveNumber = 0X80; + pbs->bpb.bpb16.extSignature = EXTENDED_BOOT_SIGNATURE; + setLe32(pbs->bpb.bpb16.volumeSerialNumber, 1234567); + for (size_t i = 0; i < sizeof(pbs->bpb.bpb16.volumeLabel); i++) { + pbs->bpb.bpb16.volumeLabel[i] = ' '; + } + pbs->bpb.bpb16.volumeType[0] = 'F'; + pbs->bpb.bpb16.volumeType[1] = 'A'; + pbs->bpb.bpb16.volumeType[2] = 'T'; + pbs->bpb.bpb16.volumeType[3] = '1'; + pbs->bpb.bpb16.volumeType[4] = '6'; + if (!m_dev->writeSector(m_relativeSectors, m_secBuf)) { + return false; + } + return initFatDir(16, m_dataStart - m_fatStart); +} +//------------------------------------------------------------------------------ +bool FatFormatter::makeFat32() { + uint32_t nc; + uint32_t r; + PbsFat_t* pbs = reinterpret_cast(m_secBuf); + FsInfo_t* fsi = reinterpret_cast(m_secBuf); + + m_relativeSectors = BU32; + for (m_dataStart = 2*BU32; ; m_dataStart += BU32) { + nc = (m_sectorCount - m_dataStart)/m_sectorsPerCluster; + m_fatSize = (nc + 2 + (BYTES_PER_SECTOR/4) - 1)/(BYTES_PER_SECTOR/4); + r = m_relativeSectors + 9 + 2*m_fatSize; + if (m_dataStart >= r) { + break; + } + } + // error if too few clusters in FAT32 volume + if (nc < 65525) { + writeMsg("Bad cluster count\r\n"); + return false; + } + m_reservedSectorCount = m_dataStart - m_relativeSectors - 2*m_fatSize; + m_fatStart = m_relativeSectors + m_reservedSectorCount; + m_totalSectors = nc*m_sectorsPerCluster + m_dataStart - m_relativeSectors; + // type depends on address of end sector + // max CHS has lba = 16450560 = 1024*255*63 + if ((m_relativeSectors + m_totalSectors) <= 16450560) { + // FAT32 with CHS and LBA + m_partType = 0X0B; + } else { + // FAT32 with only LBA + m_partType = 0X0C; + } + if (!writeMbr()) { + return false; + } + initPbs(); + setLe32(pbs->bpb.bpb32.sectorsPerFat32, m_fatSize); + setLe32(pbs->bpb.bpb32.fat32RootCluster, 2); + setLe16(pbs->bpb.bpb32.fat32FSInfoSector, 1); + setLe16(pbs->bpb.bpb32.fat32BackBootSector, 6); + pbs->bpb.bpb32.physicalDriveNumber = 0X80; + pbs->bpb.bpb32.extSignature = EXTENDED_BOOT_SIGNATURE; + setLe32(pbs->bpb.bpb32.volumeSerialNumber, 1234567); + for (size_t i = 0; i < sizeof(pbs->bpb.bpb32.volumeLabel); i++) { + pbs->bpb.bpb32.volumeLabel[i] = ' '; + } + pbs->bpb.bpb32.volumeType[0] = 'F'; + pbs->bpb.bpb32.volumeType[1] = 'A'; + pbs->bpb.bpb32.volumeType[2] = 'T'; + pbs->bpb.bpb32.volumeType[3] = '3'; + pbs->bpb.bpb32.volumeType[4] = '2'; + if (!m_dev->writeSector(m_relativeSectors, m_secBuf) || + !m_dev->writeSector(m_relativeSectors + 6, m_secBuf)) { + return false; + } + // write extra boot area and backup + memset(m_secBuf, 0 , BYTES_PER_SECTOR); + setLe32(fsi->trailSignature, FSINFO_TRAIL_SIGNATURE); + if (!m_dev->writeSector(m_relativeSectors + 2, m_secBuf) || + !m_dev->writeSector(m_relativeSectors + 8, m_secBuf)) { + return false; + } + // write FSINFO sector and backup + setLe32(fsi->leadSignature, FSINFO_LEAD_SIGNATURE); + setLe32(fsi->structSignature, FSINFO_STRUCT_SIGNATURE); + setLe32(fsi->freeCount, 0XFFFFFFFF); + setLe32(fsi->nextFree, 0XFFFFFFFF); + if (!m_dev->writeSector(m_relativeSectors + 1, m_secBuf) || + !m_dev->writeSector(m_relativeSectors + 7, m_secBuf)) { + return false; + } + return initFatDir(32, 2*m_fatSize + m_sectorsPerCluster); +} +//------------------------------------------------------------------------------ +bool FatFormatter::writeMbr() { + memset(m_secBuf, 0, BYTES_PER_SECTOR); + MbrSector_t* mbr = reinterpret_cast(m_secBuf); + +#if USE_LBA_TO_CHS + lbaToMbrChs(mbr->part->beginCHS, m_capacityMB, m_relativeSectors); + lbaToMbrChs(mbr->part->endCHS, m_capacityMB, + m_relativeSectors + m_totalSectors -1); +#else // USE_LBA_TO_CHS + mbr->part->beginCHS[0] = 1; + mbr->part->beginCHS[1] = 1; + mbr->part->beginCHS[2] = 0; + mbr->part->endCHS[0] = 0XFE; + mbr->part->endCHS[1] = 0XFF; + mbr->part->endCHS[2] = 0XFF; +#endif // USE_LBA_TO_CHS + + mbr->part->type = m_partType; + setLe32(mbr->part->relativeSectors, m_relativeSectors); + setLe32(mbr->part->totalSectors, m_totalSectors); + setLe16(mbr->signature, MBR_SIGNATURE); + return m_dev->writeSector(0, m_secBuf); +} diff --git a/Firmware_V3/lib/SdFat/src/FatLib/FatFormatter.h b/Firmware_V3/lib/SdFat/src/FatLib/FatFormatter.h new file mode 100644 index 0000000..e9663c5 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FatLib/FatFormatter.h @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef FatFormatter_h +#define FatFormatter_h +#include "FatFile.h" +#include "../common/SysCall.h" +#include "../common/BlockDevice.h" +#include "../common/FsStructs.h" +/** + * \class FatFormatter + * \brief Format a FAT volume. + */ +class FatFormatter { + public: + /** + * Format a FAT volume. + * + * \param[in] dev Block device for volume. + * \param[in] secBuffer buffer for writing to volume. + * \param[in] pr Print device for progress output. + * + * \return true for success or false for failure. + */ + bool format(BlockDevice* dev, uint8_t* secBuffer, print_t* pr = nullptr); + + private: + bool initFatDir(uint8_t fatType, uint32_t sectorCount); + void initPbs(); + bool makeFat16(); + bool makeFat32(); + bool writeMbr(); + uint32_t m_capacityMB; + uint32_t m_dataStart; + uint32_t m_fatSize; + uint32_t m_fatStart; + uint32_t m_relativeSectors; + uint32_t m_sectorCount; + uint32_t m_totalSectors; + BlockDevice* m_dev; + print_t*m_pr; + uint8_t* m_secBuf; + uint16_t m_reservedSectorCount; + uint8_t m_partType; + uint8_t m_sectorsPerCluster; +}; +#endif // FatFormatter_h diff --git a/Firmware_V3/lib/SdFat/src/FatLib/FatLib.h b/Firmware_V3/lib/SdFat/src/FatLib/FatLib.h new file mode 100644 index 0000000..ceaa348 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FatLib/FatLib.h @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef FatLib_h +#define FatLib_h +#include "FatVolume.h" +#include "FatLibConfig.h" +#include "FatFormatter.h" +#endif // FatLib_h diff --git a/Firmware_V3/lib/SdFat/src/FatLib/FatLibConfig.h b/Firmware_V3/lib/SdFat/src/FatLib/FatLibConfig.h new file mode 100644 index 0000000..08f7966 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FatLib/FatLibConfig.h @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief configuration definitions + */ +#ifndef FatLibConfig_h +#define FatLibConfig_h +#include "SdFatConfig.h" +#endif // FatLibConfig_h diff --git a/Firmware_V3/lib/SdFat/src/FatLib/FatPartition.cpp b/Firmware_V3/lib/SdFat/src/FatLib/FatPartition.cpp new file mode 100644 index 0000000..4ea6ae3 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FatLib/FatPartition.cpp @@ -0,0 +1,492 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include +#define DBG_FILE "FatPartition.cpp" +#include "../common/DebugMacros.h" +#include "../common/FsStructs.h" +#include "FatPartition.h" +//------------------------------------------------------------------------------ +bool FatPartition::allocateCluster(uint32_t current, uint32_t* next) { + uint32_t find; + bool setStart; + if (m_allocSearchStart < current) { + // Try to keep file contiguous. Start just after current cluster. + find = current; + setStart = false; + } else { + find = m_allocSearchStart; + setStart = true; + } + while (1) { + find++; + if (find > m_lastCluster) { + if (setStart) { + // Can't find space, checked all clusters. + DBG_FAIL_MACRO; + goto fail; + } + find = m_allocSearchStart; + setStart = true; + continue; + } + if (find == current) { + // Can't find space, already searched clusters after current. + DBG_FAIL_MACRO; + goto fail; + } + uint32_t f; + int8_t fg = fatGet(find, &f); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (fg && f == 0) { + break; + } + } + if (setStart) { + m_allocSearchStart = find; + } + // Mark end of chain. + if (!fatPutEOC(find)) { + DBG_FAIL_MACRO; + goto fail; + } + if (current) { + // Link clusters. + if (!fatPut(current, find)) { + DBG_FAIL_MACRO; + goto fail; + } + } + updateFreeClusterCount(-1); + *next = find; + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +// find a contiguous group of clusters +bool FatPartition::allocContiguous(uint32_t count, uint32_t* firstCluster) { + // flag to save place to start next search + bool setStart = true; + // start of group + uint32_t bgnCluster; + // end of group + uint32_t endCluster; + // Start at cluster after last allocated cluster. + endCluster = bgnCluster = m_allocSearchStart + 1; + + // search the FAT for free clusters + while (1) { + if (endCluster > m_lastCluster) { + // Can't find space. + DBG_FAIL_MACRO; + goto fail; + } + uint32_t f; + int8_t fg = fatGet(endCluster, &f); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (f || fg == 0) { + // don't update search start if unallocated clusters before endCluster. + if (bgnCluster != endCluster) { + setStart = false; + } + // cluster in use try next cluster as bgnCluster + bgnCluster = endCluster + 1; + } else if ((endCluster - bgnCluster + 1) == count) { + // done - found space + break; + } + endCluster++; + } + // Remember possible next free cluster. + if (setStart) { + m_allocSearchStart = endCluster; + } + // mark end of chain + if (!fatPutEOC(endCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + // link clusters + while (endCluster > bgnCluster) { + if (!fatPut(endCluster - 1, endCluster)) { + DBG_FAIL_MACRO; + goto fail; + } + endCluster--; + } + // Maintain count of free clusters. + updateFreeClusterCount(-count); + + // return first cluster number to caller + *firstCluster = bgnCluster; + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +// Fetch a FAT entry - return -1 error, 0 EOC, else 1. +int8_t FatPartition::fatGet(uint32_t cluster, uint32_t* value) { + uint32_t sector; + uint32_t next; + cache_t* pc; + + // error if reserved cluster of beyond FAT + if (cluster < 2 || cluster > m_lastCluster) { + DBG_FAIL_MACRO; + goto fail; + } + + if (fatType() == 32) { + sector = m_fatStartSector + (cluster >> (m_bytesPerSectorShift - 2)); + pc = cacheFetchFat(sector, FsCache::CACHE_FOR_READ); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + next = getLe32(reinterpret_cast + (&pc->fat32[cluster & (m_sectorMask >> 2)])); + } else if (fatType() == 16) { + cluster &= 0XFFFF; + sector = m_fatStartSector + (cluster >> (m_bytesPerSectorShift - 1) ); + pc = cacheFetchFat(sector, FsCache::CACHE_FOR_READ); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + next = getLe16(reinterpret_cast + (&pc->fat16[cluster & (m_sectorMask >> 1)])); + } else if (FAT12_SUPPORT && fatType() == 12) { + uint16_t index = cluster; + index += index >> 1; + sector = m_fatStartSector + (index >> m_bytesPerSectorShift); + pc = cacheFetchFat(sector, FsCache::CACHE_FOR_READ); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + index &= m_sectorMask; + uint16_t tmp = pc->data[index]; + index++; + if (index == m_bytesPerSector) { + pc = cacheFetchFat(sector + 1, FsCache::CACHE_FOR_READ); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + index = 0; + } + tmp |= pc->data[index] << 8; + next = cluster & 1 ? tmp >> 4 : tmp & 0XFFF; + } else { + DBG_FAIL_MACRO; + goto fail; + } + if (isEOC(next)) { + return 0; + } + *value = next; + return 1; + + fail: + return -1; +} +//------------------------------------------------------------------------------ +// Store a FAT entry +bool FatPartition::fatPut(uint32_t cluster, uint32_t value) { + uint32_t sector; + cache_t* pc; + + // error if reserved cluster of beyond FAT + if (cluster < 2 || cluster > m_lastCluster) { + DBG_FAIL_MACRO; + goto fail; + } + + if (fatType() == 32) { + sector = m_fatStartSector + (cluster >> (m_bytesPerSectorShift - 2)); + pc = cacheFetchFat(sector, FsCache::CACHE_FOR_WRITE); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + setLe32(reinterpret_cast + (&pc->fat32[cluster & (m_sectorMask >> 2)]), value); + return true; + } + + if (fatType() == 16) { + cluster &= 0XFFFF; + sector = m_fatStartSector + (cluster >> (m_bytesPerSectorShift - 1) ); + pc = cacheFetchFat(sector, FsCache::CACHE_FOR_WRITE); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + setLe16(reinterpret_cast + (&pc->fat16[cluster & (m_sectorMask >> 1)]), value); + return true; + } + + if (FAT12_SUPPORT && fatType() == 12) { + uint16_t index = cluster; + index += index >> 1; + sector = m_fatStartSector + (index >> m_bytesPerSectorShift); + pc = cacheFetchFat(sector, FsCache::CACHE_FOR_WRITE); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + index &= m_sectorMask; + uint8_t tmp = value; + if (cluster & 1) { + tmp = (pc->data[index] & 0XF) | tmp << 4; + } + pc->data[index] = tmp; + + index++; + if (index == m_bytesPerSector) { + sector++; + index = 0; + pc = cacheFetchFat(sector, FsCache::CACHE_FOR_WRITE); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + } + tmp = value >> 4; + if (!(cluster & 1)) { + tmp = ((pc->data[index] & 0XF0)) | tmp >> 4; + } + pc->data[index] = tmp; + return true; + } else { + DBG_FAIL_MACRO; + goto fail; + } + + fail: + return false; +} +//------------------------------------------------------------------------------ +// free a cluster chain +bool FatPartition::freeChain(uint32_t cluster) { + uint32_t next = 0; + int8_t fg; + do { + fg = fatGet(cluster, &next); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + // free cluster + if (!fatPut(cluster, 0)) { + DBG_FAIL_MACRO; + goto fail; + } + // Add one to count of free clusters. + updateFreeClusterCount(1); + if (cluster < m_allocSearchStart) { + m_allocSearchStart = cluster - 1; + } + cluster = next; + } while (fg); + + return true; + + fail: + return false; +} +//------------------------------------------------------------------------------ +int32_t FatPartition::freeClusterCount() { +#if MAINTAIN_FREE_CLUSTER_COUNT + if (m_freeClusterCount >= 0) { + return m_freeClusterCount; + } +#endif // MAINTAIN_FREE_CLUSTER_COUNT + uint32_t free = 0; + uint32_t sector; + uint32_t todo = m_lastCluster + 1; + uint16_t n; + + if (FAT12_SUPPORT && fatType() == 12) { + for (unsigned i = 2; i < todo; i++) { + uint32_t c; + int8_t fg = fatGet(i, &c); + if (fg < 0) { + DBG_FAIL_MACRO; + goto fail; + } + if (fg && c == 0) { + free++; + } + } + } else if (fatType() == 16 || fatType() == 32) { + sector = m_fatStartSector; + while (todo) { + cache_t* pc = cacheFetchFat(sector++, FsCache::CACHE_FOR_READ); + if (!pc) { + DBG_FAIL_MACRO; + goto fail; + } + n = fatType() == 16 ? m_bytesPerSector/2 : m_bytesPerSector/4; + if (todo < n) { + n = todo; + } + if (fatType() == 16) { + for (uint16_t i = 0; i < n; i++) { + if (pc->fat16[i] == 0) { + free++; + } + } + } else { + for (uint16_t i = 0; i < n; i++) { + if (pc->fat32[i] == 0) { + free++; + } + } + } + todo -= n; + } + } else { + // invalid FAT type + DBG_FAIL_MACRO; + goto fail; + } + setFreeClusterCount(free); + return free; + + fail: + return -1; +} +//------------------------------------------------------------------------------ +bool FatPartition::init(BlockDevice* dev, uint8_t part) { + uint32_t clusterCount; + uint32_t totalSectors; + uint32_t volumeStartSector = 0; + m_blockDev = dev; + pbs_t* pbs; + BpbFat32_t* bpb; + MbrSector_t* mbr; + uint8_t tmp; + m_fatType = 0; + m_allocSearchStart = 1; + m_cache.init(dev); +#if USE_SEPARATE_FAT_CACHE + m_fatCache.init(dev); +#endif // USE_SEPARATE_FAT_CACHE + // if part == 0 assume super floppy with FAT boot sector in sector zero + // if part > 0 assume mbr volume with partition table + if (part) { + if (part > 4) { + DBG_FAIL_MACRO; + goto fail; + } + mbr = reinterpret_cast + (cacheFetchData(0, FsCache::CACHE_FOR_READ)); + MbrPart_t* mp = mbr->part + part - 1; + + if (!mbr || mp->type == 0 || (mp->boot != 0 && mp->boot != 0X80)) { + DBG_FAIL_MACRO; + goto fail; + } + volumeStartSector = getLe32(mp->relativeSectors); + } + pbs = reinterpret_cast + (cacheFetchData(volumeStartSector, FsCache::CACHE_FOR_READ)); + bpb = reinterpret_cast(pbs->bpb); + if (!pbs || bpb->fatCount != 2 || getLe16(bpb->bytesPerSector) != 512) { + DBG_FAIL_MACRO; + goto fail; + } + m_sectorsPerCluster = bpb->sectorsPerCluster; + m_clusterSectorMask = m_sectorsPerCluster - 1; + // determine shift that is same as multiply by m_sectorsPerCluster + m_sectorsPerClusterShift = 0; + for (tmp = 1; m_sectorsPerCluster != tmp; tmp <<= 1) { + if (tmp == 0) { + DBG_FAIL_MACRO; + goto fail; + } + m_sectorsPerClusterShift++; + } + m_sectorsPerFat = getLe16(bpb->sectorsPerFat16); + if (m_sectorsPerFat == 0) { + m_sectorsPerFat = getLe32(bpb->sectorsPerFat32); + } + m_fatStartSector = volumeStartSector + getLe16(bpb->reservedSectorCount); + + // count for FAT16 zero for FAT32 + m_rootDirEntryCount = getLe16(bpb->rootDirEntryCount); + + // directory start for FAT16 dataStart for FAT32 + m_rootDirStart = m_fatStartSector + 2 * m_sectorsPerFat; + // data start for FAT16 and FAT32 + m_dataStartSector = m_rootDirStart + + ((32 * m_rootDirEntryCount + m_bytesPerSector - 1)/m_bytesPerSector); + + // total sectors for FAT16 or FAT32 + totalSectors = getLe16(bpb->totalSectors16); + if (totalSectors == 0) { + totalSectors = getLe32(bpb->totalSectors32); + } + // total data sectors + clusterCount = totalSectors - (m_dataStartSector - volumeStartSector); + + // divide by cluster size to get cluster count + clusterCount >>= m_sectorsPerClusterShift; + m_lastCluster = clusterCount + 1; + + // Indicate unknown number of free clusters. + setFreeClusterCount(-1); + // FAT type is determined by cluster count + if (clusterCount < 4085) { + m_fatType = 12; + if (!FAT12_SUPPORT) { + DBG_FAIL_MACRO; + goto fail; + } + } else if (clusterCount < 65525) { + m_fatType = 16; + } else { + m_rootDirStart = getLe32(bpb->fat32RootCluster); + m_fatType = 32; + } + m_cache.setMirrorOffset(m_sectorsPerFat); +#if USE_SEPARATE_FAT_CACHE + m_fatCache.setMirrorOffset(m_sectorsPerFat); +#endif // USE_SEPARATE_FAT_CACHE + return true; + + fail: + return false; +} diff --git a/Firmware_V3/lib/SdFat/src/FatLib/FatPartition.h b/Firmware_V3/lib/SdFat/src/FatLib/FatPartition.h new file mode 100644 index 0000000..0c019fb --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FatLib/FatPartition.h @@ -0,0 +1,304 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef FatPartition_h +#define FatPartition_h +/** + * \file + * \brief FatPartition class + */ +#include +#include "FatLibConfig.h" +#include "../common/SysCall.h" +#include "../common/BlockDevice.h" +#include "../common/FsCache.h" +#include "../common/FsStructs.h" + +/** Type for FAT12 partition */ +const uint8_t FAT_TYPE_FAT12 = 12; + +/** Type for FAT12 partition */ +const uint8_t FAT_TYPE_FAT16 = 16; + +/** Type for FAT12 partition */ +const uint8_t FAT_TYPE_FAT32 = 32; + +//------------------------------------------------------------------------------ +/** + * \brief Cache type for a sector. + */ +union cache_t { + /** Used to access cached file data sectors. */ + uint8_t data[512]; + /** Used to access cached FAT16 entries. */ + uint16_t fat16[256]; + /** Used to access cached FAT32 entries. */ + uint32_t fat32[128]; + /** Used to access cached directory entries. */ + DirFat_t dir[16]; +}; +//============================================================================== +/** + * \class FatPartition + * \brief Access FAT16 and FAT32 partitions on raw file devices. + */ +class FatPartition { + public: + /** Create an instance of FatPartition + */ + FatPartition() {} + + /** \return The shift count required to multiply by bytesPerCluster. */ + uint8_t bytesPerClusterShift() const { + return m_sectorsPerClusterShift + m_bytesPerSectorShift; + } + /** \return Number of bytes in a cluster. */ + uint16_t bytesPerCluster() const { + return m_bytesPerSector << m_sectorsPerClusterShift; + } + /** \return Number of bytes per sector. */ + uint16_t bytesPerSector() const { + return m_bytesPerSector; + } + /** \return The shift count required to multiply by bytesPerCluster. */ + uint8_t bytesPerSectorShift() const { + return m_bytesPerSectorShift; + } + /** \return Mask for sector offset. */ + uint16_t sectorMask() const { + return m_sectorMask; + } + /** \return The volume's cluster size in sectors. */ + uint8_t sectorsPerCluster() const { + return m_sectorsPerCluster; + } +#ifndef DOXYGEN_SHOULD_SKIP_THIS + // Use sectorsPerCluster(). blocksPerCluster() will be removed in the future. + uint8_t blocksPerCluster() __attribute__ ((deprecated)) {return sectorsPerCluster();} //NOLINT +#endif // DOXYGEN_SHOULD_SKIP_THIS + /** \return The number of sectors in one FAT. */ + uint32_t sectorsPerFat() const { + return m_sectorsPerFat; + } + /** Clear the cache and returns a pointer to the cache. Not for normal apps. + * \return A pointer to the cache buffer or zero if an error occurs. + */ + uint8_t* cacheClear() { + return m_cache.clear(); + } + /** \return The total number of clusters in the volume. */ + uint32_t clusterCount() const { + return m_lastCluster - 1; + } + /** \return The shift count required to multiply by sectorsPerCluster. */ + uint8_t sectorsPerClusterShift() const { + return m_sectorsPerClusterShift; + } + /** \return The logical sector number for the start of file data. */ + uint32_t dataStartSector() const { + return m_dataStartSector; + } + /** \return The number of File Allocation Tables. */ + uint8_t fatCount() const { + return 2; + } + /** \return The logical sector number for the start of the first FAT. */ + uint32_t fatStartSector() const { + return m_fatStartSector; + } + /** \return The FAT type of the volume. Values are 12, 16 or 32. */ + uint8_t fatType() const { + return m_fatType; + } + /** Volume free space in clusters. + * + * \return Count of free clusters for success or -1 if an error occurs. + */ + int32_t freeClusterCount(); + /** Initialize a FAT partition. + * + * \param[in] dev BlockDevice for this partition. + * \param[in] part The partition to be used. Legal values for \a part are + * 1-4 to use the corresponding partition on a device formatted with + * a MBR, Master Boot Record, or zero if the device is formatted as + * a super floppy with the FAT boot sector in sector zero. + * + * \return true for success or false for failure. + */ + bool init(BlockDevice* dev, uint8_t part = 1); + /** \return The number of entries in the root directory for FAT16 volumes. */ + uint16_t rootDirEntryCount() const { + return m_rootDirEntryCount; + } + /** \return The logical sector number for the start of the root directory + on FAT16 volumes or the first cluster number on FAT32 volumes. */ + uint32_t rootDirStart() const { + return m_rootDirStart; + } + /** \return The number of sectors in the volume */ + uint32_t volumeSectorCount() const { + return sectorsPerCluster()*clusterCount(); + } + /** Debug access to FAT table + * + * \param[in] n cluster number. + * \param[out] v value of entry + * \return -1 error, 0 EOC, else 1. + */ + int8_t dbgFat(uint32_t n, uint32_t* v) { + return fatGet(n, v); + } + /** + * Check for BlockDevice busy. + * + * \return true if busy else false. + */ + bool isBusy() {return m_blockDev->isBusy();} + //---------------------------------------------------------------------------- +#ifndef DOXYGEN_SHOULD_SKIP_THIS + void dmpDirSector(print_t* pr, uint32_t sector); + void dmpFat(print_t* pr, uint32_t start, uint32_t count); + void dmpRootDir(print_t* pr); + void dmpSector(print_t* pr, uint32_t sector, uint8_t bits = 8); +#endif // DOXYGEN_SHOULD_SKIP_THIS + //---------------------------------------------------------------------------- + private: + /** FatFile allowed access to private members. */ + friend class FatFile; + //---------------------------------------------------------------------------- + static const uint8_t m_bytesPerSectorShift = 9; + static const uint16_t m_bytesPerSector = 512; + static const uint16_t m_sectorMask = 0x1FF; + //---------------------------------------------------------------------------- + BlockDevice* m_blockDev; // sector device + uint8_t m_sectorsPerCluster; // Cluster size in sectors. + uint8_t m_clusterSectorMask; // Mask to extract sector of cluster. + uint8_t m_sectorsPerClusterShift; // Cluster count to sector count shift. + uint8_t m_fatType = 0; // Volume type (12, 16, OR 32). + uint16_t m_rootDirEntryCount; // Number of entries in FAT16 root dir. + uint32_t m_allocSearchStart; // Start cluster for alloc search. + uint32_t m_sectorsPerFat; // FAT size in sectors + uint32_t m_dataStartSector; // First data sector number. + uint32_t m_fatStartSector; // Start sector for first FAT. + uint32_t m_lastCluster; // Last cluster number in FAT. + uint32_t m_rootDirStart; // Start sector FAT16, cluster FAT32. + //---------------------------------------------------------------------------- + // sector I/O functions. + bool cacheSafeRead(uint32_t sector, uint8_t* dst) { + return m_cache.cacheSafeRead(sector, dst); + } + bool cacheSafeRead(uint32_t sector, uint8_t* dst, size_t count) { + return m_cache.cacheSafeRead(sector, dst, count); + } + bool cacheSafeWrite(uint32_t sector, const uint8_t* dst) { + return m_cache.cacheSafeWrite(sector, dst); + } + bool cacheSafeWrite(uint32_t sector, const uint8_t* dst, size_t count) { + return m_cache.cacheSafeWrite(sector, dst, count); + } + bool readSector(uint32_t sector, uint8_t* dst) { + return m_blockDev->readSector(sector, dst); + } + bool syncDevice() { + return m_blockDev->syncDevice(); + } + bool writeSector(uint32_t sector, const uint8_t* src) { + return m_blockDev->writeSector(sector, src); + } +#if MAINTAIN_FREE_CLUSTER_COUNT + int32_t m_freeClusterCount; // Count of free clusters in volume. + void setFreeClusterCount(int32_t value) { + m_freeClusterCount = value; + } + void updateFreeClusterCount(int32_t change) { + if (m_freeClusterCount >= 0) { + m_freeClusterCount += change; + } + } +#else // MAINTAIN_FREE_CLUSTER_COUNT + void setFreeClusterCount(int32_t value) { + (void)value; + } + void updateFreeClusterCount(int32_t change) { + (void)change; + } +#endif // MAINTAIN_FREE_CLUSTER_COUNT +// sector caches + FsCache m_cache; +#if USE_SEPARATE_FAT_CACHE + FsCache m_fatCache; + cache_t* cacheFetchFat(uint32_t sector, uint8_t options) { + options |= FsCache::CACHE_STATUS_MIRROR_FAT; + return reinterpret_cast(m_fatCache.get(sector, options)); + } + bool cacheSync() { + return m_cache.sync() && m_fatCache.sync() && syncDevice(); + } +#else // USE_SEPARATE_FAT_CACHE + cache_t* cacheFetchFat(uint32_t sector, uint8_t options) { + options |= FsCache::CACHE_STATUS_MIRROR_FAT; + return cacheFetchData(sector, options); + } + bool cacheSync() { + return m_cache.sync() && syncDevice(); + } +#endif // USE_SEPARATE_FAT_CACHE + cache_t* cacheFetchData(uint32_t sector, uint8_t options) { + return reinterpret_cast(m_cache.get(sector, options)); + } + void cacheInvalidate() { + m_cache.invalidate(); + } + bool cacheSyncData() { + return m_cache.sync(); + } + cache_t* cacheAddress() { + return reinterpret_cast(m_cache.cacheBuffer()); + } + uint32_t cacheSectorNumber() { + return m_cache.sector(); + } + void cacheDirty() { + m_cache.dirty(); + } + //---------------------------------------------------------------------------- + bool allocateCluster(uint32_t current, uint32_t* next); + bool allocContiguous(uint32_t count, uint32_t* firstCluster); + uint8_t sectorOfCluster(uint32_t position) const { + return (position >> 9) & m_clusterSectorMask; + } + uint32_t clusterStartSector(uint32_t cluster) const { + return m_dataStartSector + ((cluster - 2) << m_sectorsPerClusterShift); + } + int8_t fatGet(uint32_t cluster, uint32_t* value); + bool fatPut(uint32_t cluster, uint32_t value); + bool fatPutEOC(uint32_t cluster) { + return fatPut(cluster, 0x0FFFFFFF); + } + bool freeChain(uint32_t cluster); + bool isEOC(uint32_t cluster) const { + return cluster > m_lastCluster; + } +}; +#endif // FatPartition diff --git a/Firmware_V3/lib/SdFat/src/FatLib/FatVolume.cpp b/Firmware_V3/lib/SdFat/src/FatLib/FatVolume.cpp new file mode 100644 index 0000000..d6f59e2 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FatLib/FatVolume.cpp @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "FatVolume.h" +FatVolume* FatVolume::m_cwv = nullptr; +//------------------------------------------------------------------------------ +bool FatVolume::chdir(const char *path) { + FatFile dir; + if (!dir.open(vwd(), path, O_RDONLY)) { + goto fail; + } + if (!dir.isDir()) { + goto fail; + } + m_vwd = dir; + return true; + + fail: + return false; +} diff --git a/Firmware_V3/lib/SdFat/src/FatLib/FatVolume.h b/Firmware_V3/lib/SdFat/src/FatLib/FatVolume.h new file mode 100644 index 0000000..df8e4c1 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FatLib/FatVolume.h @@ -0,0 +1,340 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef FatVolume_h +#define FatVolume_h +#include "FatPartition.h" +#include "FatFile.h" +/** + * \file + * \brief FatVolume class + */ +//------------------------------------------------------------------------------ +/** + * \class FatVolume + * \brief Integration class for the FatLib library. + */ +class FatVolume : public FatPartition { + public: + /** + * Initialize an FatVolume object. + * \param[in] dev Device block driver. + * \param[in] setCwv Set current working volume if true. + * \param[in] part partition to initialize. + * \return true for success or false for failure. + */ + bool begin(BlockDevice* dev, bool setCwv = true, uint8_t part = 1) { + if (!init(dev, part)) { + return false; + } + if (!chdir()) { + return false; + } + if (setCwv || !m_cwv) { + m_cwv = this; + } + return true; + } + /** Change global current working volume to this volume. */ + void chvol() {m_cwv = this;} + + /** + * Set volume working directory to root. + * \return true for success or false for failure. + */ + bool chdir() { + m_vwd.close(); + return m_vwd.openRoot(this); + } + /** + * Set volume working directory. + * \param[in] path Path for volume working directory. + * \return true for success or false for failure. + */ + bool chdir(const char *path); + + //---------------------------------------------------------------------------- + /** + * Test for the existence of a file. + * + * \param[in] path Path of the file to be tested for. + * + * \return true if the file exists else false. + */ + bool exists(const char* path) { + FatFile tmp; + return tmp.open(this, path, O_RDONLY); + } + //---------------------------------------------------------------------------- + /** List the directory contents of the volume root directory. + * + * \param[in] pr Print stream for list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(print_t* pr, uint8_t flags = 0) { + return m_vwd.ls(pr, flags); + } + //---------------------------------------------------------------------------- + /** List the contents of a directory. + * + * \param[in] pr Print stream for list. + * + * \param[in] path directory to list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(print_t* pr, const char* path, uint8_t flags) { + FatFile dir; + return dir.open(this, path, O_RDONLY) && dir.ls(pr, flags); + } + //---------------------------------------------------------------------------- + /** Make a subdirectory in the volume root directory. + * + * \param[in] path A path with a valid name for the subdirectory. + * + * \param[in] pFlag Create missing parent directories if true. + * + * \return true for success or false for failure. + */ + bool mkdir(const char* path, bool pFlag = true) { + FatFile sub; + return sub.mkdir(vwd(), path, pFlag); + } + //---------------------------------------------------------------------------- + /** open a file + * + * \param[in] path location of file to be opened. + * \param[in] oflag open flags. + * \return a File32 object. + */ + File32 open(const char *path, oflag_t oflag = O_RDONLY) { + File32 tmpFile; + tmpFile.open(this, path, oflag); + return tmpFile; + } + //---------------------------------------------------------------------------- + /** Remove a file from the volume root directory. + * + * \param[in] path A path with a valid name for the file. + * + * \return true for success or false for failure. + */ + bool remove(const char* path) { + FatFile tmp; + return tmp.open(this, path, O_WRONLY) && tmp.remove(); + } + //---------------------------------------------------------------------------- + /** Rename a file or subdirectory. + * + * \param[in] oldPath Path name to the file or subdirectory to be renamed. + * + * \param[in] newPath New path name of the file or subdirectory. + * + * The \a newPath object must not exist before the rename call. + * + * The file to be renamed must not be open. The directory entry may be + * moved and file system corruption could occur if the file is accessed by + * a file object that was opened before the rename() call. + * + * \return true for success or false for failure. + */ + bool rename(const char *oldPath, const char *newPath) { + FatFile file; + return file.open(vwd(), oldPath, O_RDONLY) && file.rename(vwd(), newPath); + } + //---------------------------------------------------------------------------- + /** Remove a subdirectory from the volume's working directory. + * + * \param[in] path A path with a valid name for the subdirectory. + * + * The subdirectory file will be removed only if it is empty. + * + * \return true for success or false for failure. + */ + bool rmdir(const char* path) { + FatFile sub; + return sub.open(this, path, O_RDONLY) && sub.rmdir(); + } + //---------------------------------------------------------------------------- + /** Truncate a file to a specified length. The current file position + * will be at the new EOF. + * + * \param[in] path A path with a valid name for the file. + * \param[in] length The desired length for the file. + * + * \return true for success or false for failure. + */ + bool truncate(const char* path, uint32_t length) { + FatFile file; + return file.open(this, path, O_WRONLY) && file.truncate(length); + } +#if ENABLE_ARDUINO_SERIAL + /** List the directory contents of the root directory to Serial. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(uint8_t flags = 0) { + return ls(&Serial, flags); + } + /** List the directory contents of a directory to Serial. + * + * \param[in] path directory to list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(const char* path, uint8_t flags = 0) { + return ls(&Serial, path, flags); + } +#endif // ENABLE_ARDUINO_SERIAL +#if ENABLE_ARDUINO_STRING + //---------------------------------------------------------------------------- + /** + * Set volume working directory. + * \param[in] path Path for volume working directory. + * \return true for success or false for failure. + */ + bool chdir(const String& path) { + return chdir(path.c_str()); + } + /** + * Test for the existence of a file. + * + * \param[in] path Path of the file to be tested for. + * + * \return true if the file exists else false. + */ + bool exists(const String& path) { + return exists(path.c_str()); + } + /** Make a subdirectory in the volume root directory. + * + * \param[in] path A path with a valid name for the subdirectory. + * + * \param[in] pFlag Create missing parent directories if true. + * + * \return true for success or false for failure. + */ + bool mkdir(const String& path, bool pFlag = true) { + return mkdir(path.c_str(), pFlag); + } + /** open a file + * + * \param[in] path location of file to be opened. + * \param[in] oflag open flags. + * \return a File32 object. + */ + File32 open(const String& path, oflag_t oflag = O_RDONLY) { + return open(path.c_str(), oflag ); + } + /** Remove a file from the volume root directory. + * + * \param[in] path A path with a valid name for the file. + * + * \return true for success or false for failure. + */ + bool remove(const String& path) { + return remove(path.c_str()); + } + /** Rename a file or subdirectory. + * + * \param[in] oldPath Path name to the file or subdirectory to be renamed. + * + * \param[in] newPath New path name of the file or subdirectory. + * + * The \a newPath object must not exist before the rename call. + * + * The file to be renamed must not be open. The directory entry may be + * moved and file system corruption could occur if the file is accessed by + * a file object that was opened before the rename() call. + * + * \return true for success or false for failure. + */ + bool rename(const String& oldPath, const String& newPath) { + return rename(oldPath.c_str(), newPath.c_str()); + } + /** Remove a subdirectory from the volume's working directory. + * + * \param[in] path A path with a valid name for the subdirectory. + * + * The subdirectory file will be removed only if it is empty. + * + * \return true for success or false for failure. + */ + bool rmdir(const String& path) { + return rmdir(path.c_str()); + } + /** Truncate a file to a specified length. The current file position + * will be at the new EOF. + * + * \param[in] path A path with a valid name for the file. + * \param[in] length The desired length for the file. + * + * \return true for success or false for failure. + */ + bool truncate(const String& path, uint32_t length) { + return truncate(path.c_str(), length); + } +#endif // ENABLE_ARDUINO_STRING + + private: + friend FatFile; + static FatVolume* cwv() {return m_cwv;} + FatFile* vwd() {return &m_vwd;} + static FatVolume* m_cwv; + FatFile m_vwd; +}; +#endif // FatVolume_h diff --git a/Firmware_V3/lib/SdFat/src/FreeStack.cpp b/Firmware_V3/lib/SdFat/src/FreeStack.cpp new file mode 100644 index 0000000..3d3c58c --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FreeStack.cpp @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define FREE_STACK_CPP +#include "FreeStack.h" +#if defined(HAS_UNUSED_STACK) && HAS_UNUSED_STACK +//------------------------------------------------------------------------------ +inline char* stackBegin() { +#if defined(__AVR__) + return __brkval ? __brkval : &__bss_end; +#elif defined(__IMXRT1062__) + return reinterpret_cast(&_ebss); +#elif defined(__arm__) + return reinterpret_cast(sbrk(0)); +#else // defined(__AVR__) +#error "undefined stackBegin" +#endif // defined(__AVR__) +} +//------------------------------------------------------------------------------ +inline char* stackPointer() { +#if defined(__AVR__) + return reinterpret_cast(SP); +#elif defined(__arm__) + register uint32_t sp asm("sp"); + return reinterpret_cast(sp); +#else // defined(__AVR__) +#error "undefined stackPointer" +#endif // defined(__AVR__) +} +//------------------------------------------------------------------------------ +/** Stack fill pattern. */ +const char FILL = 0x55; +void FillStack() { + char* p = stackBegin(); + char* top = stackPointer(); + while (p < top) { + *p++ = FILL; + } +} +//------------------------------------------------------------------------------ +// May fail if malloc or new is used. +int UnusedStack() { + char* h = stackBegin(); + char* top = stackPointer(); + int n; + + for (n = 0; (h + n) < top; n++) { + if (h[n] != FILL) { + if (n >= 16) { + break; + } + // Attempt to skip used heap. + h += n; + n = 0; + } + } + return n; +} +#endif // defined(HAS_UNUSED_STACK) && HAS_UNUSED_STACK diff --git a/Firmware_V3/lib/SdFat/src/FreeStack.h b/Firmware_V3/lib/SdFat/src/FreeStack.h new file mode 100644 index 0000000..257f538 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FreeStack.h @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef FreeStack_h +#define FreeStack_h +/** + * \file + * \brief FreeStack() function. + */ +#include +#if defined(__AVR__) || defined(DOXYGEN) +#include +/** Indicate FillStack() and UnusedStack() are available. */ +#define HAS_UNUSED_STACK 1 +/** boundary between stack and heap. */ +extern char *__brkval; +/** End of bss section.*/ +extern char __bss_end; +/** Amount of free stack space. + * \return The number of free bytes. + */ +inline int FreeStack() { + char* sp = reinterpret_cast(SP); + return __brkval ? sp - __brkval : sp - &__bss_end; +} +#elif defined(ARDUINO_ARCH_APOLLO3) +#define HAS_UNUSED_STACK 0 +#elif defined(PLATFORM_ID) // Particle board +#include "Arduino.h" +inline int FreeStack() { + return System.freeMemory(); +} +#elif defined(__IMXRT1062__) +#define HAS_UNUSED_STACK 1 +extern uint8_t _ebss; +inline int FreeStack() { + register uint32_t sp asm("sp"); + return reinterpret_cast(sp) - reinterpret_cast(&_ebss); +} +#elif defined(__arm__) +#define HAS_UNUSED_STACK 1 +extern "C" char* sbrk(int incr); +inline int FreeStack() { + register uint32_t sp asm("sp"); + return reinterpret_cast(sp) - reinterpret_cast(sbrk(0)); +} +#else // defined(__AVR__) || defined(DOXYGEN) +#ifndef FREE_STACK_CPP +#warning FreeStack is not defined for this system. +#endif // FREE_STACK_CPP +inline int FreeStack() { + return 0; +} +#endif // defined(__AVR__) || defined(DOXYGEN) +#if defined(HAS_UNUSED_STACK) || defined(DOXYGEN) +/** Fill stack with 0x55 pattern */ +void FillStack(); +/** + * Determine the amount of unused stack. + * + * FillStack() must be called to fill the stack with a 0x55 pattern. + * + * UnusedStack() may fail if malloc() or new is use. + * + * \return number of bytes with 0x55 pattern. + */ +int UnusedStack(); +#else // HAS_UNUSED_STACK +#define HAS_UNUSED_STACK 0 +inline void FillStack() {} +inline int UnusedStack() {return 0;} +#endif // defined(HAS_UNUSED_STACK) +#endif // FreeStack_h diff --git a/Firmware_V3/lib/SdFat/src/FsLib/FsFile.cpp b/Firmware_V3/lib/SdFat/src/FsLib/FsFile.cpp new file mode 100644 index 0000000..e3a6da4 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FsLib/FsFile.cpp @@ -0,0 +1,206 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "FsLib.h" +//------------------------------------------------------------------------------ +FsBaseFile::FsBaseFile(const FsBaseFile& from) { + m_fFile = nullptr; + m_xFile = nullptr; + if (from.m_fFile) { + m_fFile = new (m_fileMem) FatFile; + *m_fFile = *from.m_fFile; + } else if (from.m_xFile) { + m_xFile = new (m_fileMem) ExFatFile; + *m_xFile = *from.m_xFile; + } +} +//------------------------------------------------------------------------------ +FsBaseFile& FsBaseFile::operator=(const FsBaseFile& from) { + if (this == &from) return *this; + close(); + if (from.m_fFile) { + m_fFile = new (m_fileMem) FatFile; + *m_fFile = *from.m_fFile; + } else if (from.m_xFile) { + m_xFile = new (m_fileMem) ExFatFile; + *m_xFile = *from.m_xFile; + } + return *this; +} +//------------------------------------------------------------------------------ +bool FsBaseFile::close() { + if (m_fFile && m_fFile->close()) { + m_fFile = nullptr; + return true; + } + if (m_xFile && m_xFile->close()) { + m_xFile = nullptr; + return true; + } + return false; +} +//------------------------------------------------------------------------------ +bool FsBaseFile::mkdir(FsBaseFile* dir, const char* path, bool pFlag) { + close(); + if (dir->m_fFile) { + m_fFile = new (m_fileMem) FatFile; + if (m_fFile->mkdir(dir->m_fFile, path, pFlag)) { + return true; + } + m_fFile = nullptr; + } else if (dir->m_xFile) { + m_xFile = new (m_fileMem) ExFatFile; + if (m_xFile->mkdir(dir->m_xFile, path, pFlag)) { + return true; + } + m_xFile = nullptr; + } + return false; +} +//------------------------------------------------------------------------------ +bool FsBaseFile::open(FsVolume* vol, const char* path, oflag_t oflag) { + if (!vol) { + return false; + } + close(); + if (vol->m_fVol) { + m_fFile = new (m_fileMem) FatFile; + if (m_fFile && m_fFile->open(vol->m_fVol, path, oflag)) { + return true; + } + m_fFile = nullptr; + } else if (vol->m_xVol) { + m_xFile = new (m_fileMem) ExFatFile; + if (m_xFile && m_xFile->open(vol->m_xVol, path, oflag)) { + return true; + } + m_xFile = nullptr; + } + return false; +} +//------------------------------------------------------------------------------ +bool FsBaseFile::open(FsBaseFile* dir, const char* path, oflag_t oflag) { + close(); + if (dir->m_fFile) { + m_fFile = new (m_fileMem) FatFile; + if (m_fFile->open(dir->m_fFile, path, oflag)) { + return true; + } + m_fFile = nullptr; + } else if (dir->m_xFile) { + m_xFile = new (m_fileMem) ExFatFile; + if (m_xFile->open(dir->m_xFile, path, oflag)) { + return true; + } + m_xFile = nullptr; + } + return false; +} +//------------------------------------------------------------------------------ +bool FsBaseFile::open(FsBaseFile* dir, uint32_t index, oflag_t oflag) { + close(); + if (dir->m_fFile) { + m_fFile = new (m_fileMem) FatFile; + if (m_fFile->open(dir->m_fFile, index, oflag)) { + return true; + } + m_fFile = nullptr; + } else if (dir->m_xFile) { + m_xFile = new (m_fileMem) ExFatFile; + if (m_xFile->open(dir->m_xFile, index, oflag)) { + return true; + } + m_xFile = nullptr; + } + return false; +} +//------------------------------------------------------------------------------ +bool FsBaseFile::openNext(FsBaseFile* dir, oflag_t oflag) { + close(); + if (dir->m_fFile) { + m_fFile = new (m_fileMem) FatFile; + if (m_fFile->openNext(dir->m_fFile, oflag)) { + return true; + } + m_fFile = nullptr; + } else if (dir->m_xFile) { + m_xFile = new (m_fileMem) ExFatFile; + if (m_xFile->openNext(dir->m_xFile, oflag)) { + return true; + } + m_xFile = nullptr; + } + return false; +} +//------------------------------------------------------------------------------ +bool FsBaseFile::openRoot(FsVolume* vol) { + if (!vol) { + return false; + } + close(); + if (vol->m_fVol) { + m_fFile = new (m_fileMem) FatFile; + if (m_fFile && m_fFile->openRoot(vol->m_fVol)) { + return true; + } + m_fFile = nullptr; + } else if (vol->m_xVol) { + m_xFile = new (m_fileMem) ExFatFile; + if (m_xFile && m_xFile->openRoot(vol->m_xVol)) { + return true; + } + m_xFile = nullptr; + } + return false; +} +//------------------------------------------------------------------------------ +bool FsBaseFile::remove() { + if (m_fFile) { + if (m_fFile->remove()) { + m_fFile = nullptr; + return true; + } + } else if (m_xFile) { + if (m_xFile->remove()) { + m_xFile = nullptr; + return true; + } + } + return false; +} +//------------------------------------------------------------------------------ +bool FsBaseFile::rmdir() { + if (m_fFile) { + if (m_fFile->rmdir()) { + m_fFile = nullptr; + return true; + } + } else if (m_xFile) { + if (m_xFile->rmdir()) { + m_xFile = nullptr; + return true; + } + } + return false; +} diff --git a/Firmware_V3/lib/SdFat/src/FsLib/FsFile.h b/Firmware_V3/lib/SdFat/src/FsLib/FsFile.h new file mode 100644 index 0000000..3d8e7a1 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FsLib/FsFile.h @@ -0,0 +1,812 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef FsFile_h +#define FsFile_h +/** + * \file + * \brief FsBaseFile include file. + */ +#include "FsNew.h" +#include "FatLib/FatLib.h" +#include "ExFatLib/ExFatLib.h" +/** + * \class FsBaseFile + * \brief FsBaseFile class. + */ +class FsBaseFile { + public: + /** Create an instance. */ + FsBaseFile() {} + /** Create a file object and open it in the current working directory. + * + * \param[in] path A path for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive + * OR of open flags. see FatFile::open(FatFile*, const char*, uint8_t). + */ + FsBaseFile(const char* path, oflag_t oflag) { + open(path, oflag); + } + + ~FsBaseFile() {close();} + /** Copy constructor. + * + * \param[in] from Object used to initialize this instance. + */ + FsBaseFile(const FsBaseFile& from); + /** Copy assignment operator + * \param[in] from Object used to initialize this instance. + * \return assigned object. + */ + FsBaseFile& operator=(const FsBaseFile& from); + /** The parenthesis operator. + * + * \return true if a file is open. + */ + operator bool() const {return isOpen();} + /** \return number of bytes available from the current position to EOF + * or INT_MAX if more than INT_MAX bytes are available. + */ + int available() const { + return m_fFile ? m_fFile->available() : + m_xFile ? m_xFile->available() : 0; + } + /** \return The number of bytes available from the current position + * to EOF for normal files. Zero is returned for directory files. + */ + uint64_t available64() const { + return m_fFile ? m_fFile->available32() : + m_xFile ? m_xFile->available64() : 0; + } + /** Clear writeError. */ + void clearWriteError() { + if (m_fFile) m_fFile->clearWriteError(); + if (m_xFile) m_xFile->clearWriteError(); + } + /** Close a file and force cached data and directory information + * to be written to the storage device. + * + * \return true for success or false for failure. + */ + bool close(); + /** Check for contiguous file and return its raw sector range. + * + * \param[out] bgnSector the first sector address for the file. + * \param[out] endSector the last sector address for the file. + * + * Set contiguous flag for FAT16/FAT32 files. + * Parameters may be nullptr. + * + * \return true for success or false for failure. + */ + bool contiguousRange(uint32_t* bgnSector, uint32_t* endSector) { + return m_fFile ? m_fFile->contiguousRange(bgnSector, endSector) : + m_xFile ? m_xFile->contiguousRange(bgnSector, endSector) : false; + } + /** \return The current position for a file or directory. */ + uint64_t curPosition() const { + return m_fFile ? m_fFile->curPosition() : + m_xFile ? m_xFile->curPosition() : 0; + } + /** \return Directory entry index. */ + uint32_t dirIndex() const { + return m_fFile ? m_fFile->dirIndex() : + m_xFile ? m_xFile->dirIndex() : 0; + } + /** Test for the existence of a file in a directory + * + * \param[in] path Path of the file to be tested for. + * + * The calling instance must be an open directory file. + * + * dirFile.exists("TOFIND.TXT") searches for "TOFIND.TXT" in the directory + * dirFile. + * + * \return true if the file exists else false. + */ + bool exists(const char* path) { + return m_fFile ? m_fFile->exists(path) : + m_xFile ? m_xFile->exists(path) : false; + } + /** get position for streams + * \param[out] pos struct to receive position + */ + void fgetpos(fspos_t* pos) const { + if (m_fFile) m_fFile->fgetpos(pos); + if (m_xFile) m_xFile->fgetpos(pos); + } + /** + * Get a string from a file. + * + * fgets() reads bytes from a file into the array pointed to by \a str, until + * \a num - 1 bytes are read, or a delimiter is read and transferred to \a str, + * or end-of-file is encountered. The string is then terminated + * with a null byte. + * + * fgets() deletes CR, '\\r', from the string. This insures only a '\\n' + * terminates the string for Windows text files which use CRLF for newline. + * + * \param[out] str Pointer to the array where the string is stored. + * \param[in] num Maximum number of characters to be read + * (including the final null byte). Usually the length + * of the array \a str is used. + * \param[in] delim Optional set of delimiters. The default is "\n". + * + * \return For success fgets() returns the length of the string in \a str. + * If no data is read, fgets() returns zero for EOF or -1 if an error occurred. + */ + int fgets(char* str, int num, char* delim = nullptr) { + return m_fFile ? m_fFile->fgets(str, num, delim) : + m_xFile ? m_xFile->fgets(str, num, delim) : -1; + } + /** \return The total number of bytes in a file. */ + uint64_t fileSize() const { + return m_fFile ? m_fFile->fileSize() : + m_xFile ? m_xFile->fileSize() : 0; + } + /** \return Address of first sector or zero for empty file. */ + uint32_t firstSector() const { + return m_fFile ? m_fFile->firstSector() : + m_xFile ? m_xFile->firstSector() : 0; + } + /** Ensure that any bytes written to the file are saved to the SD card. */ + void flush() {sync();} + /** set position for streams + * \param[in] pos struct with value for new position + */ + void fsetpos(const fspos_t* pos) { + if (m_fFile) m_fFile->fsetpos(pos); + if (m_xFile) m_xFile->fsetpos(pos); + } + /** Get a file's access date and time. + * + * \param[out] pdate Packed date for directory entry. + * \param[out] ptime Packed time for directory entry. + * + * \return true for success or false for failure. + */ + bool getAccessDateTime(uint16_t* pdate, uint16_t* ptime) { + return m_fFile ? m_fFile->getAccessDateTime(pdate, ptime) : + m_xFile ? m_xFile->getAccessDateTime(pdate, ptime) : false; + } + /** Get a file's create date and time. + * + * \param[out] pdate Packed date for directory entry. + * \param[out] ptime Packed time for directory entry. + * + * \return true for success or false for failure. + */ + bool getCreateDateTime(uint16_t* pdate, uint16_t* ptime) { + return m_fFile ? m_fFile->getCreateDateTime(pdate, ptime) : + m_xFile ? m_xFile->getCreateDateTime(pdate, ptime) : false; + } + /** \return All error bits. */ + uint8_t getError() const { + return m_fFile ? m_fFile->getError() : + m_xFile ? m_xFile->getError() : 0XFF; + } + /** Get a file's Modify date and time. + * + * \param[out] pdate Packed date for directory entry. + * \param[out] ptime Packed time for directory entry. + * + * \return true for success or false for failure. + */ + bool getModifyDateTime(uint16_t* pdate, uint16_t* ptime) { + return m_fFile ? m_fFile->getModifyDateTime(pdate, ptime) : + m_xFile ? m_xFile->getModifyDateTime(pdate, ptime) : false; + } + /** + * Get a file's name followed by a zero byte. + * + * \param[out] name An array of characters for the file's name. + * \param[in] len The size of the array in bytes. The array + * must be at least 13 bytes long. The file's name will be + * truncated if the file's name is too long. + * \return The length of the returned string. + */ + size_t getName(char* name, size_t len) { + *name = 0; + return m_fFile ? m_fFile->getName(name, len) : + m_xFile ? m_xFile->getName(name, len) : 0; + } + + /** \return value of writeError */ + bool getWriteError() const { + return m_fFile ? m_fFile->getWriteError() : + m_xFile ? m_xFile->getWriteError() : true; + } + /** + * Check for BlockDevice busy. + * + * \return true if busy else false. + */ + bool isBusy() { + return m_fFile ? m_fFile->isBusy() : + m_xFile ? m_xFile->isBusy() : true; + } + /** \return True if the file is contiguous. */ + bool isContiguous() const { +#if USE_FAT_FILE_FLAG_CONTIGUOUS + return m_fFile ? m_fFile->isContiguous() : + m_xFile ? m_xFile->isContiguous() : false; +#else // USE_FAT_FILE_FLAG_CONTIGUOUS + return m_xFile ? m_xFile->isContiguous() : false; +#endif // USE_FAT_FILE_FLAG_CONTIGUOUS + } + /** \return True if this is a directory else false. */ + bool isDir() const { + return m_fFile ? m_fFile->isDir() : + m_xFile ? m_xFile->isDir() : false; + } + /** This function reports if the current file is a directory or not. + * \return true if the file is a directory. + */ + bool isDirectory() const {return isDir();} + /** \return True if this is a normal file. */ + bool isFile() const { + return m_fFile ? m_fFile->isFile() : + m_xFile ? m_xFile->isFile() : false; + } + /** \return True if this is a hidden file else false. */ + bool isHidden() const { + return m_fFile ? m_fFile->isHidden() : + m_xFile ? m_xFile->isHidden() : false; + } + /** \return True if this is an open file/directory else false. */ + bool isOpen() const {return m_fFile || m_xFile;} + /** \return True file is readable. */ + bool isReadable() const { + return m_fFile ? m_fFile->isReadable() : + m_xFile ? m_xFile->isReadable() : false; + } + /** \return True if file is read-only */ + bool isReadOnly() const { + return m_fFile ? m_fFile->isReadOnly() : + m_xFile ? m_xFile->isReadOnly() : false; + } + /** \return True if this is a subdirectory file else false. */ + bool isSubDir() const { + return m_fFile ? m_fFile->isSubDir() : + m_xFile ? m_xFile->isSubDir() : false; + } + /** \return True file is writable. */ + bool isWritable() const { + return m_fFile ? m_fFile->isWritable() : + m_xFile ? m_xFile->isWritable() : false; + } +#if ENABLE_ARDUINO_SERIAL + /** List directory contents. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + */ + bool ls(uint8_t flags) { + return ls(&Serial, flags); + } + /** List directory contents. */ + bool ls() { + return ls(&Serial); + } +#endif // ENABLE_ARDUINO_SERIAL + /** List directory contents. + * + * \param[in] pr Print object. + * + * \return true for success or false for failure. + */ + bool ls(print_t* pr) { + return m_fFile ? m_fFile->ls(pr) : + m_xFile ? m_xFile->ls(pr) : false; + } + /** List directory contents. + * + * \param[in] pr Print object. + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(print_t* pr, uint8_t flags) { + return m_fFile ? m_fFile->ls(pr, flags) : + m_xFile ? m_xFile->ls(pr, flags) : false; + } + /** Make a new directory. + * + * \param[in] dir An open FatFile instance for the directory that will + * contain the new directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the new directory. + * + * \param[in] pFlag Create missing parent directories if true. + * + * \return true for success or false for failure. + */ + bool mkdir(FsBaseFile* dir, const char* path, bool pFlag = true); + /** No longer implemented due to Long File Names. + * + * Use getName(char* name, size_t size). + * \return a pointer to replacement suggestion. + */ + const char* name() const { + return "use getName()"; + } + /** Open a file or directory by name. + * + * \param[in] dir An open file instance for the directory containing + * the file to be opened. + * + * \param[in] path A path with a valid 8.3 DOS name for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a + * bitwise-inclusive OR of flags from the following list + * + * O_RDONLY - Open for reading only.. + * + * O_READ - Same as O_RDONLY. + * + * O_WRONLY - Open for writing only. + * + * O_WRITE - Same as O_WRONLY. + * + * O_RDWR - Open for reading and writing. + * + * O_APPEND - If set, the file offset shall be set to the end of the + * file prior to each write. + * + * O_AT_END - Set the initial position at the end of the file. + * + * O_CREAT - If the file exists, this flag has no effect except as noted + * under O_EXCL below. Otherwise, the file shall be created + * + * O_EXCL - If O_CREAT and O_EXCL are set, open() shall fail if the file exists. + * + * O_TRUNC - If the file exists and is a regular file, and the file is + * successfully opened and is not read only, its length shall be truncated to 0. + * + * WARNING: A given file must not be opened by more than one file object + * or file corruption may occur. + * + * \note Directory files must be opened read only. Write and truncation is + * not allowed for directory files. + * + * \return true for success or false for failure. + */ + bool open(FsBaseFile* dir, const char* path, oflag_t oflag = O_RDONLY); + /** Open a file by index. + * + * \param[in] dir An open FsFile instance for the directory. + * + * \param[in] index The \a index of the directory entry for the file to be + * opened. The value for \a index is (directory file position)/32. + * + * \param[in] oflag bitwise-inclusive OR of open flags. + * See see FsFile::open(FsFile*, const char*, uint8_t). + * + * See open() by path for definition of flags. + * \return true for success or false for failure. + */ + bool open(FsBaseFile* dir, uint32_t index, oflag_t oflag); + /** Open a file or directory by name. + * + * \param[in] vol Volume where the file is located. + * + * \param[in] path A path for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a + * bitwise-inclusive OR of open flags. + * + * \return true for success or false for failure. + */ + bool open(FsVolume* vol, const char* path, oflag_t oflag); + /** Open a file or directory by name. + * + * \param[in] path A path for a file to be opened. + * + * \param[in] oflag Values for \a oflag are constructed by a + * bitwise-inclusive OR of open flags. + * + * \return true for success or false for failure. + */ + bool open(const char* path, oflag_t oflag = O_RDONLY) { + return FsVolume::m_cwv && open(FsVolume::m_cwv, path, oflag); + } + /** Opens the next file or folder in a directory. + * \param[in] dir directory containing files. + * \param[in] oflag open flags. + * \return a file object. + */ + bool openNext(FsBaseFile* dir, oflag_t oflag = O_RDONLY); + /** Open a volume's root directory. + * + * \param[in] vol The SdFs volume containing the root directory to be opened. + * + * \return true for success or false for failure. + */ + bool openRoot(FsVolume* vol); + /** \return the current file position. */ + uint64_t position() const {return curPosition();} + /** Return the next available byte without consuming it. + * + * \return The byte if no error and not at eof else -1; + */ + int peek() { + return m_fFile ? m_fFile->peek() : + m_xFile ? m_xFile->peek() : -1; + } + /** Allocate contiguous clusters to an empty file. + * + * The file must be empty with no clusters allocated. + * + * The file will contain uninitialized data for FAT16/FAT32 files. + * exFAT files will have zero validLength and dataLength will equal + * the requested length. + * + * \param[in] length size of the file in bytes. + * \return true for success or false for failure. + */ + bool preAllocate(uint64_t length) { + return m_fFile ? length < (1ULL << 32) && m_fFile->preAllocate(length) : + m_xFile ? m_xFile->preAllocate(length) : false; + } + /** Print a file's access date and time + * + * \param[in] pr Print stream for output. + * + * \return true for success or false for failure. + */ + size_t printAccessDateTime(print_t* pr) { + return m_fFile ? m_fFile->printAccessDateTime(pr) : + m_xFile ? m_xFile->printAccessDateTime(pr) : 0; + } + /** Print a file's creation date and time + * + * \param[in] pr Print stream for output. + * + * \return true for success or false for failure. + */ + size_t printCreateDateTime(print_t* pr) { + return m_fFile ? m_fFile->printCreateDateTime(pr) : + m_xFile ? m_xFile->printCreateDateTime(pr) : 0; + } + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + size_t printField(double value, char term, uint8_t prec = 2) { + return m_fFile ? m_fFile->printField(value, term, prec) : + m_xFile ? m_xFile->printField(value, term, prec) : 0; + } + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + size_t printField(float value, char term, uint8_t prec = 2) { + return printField(static_cast(value), term, prec); + } + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \return The number of bytes written or -1 if an error occurs. + */ + template + size_t printField(Type value, char term) { + return m_fFile ? m_fFile->printField(value, term) : + m_xFile ? m_xFile->printField(value, term) : 0; + } + /** Print a file's size. + * + * \param[in] pr Print stream for output. + * + * \return The number of characters printed is returned + * for success and zero is returned for failure. + */ + size_t printFileSize(print_t* pr) { + return m_fFile ? m_fFile->printFileSize(pr) : + m_xFile ? m_xFile->printFileSize(pr) : 0; + } + /** Print a file's modify date and time + * + * \param[in] pr Print stream for output. + * + * \return true for success or false for failure. + */ + size_t printModifyDateTime(print_t* pr) { + return m_fFile ? m_fFile->printModifyDateTime(pr) : + m_xFile ? m_xFile->printModifyDateTime(pr) : 0; + } + /** Print a file's name + * + * \param[in] pr Print stream for output. + * + * \return true for success or false for failure. + */ + size_t printName(print_t* pr) { + return m_fFile ? m_fFile->printName(pr) : + m_xFile ? m_xFile->printName(pr) : 0; + } + /** Read the next byte from a file. + * + * \return For success return the next byte in the file as an int. + * If an error occurs or end of file is reached return -1. + */ + int read() { + uint8_t b; + return read(&b, 1) == 1 ? b : -1; + } + /** Read data from a file starting at the current position. + * + * \param[out] buf Pointer to the location that will receive the data. + * + * \param[in] count Maximum number of bytes to read. + * + * \return For success read() returns the number of bytes read. + * A value less than \a count, including zero, will be returned + * if end of file is reached. + * If an error occurs, read() returns -1. Possible errors include + * read() called before a file has been opened, corrupt file system + * or an I/O error occurred. + */ + int read(void* buf, size_t count) { + return m_fFile ? m_fFile->read(buf, count) : + m_xFile ? m_xFile->read(buf, count) : -1; + } + /** Remove a file. + * + * The directory entry and all data for the file are deleted. + * + * \note This function should not be used to delete the 8.3 version of a + * file that has a long name. For example if a file has the long name + * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT". + * + * \return true for success or false for failure. + */ + bool remove(); + /** Remove a file. + * + * The directory entry and all data for the file are deleted. + * + * \param[in] path Path for the file to be removed. + * + * Example use: dirFile.remove(filenameToRemove); + * + * \note This function should not be used to delete the 8.3 version of a + * file that has a long name. For example if a file has the long name + * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT". + * + * \return true for success or false for failure. + */ + bool remove(const char* path) { + return m_fFile ? m_fFile->remove(path) : + m_xFile ? m_xFile->remove(path) : false; + } + /** Rename a file or subdirectory. + * + * \param[in] newPath New path name for the file/directory. + * + * \return true for success or false for failure. + */ + bool rename(const char* newPath) { + return m_fFile ? m_fFile->rename(newPath) : + m_xFile ? m_xFile->rename(newPath) : false; + } + /** Rename a file or subdirectory. + * + * \param[in] dirFile Directory for the new path. + * \param[in] newPath New path name for the file/directory. + * + * \return true for success or false for failure. + */ + bool rename(FsBaseFile* dirFile, const char* newPath) { + return m_fFile ? m_fFile->rename(dirFile->m_fFile, newPath) : + m_xFile ? m_xFile->rename(dirFile->m_xFile, newPath) : false; + } + /** Set the file's current position to zero. */ + void rewind() { + if (m_fFile) m_fFile->rewind(); + if (m_xFile) m_xFile->rewind(); + } + /** Rewind a file if it is a directory */ + void rewindDirectory() { + if (isDir()) rewind(); + } + /** Remove a directory file. + * + * The directory file will be removed only if it is empty and is not the + * root directory. rmdir() follows DOS and Windows and ignores the + * read-only attribute for the directory. + * + * \note This function should not be used to delete the 8.3 version of a + * directory that has a long name. For example if a directory has the + * long name "New folder" you should not delete the 8.3 name "NEWFOL~1". + * + * \return true for success or false for failure. + */ + bool rmdir(); + /** Seek to a new position in the file, which must be between + * 0 and the size of the file (inclusive). + * + * \param[in] pos the new file position. + * \return true for success or false for failure. + */ + bool seek(uint64_t pos) {return seekSet(pos);} + /** Set the files position to current position + \a pos. See seekSet(). + * \param[in] offset The new position in bytes from the current position. + * \return true for success or false for failure. + */ + bool seekCur(int64_t offset) { + return seekSet(curPosition() + offset); + } + /** Set the files position to end-of-file + \a offset. See seekSet(). + * Can't be used for directory files since file size is not defined. + * \param[in] offset The new position in bytes from end-of-file. + * \return true for success or false for failure. + */ + bool seekEnd(int64_t offset = 0) { + return seekSet(fileSize() + offset); + } + /** Sets a file's position. + * + * \param[in] pos The new position in bytes from the beginning of the file. + * + * \return true for success or false for failure. + */ + bool seekSet(uint64_t pos) { + return m_fFile ? pos < (1ULL << 32) && m_fFile->seekSet(pos) : + m_xFile ? m_xFile->seekSet(pos) : false; + } + /** \return the file's size. */ + uint64_t size() const {return fileSize();} + /** The sync() call causes all modified data and directory fields + * to be written to the storage device. + * + * \return true for success or false for failure. + */ + bool sync() { + return m_fFile ? m_fFile->sync() : + m_xFile ? m_xFile->sync() : false; + } + /** Set a file's timestamps in its directory entry. + * + * \param[in] flags Values for \a flags are constructed by a bitwise-inclusive + * OR of flags from the following list + * + * T_ACCESS - Set the file's last access date and time. + * + * T_CREATE - Set the file's creation date and time. + * + * T_WRITE - Set the file's last write/modification date and time. + * + * \param[in] year Valid range 1980 - 2107 inclusive. + * + * \param[in] month Valid range 1 - 12 inclusive. + * + * \param[in] day Valid range 1 - 31 inclusive. + * + * \param[in] hour Valid range 0 - 23 inclusive. + * + * \param[in] minute Valid range 0 - 59 inclusive. + * + * \param[in] second Valid range 0 - 59 inclusive + * + * \note It is possible to set an invalid date since there is no check for + * the number of days in a month. + * + * \note + * Modify and access timestamps may be overwritten if a date time callback + * function has been set by dateTimeCallback(). + * + * \return true for success or false for failure. + */ + bool timestamp(uint8_t flags, uint16_t year, uint8_t month, uint8_t day, + uint8_t hour, uint8_t minute, uint8_t second) { + return m_fFile ? + m_fFile->timestamp(flags, year, month, day, hour, minute, second) : + m_xFile ? + m_xFile->timestamp(flags, year, month, day, hour, minute, second) : + false; + } + /** Truncate a file to the current position. + * + * \return true for success or false for failure. + */ + bool truncate() { + return m_fFile ? m_fFile->truncate() : + m_xFile ? m_xFile->truncate() : false; + } + /** Truncate a file to a specified length. + * The current file position will be set to end of file. + * + * \param[in] length The desired length for the file. + * + * \return true for success or false for failure. + */ + bool truncate(uint64_t length) { + return m_fFile ? length < (1ULL << 32) && m_fFile->truncate(length) : + m_xFile ? m_xFile->truncate(length) : false; + } + /** Write a byte to a file. Required by the Arduino Print class. + * \param[in] b the byte to be written. + * Use getWriteError to check for errors. + * \return 1 for success and 0 for failure. + */ + size_t write(uint8_t b) {return write(&b, 1);} + /** Write data to an open file. + * + * \note Data is moved to the cache but may not be written to the + * storage device until sync() is called. + * + * \param[in] buf Pointer to the location of the data to be written. + * + * \param[in] count Number of bytes to write. + * + * \return For success write() returns the number of bytes written, always + * \a nbyte. If an error occurs, write() returns -1. Possible errors + * include write() is called before a file has been opened, write is called + * for a read-only file, device is full, a corrupt file system or an + * I/O error. + */ + size_t write(const void* buf, size_t count) { + return m_fFile ? m_fFile->write(buf, count) : + m_xFile ? m_xFile->write(buf, count) : 0; + } + + private: + newalign_t m_fileMem[FS_ALIGN_DIM(ExFatFile, FatFile)]; + FatFile* m_fFile = nullptr; + ExFatFile* m_xFile = nullptr; +}; +/** + * \class FsFile + * \brief FsBaseFile file with Arduino Stream. + */ +class FsFile : public StreamFile { + public: + /** Opens the next file or folder in a directory. + * + * \param[in] oflag open flags. + * \return a FatStream object. + */ + FsFile openNextFile(oflag_t oflag = O_RDONLY) { + FsFile tmpFile; + tmpFile.openNext(this, oflag); + return tmpFile; + } +}; +#endif // FsFile_h diff --git a/Firmware_V3/lib/SdFat/src/FsLib/FsLib.h b/Firmware_V3/lib/SdFat/src/FsLib/FsLib.h new file mode 100644 index 0000000..a39f620 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FsLib/FsLib.h @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef FsLib_h +#define FsLib_h +/** + * \file + * \brief FsLib include file. + */ +#include "FsVolume.h" +#include "FsFile.h" +#endif // FsLib_h diff --git a/Firmware_V3/lib/SdFat/src/FsLib/FsNew.cpp b/Firmware_V3/lib/SdFat/src/FsLib/FsNew.cpp new file mode 100644 index 0000000..1b3c04e --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FsLib/FsNew.cpp @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "FsNew.h" +void* operator new(size_t size, newalign_t* ptr) { + (void)size; + return ptr; +} diff --git a/Firmware_V3/lib/SdFat/src/FsLib/FsNew.h b/Firmware_V3/lib/SdFat/src/FsLib/FsNew.h new file mode 100644 index 0000000..bd08a50 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FsLib/FsNew.h @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef FsNew_h +#define FsNew_h +#include +#include + +/** 32-bit alignment */ +typedef uint32_t newalign_t; + +/** Size required for exFAT or FAT class. */ +#define FS_SIZE(etype, ftype) \ + (sizeof(ftype) < sizeof(etype) ? sizeof(etype) : sizeof(ftype)) + +/** Dimension of aligned area. */ +#define NEW_ALIGN_DIM(n) \ + (((size_t)(n) + sizeof(newalign_t) - 1U)/sizeof(newalign_t)) + +/** Dimension of aligned area for etype or ftype class. */ +#define FS_ALIGN_DIM(etype, ftype) NEW_ALIGN_DIM(FS_SIZE(etype, ftype)) + +/** Custom new placement operator */ +void* operator new(size_t size, newalign_t* ptr); +#endif // FsNew_h diff --git a/Firmware_V3/lib/SdFat/src/FsLib/FsVolume.cpp b/Firmware_V3/lib/SdFat/src/FsLib/FsVolume.cpp new file mode 100644 index 0000000..3121486 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FsLib/FsVolume.cpp @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "FsLib.h" +FsVolume* FsVolume::m_cwv = nullptr; +//------------------------------------------------------------------------------ +bool FsVolume::begin(BlockDevice* blockDev) { + m_blockDev = blockDev; + m_fVol = nullptr; + m_xVol = new (m_volMem) ExFatVolume; + if (m_xVol && m_xVol->begin(m_blockDev, false)) { + goto done; + } + m_xVol = nullptr; + m_fVol = new (m_volMem) FatVolume; + if (m_fVol && m_fVol->begin(m_blockDev, false)) { + goto done; + } + m_cwv = nullptr; + m_fVol = nullptr; + return false; + + done: + m_cwv = this; + return true; +} +//------------------------------------------------------------------------------ +bool FsVolume::ls(print_t* pr, const char* path, uint8_t flags) { + FsBaseFile dir; + return dir.open(this, path, O_RDONLY) && dir.ls(pr, flags); +} +//------------------------------------------------------------------------------ +FsFile FsVolume::open(const char *path, oflag_t oflag) { + FsFile tmpFile; + tmpFile.open(this, path, oflag); + return tmpFile; +} +#if ENABLE_ARDUINO_STRING +//------------------------------------------------------------------------------ +FsFile FsVolume::open(const String &path, oflag_t oflag) { + return open(path.c_str(), oflag ); +} +#endif // ENABLE_ARDUINO_STRING diff --git a/Firmware_V3/lib/SdFat/src/FsLib/FsVolume.h b/Firmware_V3/lib/SdFat/src/FsLib/FsVolume.h new file mode 100644 index 0000000..bcac166 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/FsLib/FsVolume.h @@ -0,0 +1,385 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef FsVolume_h +#define FsVolume_h +/** + * \file + * \brief FsVolume include file. + */ +#include "FsNew.h" +#include "../FatLib/FatLib.h" +#include "../ExFatLib/ExFatLib.h" + +class FsFile; +/** + * \class FsVolume + * \brief FsVolume class. + */ +class FsVolume { + public: + FsVolume() {} + + ~FsVolume() {end();} + + /** + * Initialize an FatVolume object. + * \param[in] blockDev Device block driver. + * \return true for success or false for failure. + */ + bool begin(BlockDevice* blockDev); +#ifndef DOXYGEN_SHOULD_SKIP_THIS + // Use sectorsPerCluster(). blocksPerCluster() will be removed in the future. + uint32_t blocksPerCluster() __attribute__ ((deprecated)) {return sectorsPerCluster();} //NOLINT +#endif // DOXYGEN_SHOULD_SKIP_THIS + /** \return the number of bytes in a cluster. */ + uint32_t bytesPerCluster() const { + return m_fVol ? m_fVol->bytesPerCluster() : + m_xVol ? m_xVol->bytesPerCluster() : 0; + } + /** + * Set volume working directory to root. + * \return true for success or false for failure. + */ + bool chdir() { + return m_fVol ? m_fVol->chdir() : + m_xVol ? m_xVol->chdir() : false; + } + /** + * Set volume working directory. + * \param[in] path Path for volume working directory. + * \return true for success or false for failure. + */ + bool chdir(const char* path) { + return m_fVol ? m_fVol->chdir(path) : + m_xVol ? m_xVol->chdir(path) : false; + } + /** Change global working volume to this volume. */ + void chvol() {m_cwv = this;} + /** \return The total number of clusters in the volume. */ + uint32_t clusterCount() const { + return m_fVol ? m_fVol->clusterCount() : + m_xVol ? m_xVol->clusterCount() : 0; + } + /** \return The logical sector number for the start of file data. */ + uint32_t dataStartSector() const { + return m_fVol ? m_fVol->dataStartSector() : + m_xVol ? m_xVol->clusterHeapStartSector() : 0; + } + /** free dynamic memory and end access to volume */ + void end() { + m_fVol = nullptr; + m_xVol = nullptr; + } + /** Test for the existence of a file in a directory + * + * \param[in] path Path of the file to be tested for. + * + * \return true if the file exists else false. + */ + bool exists(const char* path) { + return m_fVol ? m_fVol->exists(path) : + m_xVol ? m_xVol->exists(path) : false; + } + /** \return The logical sector number for the start of the first FAT. */ + uint32_t fatStartSector() const { + return m_fVol ? m_fVol->fatStartSector() : + m_xVol ? m_xVol->fatStartSector() : 0; + } + /** \return Partition type, FAT_TYPE_EXFAT, FAT_TYPE_FAT32, + * FAT_TYPE_FAT16, or zero for error. + */ + uint8_t fatType() const { + return m_fVol ? m_fVol->fatType() : + m_xVol ? m_xVol->fatType() : 0; + } + /** \return the free cluster count. */ + uint32_t freeClusterCount() const { + return m_fVol ? m_fVol->freeClusterCount() : + m_xVol ? m_xVol->freeClusterCount() : 0; + } + /** + * Check for BlockDevice busy. + * + * \return true if busy else false. + */ + bool isBusy() { + return m_fVol ? m_fVol->isBusy() : + m_xVol ? m_xVol->isBusy() : false; + } + /** List directory contents. + * + * \param[in] pr Print object. + * + * \return true for success or false for failure. + */ + bool ls(print_t* pr) { + return m_fVol ? m_fVol->ls(pr) : + m_xVol ? m_xVol->ls(pr) : false; + } + /** List directory contents. + * + * \param[in] pr Print object. + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(print_t* pr, uint8_t flags) { + return m_fVol ? m_fVol->ls(pr, flags) : + m_xVol ? m_xVol->ls(pr, flags) : false; + } + /** List the directory contents of a directory. + * + * \param[in] pr Print stream for list. + * + * \param[in] path directory to list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(print_t* pr, const char* path, uint8_t flags); + /** Make a subdirectory in the volume root directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the subdirectory. + * + * \param[in] pFlag Create missing parent directories if true. + * + * \return true for success or false for failure. + */ + bool mkdir(const char *path, bool pFlag = true) { + return m_fVol ? m_fVol->mkdir(path, pFlag) : + m_xVol ? m_xVol->mkdir(path, pFlag) : false; + } + /** open a file + * + * \param[in] path location of file to be opened. + * \param[in] oflag open flags. + * \return a FsBaseFile object. + */ + FsFile open(const char* path, oflag_t oflag = O_RDONLY); + /** Remove a file from the volume root directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the file. + * + * \return true for success or false for failure. + */ + bool remove(const char *path) { + return m_fVol ? m_fVol->remove(path) : + m_xVol ? m_xVol->remove(path) : false; + } + /** Rename a file or subdirectory. + * + * \param[in] oldPath Path name to the file or subdirectory to be renamed. + * + * \param[in] newPath New path name of the file or subdirectory. + * + * The \a newPath object must not exist before the rename call. + * + * The file to be renamed must not be open. The directory entry may be + * moved and file system corruption could occur if the file is accessed by + * a file object that was opened before the rename() call. + * + * \return true for success or false for failure. + */ + bool rename(const char *oldPath, const char *newPath) { + return m_fVol ? m_fVol->rename(oldPath, newPath) : + m_xVol ? m_xVol->rename(oldPath, newPath) : false; + } + /** Remove a subdirectory from the volume's root directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the subdirectory. + * + * The subdirectory file will be removed only if it is empty. + * + * \return true for success or false for failure. + */ + bool rmdir(const char *path) { + return m_fVol ? m_fVol->rmdir(path) : + m_xVol ? m_xVol->rmdir(path) : false; + } + /** \return The volume's cluster size in sectors. */ + uint32_t sectorsPerCluster() const { + return m_fVol ? m_fVol->sectorsPerCluster() : + m_xVol ? m_xVol->sectorsPerCluster() : 0; + } +#if ENABLE_ARDUINO_SERIAL + /** List directory contents. + * \return true for success or false for failure. + */ + bool ls() { + return ls(&Serial); + } + /** List directory contents. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + */ + bool ls(uint8_t flags) { + return ls(&Serial, flags); + } + /** List the directory contents of a directory to Serial. + * + * \param[in] path directory to list. + * + * \param[in] flags The inclusive OR of + * + * LS_DATE - %Print file modification date + * + * LS_SIZE - %Print file size. + * + * LS_R - Recursive list of subdirectories. + * + * \return true for success or false for failure. + * + * \return true for success or false for failure. + */ + bool ls(const char* path, uint8_t flags = 0) { + return ls(&Serial, path, flags); + } +#endif // ENABLE_ARDUINO_SERIAL +#if ENABLE_ARDUINO_STRING + /** + * Set volume working directory. + * \param[in] path Path for volume working directory. + * \return true for success or false for failure. + */ + bool chdir(const String& path) { + return chdir(path.c_str()); + } + /** Test for the existence of a file in a directory + * + * \param[in] path Path of the file to be tested for. + * + * \return true if the file exists else false. + */ + bool exists(const String &path) { + return exists(path.c_str()); + } + /** Make a subdirectory in the volume root directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the subdirectory. + * + * \param[in] pFlag Create missing parent directories if true. + * + * \return true for success or false for failure. + */ + bool mkdir(const String &path, bool pFlag = true) { + return mkdir(path.c_str(), pFlag); + } + /** open a file + * + * \param[in] path location of file to be opened. + * \param[in] oflag open flags. + * \return a FsBaseFile object. + */ + FsFile open(const String &path, oflag_t oflag = O_RDONLY); + /** Remove a file from the volume root directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the file. + * + * \return true for success or false for failure. + */ + bool remove(const String &path) { + return remove(path.c_str()); + } + /** Rename a file or subdirectory. + * + * \param[in] oldPath Path name to the file or subdirectory to be renamed. + * + * \param[in] newPath New path name of the file or subdirectory. + * + * The \a newPath object must not exist before the rename call. + * + * The file to be renamed must not be open. The directory entry may be + * moved and file system corruption could occur if the file is accessed by + * a file object that was opened before the rename() call. + * + * \return true for success or false for failure. + */ + bool rename(const String& oldPath, const String& newPath) { + return rename(oldPath.c_str(), newPath.c_str()); + } + /** Remove a subdirectory from the volume's root directory. + * + * \param[in] path A path with a valid 8.3 DOS name for the subdirectory. + * + * The subdirectory file will be removed only if it is empty. + * + * \return true for success or false for failure. + */ + bool rmdir(const String &path) { + return rmdir(path.c_str()); + } + /** Rename a file or subdirectory. + * + * \param[in] oldPath Path name to the file or subdirectory to be renamed. + * + * \param[in] newPath New path name of the file or subdirectory. + * + * The \a newPath object must not exist before the rename call. + * + * The file to be renamed must not be open. The directory entry may be + * moved and file system corruption could occur if the file is accessed by + * a file object that was opened before the rename() call. + * + * \return true for success or false for failure. + */ +#endif // ENABLE_ARDUINO_STRING + + protected: + newalign_t m_volMem[FS_ALIGN_DIM(ExFatVolume, FatVolume)]; + + private: + /** FsBaseFile allowed access to private members. */ + friend class FsBaseFile; + static FsVolume* cwv() {return m_cwv;} + FsVolume(const FsVolume& from); + FsVolume& operator=(const FsVolume& from); + + static FsVolume* m_cwv; + FatVolume* m_fVol = nullptr; + ExFatVolume* m_xVol = nullptr; + BlockDevice* m_blockDev; +}; +#endif // FsVolume_h diff --git a/Firmware_V3/lib/SdFat/src/MinimumSerial.cpp b/Firmware_V3/lib/SdFat/src/MinimumSerial.cpp new file mode 100644 index 0000000..b4e928f --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/MinimumSerial.cpp @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "common/SysCall.h" +#if defined(UDR0) || defined(DOXYGEN) +#include "MinimumSerial.h" +const uint16_t MIN_2X_BAUD = F_CPU/(4*(2*0XFFF + 1)) + 1; +//------------------------------------------------------------------------------ +int MinimumSerial::available() { + return UCSR0A & (1 << RXC0) ? 1 : 0; +} +//------------------------------------------------------------------------------ +void MinimumSerial::begin(uint32_t baud) { + uint16_t baud_setting; + // don't worry, the compiler will squeeze out F_CPU != 16000000UL + if ((F_CPU != 16000000UL || baud != 57600) && baud > MIN_2X_BAUD) { + // Double the USART Transmission Speed + UCSR0A = 1 << U2X0; + baud_setting = (F_CPU / 4 / baud - 1) / 2; + } else { + // hardcoded exception for compatibility with the bootloader shipped + // with the Duemilanove and previous boards and the firmware on the 8U2 + // on the Uno and Mega 2560. + UCSR0A = 0; + baud_setting = (F_CPU / 8 / baud - 1) / 2; + } + // assign the baud_setting + UBRR0H = baud_setting >> 8; + UBRR0L = baud_setting; + // enable transmit and receive + UCSR0B |= (1 << TXEN0) | (1 << RXEN0); +} +//------------------------------------------------------------------------------ +void MinimumSerial::flush() { + while (((1 << UDRIE0) & UCSR0B) || !(UCSR0A & (1 << UDRE0))) {} +} +//------------------------------------------------------------------------------ +int MinimumSerial::read() { + if (UCSR0A & (1 << RXC0)) { + return UDR0; + } + return -1; +} +//------------------------------------------------------------------------------ +size_t MinimumSerial::write(uint8_t b) { + while (((1 << UDRIE0) & UCSR0B) || !(UCSR0A & (1 << UDRE0))) {} + UDR0 = b; + return 1; +} +#endif // defined(UDR0) || defined(DOXYGEN) diff --git a/Firmware_V3/lib/SdFat/src/MinimumSerial.h b/Firmware_V3/lib/SdFat/src/MinimumSerial.h new file mode 100644 index 0000000..11dd05b --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/MinimumSerial.h @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + /** + * \file + * \brief Minimal AVR Serial driver. + */ +#ifndef MinimumSerial_h +#define MinimumSerial_h +#include "common/SysCall.h" +//============================================================================== +/** + * \class MinimumSerial + * \brief mini serial class for the %SdFat library. + */ +class MinimumSerial : public print_t { + public: + /** \return true for hardware serial */ + operator bool() {return true;} + /** + * \return one if data is available. + */ + int available(); + /** + * Set baud rate for serial port zero and enable in non interrupt mode. + * Do not call this function if you use another serial library. + * \param[in] baud rate + */ + void begin(uint32_t baud); + /** Wait for write done. */ + void flush(); + /** + * Unbuffered read + * \return -1 if no character is available or an available character. + */ + int read(); + /** + * Unbuffered write + * + * \param[in] b byte to write. + * \return 1 + */ + size_t write(uint8_t b); + using print_t::write; +}; +#endif // MinimumSerial_h diff --git a/Firmware_V3/lib/SdFat/src/RingBuf.h b/Firmware_V3/lib/SdFat/src/RingBuf.h new file mode 100644 index 0000000..eb4cd60 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/RingBuf.h @@ -0,0 +1,358 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef RingBuf_h +#define RingBuf_h +/** + * \file + * \brief Ring buffer for data loggers. + */ +#include "Arduino.h" +#include "common/FmtNumber.h" + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +// Teensy 3.5/3.6 has hard fault at 0x20000000 for unaligned memcpy. +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) +inline bool is_aligned(const void* ptr, uintptr_t alignment) { + auto iptr = reinterpret_cast(ptr); + return !(iptr % alignment); +} +inline void memcpyBuf(void* dst, const void* src, size_t len) { + const uint8_t* b = reinterpret_cast(0X20000000UL); + uint8_t* d = reinterpret_cast(dst); + const uint8_t *s = reinterpret_cast(src); + if ((is_aligned(d, 4) && is_aligned(s, 4) && (len & 3) == 0) || + !((d < b && b <= (d + len)) || (s < b && b <= (s + len)))) { + memcpy(dst, src, len); + } else { + while (len--) { + *d++ = *s++; + } + } +} +#else // defined(__MK64FX512__) || defined(__MK66FX1M0__) +inline void memcpyBuf(void* dst, const void* src, size_t len) { + memcpy(dst, src, len); +} +#endif // defined(__MK64FX512__) || defined(__MK66FX1M0__) +#endif // DOXYGEN_SHOULD_SKIP_THIS +/** + * \class RingBuf + * \brief Ring buffer for data loggers. + * + * This ring buffer may be used in ISRs. bytesFreeIsr(), bytesUsedIsr(), + * memcopyIn(), and memcopyOut() are ISR callable. For ISR use call + * memcopyIn() in the ISR and use writeOut() in non-interrupt code + * to write data to a file. readIn() and memcopyOut can be use in a + * similar way to provide file data to an ISR. + * + * Print into a RingBuf in an ISR should also work but has not been verified. + */ +template +class RingBuf : public Print { + public: + /** + * RingBuf Constructor. + */ + RingBuf() {} + /** + * Initialize RingBuf. + * \param[in] file Underlying file. + */ + void begin(F* file) { + m_file = file; + m_count = 0; + m_head = 0; + m_tail = 0; + clearWriteError(); + } + /** + * + * \return the RingBuf free space in bytes. Not ISR callable. + */ + size_t bytesFree() const { + size_t count; + noInterrupts(); + count = m_count; + interrupts(); + return Size - count; + } + /** + * \return the RingBuf free space in bytes. ISR callable. + */ + size_t bytesFreeIsr() const { + return Size - m_count; + } + /** + * \return the RingBuf used space in bytes. Not ISR callable. + */ + size_t bytesUsed() const { + size_t count; + noInterrupts(); + count = m_count; + interrupts(); + return count; + } + /** + * \return the RingBuf used space in bytes. ISR callable. + */ + size_t bytesUsedIsr() const { + return m_count; + } + /** + * Copy data to the RingBuf from buf. + * The number of bytes copied may be less than count if + * count is greater than bytesFree. + * + * This function may be used in an ISR with writeOut() + * in non-interrupt code. + * + * \param[in] buf Location of data to be copied. + * \param[in] count number of bytes to be copied. + * \return Number of bytes actually copied. + */ + size_t memcpyIn(const void* buf, size_t count) { + const uint8_t* src = (const uint8_t*)buf; + size_t n = Size - m_count; + if (count > n) { + count = n; + } + size_t nread = 0; + while (nread != count) { + n = minSize(Size - m_head, count - nread); + memcpyBuf(m_buf + m_head, src + nread, n); + m_head = advance(m_head, n); + nread += n; + } + m_count += nread; + return nread; + } + /** + * Copy date from the RingBuf to buf. + * The number of bytes copied may be less than count if + * bytesUsed is less than count. + * + * This function may be used in an ISR with readIn() in + * non-interrupt code. + * + * \param[out] buf Location to receive the data. + * \param[in] count number of bytes to be copied. + * \return Number of bytes actually copied. + */ + size_t memcpyOut(void* buf, size_t count) { + uint8_t* dst = reinterpret_cast(buf); + size_t nwrite = 0; + size_t n = m_count; + if (count > n) { + count = n; + } + while (nwrite != count) { + n = minSize(Size - m_tail, count - nwrite); + memcpyBuf(dst + nwrite, m_buf + m_tail, n); + m_tail = advance(m_tail, n); + nwrite += n; + } + m_count -= nwrite; + return nwrite; + } + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written. + */ + size_t printField(double value, char term, uint8_t prec = 2) { + char buf[24]; + char* str = buf + sizeof(buf); + if (term) { + *--str = term; + if (term == '\n') { + *--str = '\r'; + } + } + str = fmtDouble(str, value, prec, false); + return write(str, buf + sizeof(buf) - str); + } + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + size_t printField(float value, char term, uint8_t prec = 2) { + return printField(static_cast(value), term, prec); + } + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. Use '\\n' for CR LF. + * \return The number of bytes written or -1 if an error occurs. + */ + template + size_t printField(Type value, char term) { + char sign = 0; + char buf[3*sizeof(Type) + 3]; + char* str = buf + sizeof(buf); + + if (term) { + *--str = term; + if (term == '\n') { + *--str = '\r'; + } + } + if (value < 0) { + value = -value; + sign = '-'; + } + if (sizeof(Type) < 4) { + str = fmtBase10(str, (uint16_t)value); + } else { + str = fmtBase10(str, (uint32_t)value); + } + if (sign) { + *--str = sign; + } + return write((const uint8_t*)str, &buf[sizeof(buf)] - str); + } + /** + * Read data into the RingBuf from the underlying file. + * the number of bytes read may be less than count if + * bytesFree is less than count. + * + * This function may be used in non-interrupt code with + * memcopyOut() in an ISR. + * + * \param[in] count number of bytes to be read. + * \return Number of bytes actually read. + */ + size_t readIn(size_t count) { + size_t nread = 0; + size_t n = bytesFree(); // Protected from interrupts. + if (count > n) { + count = n; + } + while (nread != count) { + n = minSize(Size - m_head, count - nread); + if ((size_t)m_file->read(m_buf + m_head, n) != n) { + return nread; + } + m_head = advance(m_head, n); + nread += n; + } + noInterrupts(); + m_count += nread; + interrupts(); + return nread; + } + /** + * Write all data in the RingBuf to the underlying file. + * \param[in] data Byte to be written. + * \return Number of bytes actually written. + */ + bool sync() { + size_t n = bytesUsed(); + return writeOut(n) == n; + } + /** + * Copy data to the RingBuf from buf. + * + * The number of bytes copied may be less than count if + * count is greater than bytesFree. + * Use getWriteError() to check for print errors and + * clearWriteError() to clear error. + * + * \param[in] buf Location of data to be written. + * \param[in] count number of bytes to be written. + * \return Number of bytes actually written. + */ + size_t write(const void* buf, size_t count) { + if (count > bytesFree()) { + setWriteError(); + } + return memcpyIn(buf, count); + } + /** + * Override virtual function in Print for efficiency. + * + * \param[in] buf Location of data to be written. + * \param[in] count number of bytes to be written. + * \return Number of bytes actually written. + */ + size_t write(const uint8_t* buf, size_t count) override { + return write((const void*)buf, count); + } + /** + * Required function for Print. + * \param[in] data Byte to be written. + * \return Number of bytes actually written. + */ + size_t write(uint8_t data) override { + return write(&data, 1); + } + /** + * Write data to file from RingBuf buffer. + * \param[in] count number of bytes to be written. + * + * The number of bytes written may be less than count if + * bytesUsed is less than count or if an error occurs. + * + * This function may be used in non-interrupt code with + * memcopyIn() in an ISR. + * + * \return Number of bytes actually written. + */ + size_t writeOut(size_t count) { + size_t n = bytesUsed(); // Protected from interrupts; + if (count > n) { + count = n; + } + size_t nwrite = 0; + while (nwrite != count) { + n = minSize(Size - m_tail, count - nwrite); + if (m_file->write(m_buf + m_tail, n) != n) { + break; + } + m_tail = advance(m_tail, n); + nwrite += n; + } + noInterrupts(); + m_count -= nwrite; + interrupts(); + return nwrite; + } + + private: + uint8_t __attribute__((aligned(4))) m_buf[Size]; + F* m_file = nullptr; + volatile size_t m_count; + size_t m_head; + size_t m_tail; + + size_t advance(size_t index, size_t n) { + index += n; + return index < Size ? index : index - Size; + } + // avoid macro MIN + size_t minSize(size_t a, size_t b) {return a < b ? a : b;} +}; +#endif // RingBuf_h diff --git a/Firmware_V3/lib/SdFat/src/SdCard/CPPLINT.cfg b/Firmware_V3/lib/SdFat/src/SdCard/CPPLINT.cfg new file mode 100644 index 0000000..7c360c7 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SdCard/CPPLINT.cfg @@ -0,0 +1 @@ +exclude_files=SdioTeensy.h \ No newline at end of file diff --git a/Firmware_V3/lib/SdFat/src/SdCard/SdCard.h b/Firmware_V3/lib/SdFat/src/SdCard/SdCard.h new file mode 100644 index 0000000..7a7c68e --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SdCard/SdCard.h @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef SdCard_h +#define SdCard_h +#include "SdioCard.h" +#include "SdSpiCard.h" +#if HAS_SDIO_CLASS +typedef SdCardInterface SdCard; +#else // HAS_SDIO_CLASS +typedef SdSpiCard SdCard; +#endif // HAS_SDIO_CLASS +/** Determine card configuration type. + * + * \param[in] cfg Card configuration. + * \return true if SPI. + */ +inline bool isSpi(SdSpiConfig cfg) {(void)cfg; return true;} +/** Determine card configuration type. + * + * \param[in] cfg Card configuration. + * \return true if SPI. + */ +inline bool isSpi(SdioConfig cfg) {(void)cfg; return false;} +/** + * \class SdCardFactory + * \brief Setup a SPI card or SDIO card. + */ +class SdCardFactory { + public: + /** Initialize SPI card. + * + * \param[in] config SPI configuration. + * \return generic card pointer. + */ + SdCard* newCard(SdSpiConfig config) { + m_spiCard.begin(config); + return &m_spiCard; + } + /** Initialize SDIO card. + * + * \param[in] config SDIO configuration. + * \return generic card pointer or nullptr if SDIO is not supported. + */ + SdCard* newCard(SdioConfig config) { +#if HAS_SDIO_CLASS + m_sdioCard.begin(config); + return &m_sdioCard; +#else // HAS_SDIO_CLASS + (void)config; + return nullptr; +#endif // HAS_SDIO_CLASS + } + + private: +#if HAS_SDIO_CLASS + SdioCard m_sdioCard; +#endif // HAS_SDIO_CLASS + SdSpiCard m_spiCard; +}; +#endif // SdCard_h diff --git a/Firmware_V3/lib/SdFat/src/SdCard/SdCardInfo.cpp b/Firmware_V3/lib/SdFat/src/SdCard/SdCardInfo.cpp new file mode 100644 index 0000000..3536800 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SdCard/SdCardInfo.cpp @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "SdCardInfo.h" +//------------------------------------------------------------------------------ +#undef SD_CARD_ERROR +#define SD_CARD_ERROR(e, m) case SD_CARD_ERROR_##e: pr->print(F(#e)); break; +void printSdErrorSymbol(print_t* pr, uint8_t code) { + pr->print(F("SD_CARD_ERROR_")); + switch (code) { + SD_ERROR_CODE_LIST + default: pr->print(F("UNKNOWN")); + } +} +//------------------------------------------------------------------------------ +#undef SD_CARD_ERROR +#define SD_CARD_ERROR(e, m) case SD_CARD_ERROR_##e: pr->print(F(m)); break; +void printSdErrorText(print_t* pr, uint8_t code) { + switch + (code) { + SD_ERROR_CODE_LIST + default: pr->print(F("Unknown error")); + } +} diff --git a/Firmware_V3/lib/SdFat/src/SdCard/SdCardInfo.h b/Firmware_V3/lib/SdFat/src/SdCard/SdCardInfo.h new file mode 100644 index 0000000..d84f6cd --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SdCard/SdCardInfo.h @@ -0,0 +1,490 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef SdCardInfo_h +#define SdCardInfo_h +#include +#include "../common/SysCall.h" +// Based on the document: +// +// SD Specifications +// Part 1 +// Physical Layer +// Simplified Specification +// Version 5.00 +// Aug 10, 2016 +// +// https://www.sdcard.org/downloads/pls/ +//------------------------------------------------------------------------------ +// SD card errors +// See the SD Specification for command info. +#define SD_ERROR_CODE_LIST\ + SD_CARD_ERROR(NONE, "No error")\ + SD_CARD_ERROR(CMD0, "Card reset failed")\ + SD_CARD_ERROR(CMD2, "SDIO read CID")\ + SD_CARD_ERROR(CMD3, "SDIO publish RCA")\ + SD_CARD_ERROR(CMD6, "Switch card function")\ + SD_CARD_ERROR(CMD7, "SDIO card select")\ + SD_CARD_ERROR(CMD8, "Send and check interface settings")\ + SD_CARD_ERROR(CMD9, "Read CSD data")\ + SD_CARD_ERROR(CMD10, "Read CID data")\ + SD_CARD_ERROR(CMD12, "Stop multiple block read")\ + SD_CARD_ERROR(CMD13, "Read card status")\ + SD_CARD_ERROR(CMD17, "Read single block")\ + SD_CARD_ERROR(CMD18, "Read multiple blocks")\ + SD_CARD_ERROR(CMD24, "Write single block")\ + SD_CARD_ERROR(CMD25, "Write multiple blocks")\ + SD_CARD_ERROR(CMD32, "Set first erase block")\ + SD_CARD_ERROR(CMD33, "Set last erase block")\ + SD_CARD_ERROR(CMD38, "Erase selected blocks")\ + SD_CARD_ERROR(CMD58, "Read OCR register")\ + SD_CARD_ERROR(CMD59, "Set CRC mode")\ + SD_CARD_ERROR(ACMD6, "Set SDIO bus width")\ + SD_CARD_ERROR(ACMD13, "Read extended status")\ + SD_CARD_ERROR(ACMD23, "Set pre-erased count")\ + SD_CARD_ERROR(ACMD41, "Activate card initialization")\ + SD_CARD_ERROR(READ_TOKEN, "Bad read data token")\ + SD_CARD_ERROR(READ_CRC, "Read CRC error")\ + SD_CARD_ERROR(READ_FIFO, "SDIO fifo read timeout")\ + SD_CARD_ERROR(READ_REG, "Read CID or CSD failed.")\ + SD_CARD_ERROR(READ_START, "Bad readStart argument")\ + SD_CARD_ERROR(READ_TIMEOUT, "Read data timeout")\ + SD_CARD_ERROR(STOP_TRAN, "Multiple block stop failed")\ + SD_CARD_ERROR(TRANSFER_COMPLETE, "SDIO transfer complete")\ + SD_CARD_ERROR(WRITE_DATA, "Write data not accepted")\ + SD_CARD_ERROR(WRITE_FIFO, "SDIO fifo write timeout")\ + SD_CARD_ERROR(WRITE_START, "Bad writeStart argument")\ + SD_CARD_ERROR(WRITE_PROGRAMMING, "Flash programming")\ + SD_CARD_ERROR(WRITE_TIMEOUT, "Write timeout")\ + SD_CARD_ERROR(DMA, "DMA transfer failed")\ + SD_CARD_ERROR(ERASE, "Card did not accept erase commands")\ + SD_CARD_ERROR(ERASE_SINGLE_SECTOR, "Card does not support erase")\ + SD_CARD_ERROR(ERASE_TIMEOUT, "Erase command timeout")\ + SD_CARD_ERROR(INIT_NOT_CALLED, "Card has not been initialized")\ + SD_CARD_ERROR(INVALID_CARD_CONFIG, "Invalid card config")\ + SD_CARD_ERROR(FUNCTION_NOT_SUPPORTED, "Unsupported SDIO command") + +enum { +#define SD_CARD_ERROR(e, m) SD_CARD_ERROR_##e, + SD_ERROR_CODE_LIST +#undef SD_CARD_ERROR + SD_CARD_ERROR_UNKNOWN +}; +void printSdErrorSymbol(print_t* pr, uint8_t code); +void printSdErrorText(print_t* pr, uint8_t code); +//------------------------------------------------------------------------------ +// card types +/** Standard capacity V1 SD card */ +const uint8_t SD_CARD_TYPE_SD1 = 1; +/** Standard capacity V2 SD card */ +const uint8_t SD_CARD_TYPE_SD2 = 2; +/** High Capacity SD card */ +const uint8_t SD_CARD_TYPE_SDHC = 3; +//------------------------------------------------------------------------------ +// SD operation timeouts +/** CMD0 retry count */ +const uint8_t SD_CMD0_RETRY = 10; +/** command timeout ms */ +const uint16_t SD_CMD_TIMEOUT = 300; +/** erase timeout ms */ +const uint16_t SD_ERASE_TIMEOUT = 10000; +/** init timeout ms */ +const uint16_t SD_INIT_TIMEOUT = 2000; +/** read timeout ms */ +const uint16_t SD_READ_TIMEOUT = 300; +/** write time out ms */ +const uint16_t SD_WRITE_TIMEOUT = 600; +//------------------------------------------------------------------------------ +// SD card commands +/** GO_IDLE_STATE - init card in spi mode if CS low */ +const uint8_t CMD0 = 0X00; +/** ALL_SEND_CID - Asks any card to send the CID. */ +const uint8_t CMD2 = 0X02; +/** SEND_RELATIVE_ADDR - Ask the card to publish a new RCA. */ +const uint8_t CMD3 = 0X03; +/** SWITCH_FUNC - Switch Function Command */ +const uint8_t CMD6 = 0X06; +/** SELECT/DESELECT_CARD - toggles between the stand-by and transfer states. */ +const uint8_t CMD7 = 0X07; +/** SEND_IF_COND - verify SD Memory Card interface operating condition.*/ +const uint8_t CMD8 = 0X08; +/** SEND_CSD - read the Card Specific Data (CSD register) */ +const uint8_t CMD9 = 0X09; +/** SEND_CID - read the card identification information (CID register) */ +const uint8_t CMD10 = 0X0A; +/** VOLTAGE_SWITCH -Switch to 1.8V bus signaling level. */ +const uint8_t CMD11 = 0X0B; +/** STOP_TRANSMISSION - end multiple sector read sequence */ +const uint8_t CMD12 = 0X0C; +/** SEND_STATUS - read the card status register */ +const uint8_t CMD13 = 0X0D; +/** READ_SINGLE_SECTOR - read a single data sector from the card */ +const uint8_t CMD17 = 0X11; +/** READ_MULTIPLE_SECTOR - read multiple data sectors from the card */ +const uint8_t CMD18 = 0X12; +/** WRITE_SECTOR - write a single data sector to the card */ +const uint8_t CMD24 = 0X18; +/** WRITE_MULTIPLE_SECTOR - write sectors of data until a STOP_TRANSMISSION */ +const uint8_t CMD25 = 0X19; +/** ERASE_WR_BLK_START - sets the address of the first sector to be erased */ +const uint8_t CMD32 = 0X20; +/** ERASE_WR_BLK_END - sets the address of the last sector of the continuous + range to be erased*/ +const uint8_t CMD33 = 0X21; +/** ERASE - erase all previously selected sectors */ +const uint8_t CMD38 = 0X26; +/** APP_CMD - escape for application specific command */ +const uint8_t CMD55 = 0X37; +/** READ_OCR - read the OCR register of a card */ +const uint8_t CMD58 = 0X3A; +/** CRC_ON_OFF - enable or disable CRC checking */ +const uint8_t CMD59 = 0X3B; +/** SET_BUS_WIDTH - Defines the data bus width for data transfer. */ +const uint8_t ACMD6 = 0X06; +/** SD_STATUS - Send the SD Status. */ +const uint8_t ACMD13 = 0X0D; +/** SET_WR_BLK_ERASE_COUNT - Set the number of write sectors to be + pre-erased before writing */ +const uint8_t ACMD23 = 0X17; +/** SD_SEND_OP_COMD - Sends host capacity support information and + activates the card's initialization process */ +const uint8_t ACMD41 = 0X29; +//============================================================================== +// CARD_STATUS +/** The command's argument was out of the allowed range for this card. */ +const uint32_t CARD_STATUS_OUT_OF_RANGE = 1UL << 31; +/** A misaligned address which did not match the sector length. */ +const uint32_t CARD_STATUS_ADDRESS_ERROR = 1UL << 30; +/** The transferred sector length is not allowed for this card. */ +const uint32_t CARD_STATUS_SECTOR_LEN_ERROR = 1UL << 29; +/** An error in the sequence of erase commands occurred. */ +const uint32_t CARD_STATUS_ERASE_SEQ_ERROR = 1UL <<28; +/** An invalid selection of write-sectors for erase occurred. */ +const uint32_t CARD_STATUS_ERASE_PARAM = 1UL << 27; +/** Set when the host attempts to write to a protected sector. */ +const uint32_t CARD_STATUS_WP_VIOLATION = 1UL << 26; +/** When set, signals that the card is locked by the host. */ +const uint32_t CARD_STATUS_CARD_IS_LOCKED = 1UL << 25; +/** Set when a sequence or password error has been detected. */ +const uint32_t CARD_STATUS_LOCK_UNLOCK_FAILED = 1UL << 24; +/** The CRC check of the previous command failed. */ +const uint32_t CARD_STATUS_COM_CRC_ERROR = 1UL << 23; +/** Command not legal for the card state. */ +const uint32_t CARD_STATUS_ILLEGAL_COMMAND = 1UL << 22; +/** Card internal ECC was applied but failed to correct the data. */ +const uint32_t CARD_STATUS_CARD_ECC_FAILED = 1UL << 21; +/** Internal card controller error */ +const uint32_t CARD_STATUS_CC_ERROR = 1UL << 20; +/** A general or an unknown error occurred during the operation. */ +const uint32_t CARD_STATUS_ERROR = 1UL << 19; +// bits 19, 18, and 17 reserved. +/** Permanent WP set or attempt to change read only values of CSD. */ +const uint32_t CARD_STATUS_CSD_OVERWRITE = 1UL <<16; +/** partial address space was erased due to write protect. */ +const uint32_t CARD_STATUS_WP_ERASE_SKIP = 1UL << 15; +/** The command has been executed without using the internal ECC. */ +const uint32_t CARD_STATUS_CARD_ECC_DISABLED = 1UL << 14; +/** out of erase sequence command was received. */ +const uint32_t CARD_STATUS_ERASE_RESET = 1UL << 13; +/** The state of the card when receiving the command. + * 0 = idle + * 1 = ready + * 2 = ident + * 3 = stby + * 4 = tran + * 5 = data + * 6 = rcv + * 7 = prg + * 8 = dis + * 9-14 = reserved + * 15 = reserved for I/O mode + */ +const uint32_t CARD_STATUS_CURRENT_STATE = 0XF << 9; +/** Shift for current state. */ +const uint32_t CARD_STATUS_CURRENT_STATE_SHIFT = 9; +/** Corresponds to buffer empty signaling on the bus. */ +const uint32_t CARD_STATUS_READY_FOR_DATA = 1UL << 8; +// bit 7 reserved. +/** Extension Functions may set this bit to get host to deal with events. */ +const uint32_t CARD_STATUS_FX_EVENT = 1UL << 6; +/** The card will expect ACMD, or the command has been interpreted as ACMD */ +const uint32_t CARD_STATUS_APP_CMD = 1UL << 5; +// bit 4 reserved. +/** Error in the sequence of the authentication process. */ +const uint32_t CARD_STATUS_AKE_SEQ_ERROR = 1UL << 3; +// bits 2,1, and 0 reserved for manufacturer test mode. +//============================================================================== +/** status for card in the ready state */ +const uint8_t R1_READY_STATE = 0X00; +/** status for card in the idle state */ +const uint8_t R1_IDLE_STATE = 0X01; +/** status bit for illegal command */ +const uint8_t R1_ILLEGAL_COMMAND = 0X04; +/** start data token for read or write single sector*/ +const uint8_t DATA_START_SECTOR = 0XFE; +/** stop token for write multiple sectors*/ +const uint8_t STOP_TRAN_TOKEN = 0XFD; +/** start data token for write multiple sectors*/ +const uint8_t WRITE_MULTIPLE_TOKEN = 0XFC; +/** mask for data response tokens after a write sector operation */ +const uint8_t DATA_RES_MASK = 0X1F; +/** write data accepted token */ +const uint8_t DATA_RES_ACCEPTED = 0X05; +//============================================================================== +/** + * \class CID + * \brief Card IDentification (CID) register. + */ +typedef struct CID { + // byte 0 + /** Manufacturer ID */ + unsigned char mid; + // byte 1-2 + /** OEM/Application ID */ + char oid[2]; + // byte 3-7 + /** Product name */ + char pnm[5]; + // byte 8 + /** Product revision least significant digit */ + unsigned char prv_m : 4; + /** Product revision most significant digit */ + unsigned char prv_n : 4; + // byte 9-12 + /** Product serial number */ + uint32_t psn; + // byte 13 + /** Manufacturing date year high digit */ + unsigned char mdt_year_high : 4; + /** not used */ + unsigned char reserved : 4; + // byte 14 + /** Manufacturing date month */ + unsigned char mdt_month : 4; + /** Manufacturing date year low digit */ + unsigned char mdt_year_low : 4; + // byte 15 + /** not used always 1 */ + unsigned char always1 : 1; + /** CRC7 checksum */ + unsigned char crc : 7; +} __attribute__((packed)) cid_t; + +//============================================================================== +#ifndef DOXYGEN_SHOULD_SKIP_THIS +/** + * \class CSDV1 + * \brief CSD register for version 1.00 cards . + */ +typedef struct CSDV1 { + // byte 0 + unsigned char reserved1 : 6; + unsigned char csd_ver : 2; + // byte 1 + unsigned char taac; + // byte 2 + unsigned char nsac; + // byte 3 + unsigned char tran_speed; + // byte 4 + unsigned char ccc_high; + // byte 5 + unsigned char read_bl_len : 4; + unsigned char ccc_low : 4; + // byte 6 + unsigned char c_size_high : 2; + unsigned char reserved2 : 2; + unsigned char dsr_imp : 1; + unsigned char read_blk_misalign : 1; + unsigned char write_blk_misalign : 1; + unsigned char read_bl_partial : 1; + // byte 7 + unsigned char c_size_mid; + // byte 8 + unsigned char vdd_r_curr_max : 3; + unsigned char vdd_r_curr_min : 3; + unsigned char c_size_low : 2; + // byte 9 + unsigned char c_size_mult_high : 2; + unsigned char vdd_w_cur_max : 3; + unsigned char vdd_w_curr_min : 3; + // byte 10 + unsigned char sector_size_high : 6; + unsigned char erase_blk_en : 1; + unsigned char c_size_mult_low : 1; + // byte 11 + unsigned char wp_grp_size : 7; + unsigned char sector_size_low : 1; + // byte 12 + unsigned char write_bl_len_high : 2; + unsigned char r2w_factor : 3; + unsigned char reserved3 : 2; + unsigned char wp_grp_enable : 1; + // byte 13 + unsigned char reserved4 : 5; + unsigned char write_partial : 1; + unsigned char write_bl_len_low : 2; + // byte 14 + unsigned char reserved5: 2; + unsigned char file_format : 2; + unsigned char tmp_write_protect : 1; + unsigned char perm_write_protect : 1; + unsigned char copy : 1; + /** Indicates the file format on the card */ + unsigned char file_format_grp : 1; + // byte 15 + unsigned char always1 : 1; + unsigned char crc : 7; +} __attribute__((packed)) csd1_t; +//============================================================================== +/** + * \class CSDV2 + * \brief CSD register for version 2.00 cards. + */ +typedef struct CSDV2 { + // byte 0 + unsigned char reserved1 : 6; + unsigned char csd_ver : 2; + // byte 1 + /** fixed to 0X0E */ + unsigned char taac; + // byte 2 + /** fixed to 0 */ + unsigned char nsac; + // byte 3 + unsigned char tran_speed; + // byte 4 + unsigned char ccc_high; + // byte 5 + /** This field is fixed to 9h, which indicates READ_BL_LEN=512 Byte */ + unsigned char read_bl_len : 4; + unsigned char ccc_low : 4; + // byte 6 + /** not used */ + unsigned char reserved2 : 4; + unsigned char dsr_imp : 1; + /** fixed to 0 */ + unsigned char read_blk_misalign : 1; + /** fixed to 0 */ + unsigned char write_blk_misalign : 1; + /** fixed to 0 - no partial read */ + unsigned char read_bl_partial : 1; + // byte 7 + /** high part of card size */ + unsigned char c_size_high : 6; + /** not used */ + unsigned char reserved3 : 2; + // byte 8 + /** middle part of card size */ + unsigned char c_size_mid; + // byte 9 + /** low part of card size */ + unsigned char c_size_low; + // byte 10 + /** sector size is fixed at 64 KB */ + unsigned char sector_size_high : 6; + /** fixed to 1 - erase single is supported */ + unsigned char erase_blk_en : 1; + /** not used */ + unsigned char reserved4 : 1; + // byte 11 + unsigned char wp_grp_size : 7; + /** sector size is fixed at 64 KB */ + unsigned char sector_size_low : 1; + // byte 12 + /** write_bl_len fixed for 512 byte sectors */ + unsigned char write_bl_len_high : 2; + /** fixed value of 2 */ + unsigned char r2w_factor : 3; + /** not used */ + unsigned char reserved5 : 2; + /** fixed value of 0 - no write protect groups */ + unsigned char wp_grp_enable : 1; + // byte 13 + unsigned char reserved6 : 5; + /** always zero - no partial sector read*/ + unsigned char write_partial : 1; + /** write_bl_len fixed for 512 byte sectors */ + unsigned char write_bl_len_low : 2; + // byte 14 + unsigned char reserved7: 2; + /** Do not use always 0 */ + unsigned char file_format : 2; + unsigned char tmp_write_protect : 1; + unsigned char perm_write_protect : 1; + unsigned char copy : 1; + /** Do not use always 0 */ + unsigned char file_format_grp : 1; + // byte 15 + /** not used always 1 */ + unsigned char always1 : 1; + /** checksum */ + unsigned char crc : 7; +} __attribute__((packed)) csd2_t; +//============================================================================== +/** + * \class csd_t + * \brief Union of old and new style CSD register. + */ +union csd_t { + csd1_t v1; + csd2_t v2; +}; +//----------------------------------------------------------------------------- +inline uint32_t sdCardCapacity(csd_t* csd) { + if (csd->v1.csd_ver == 0) { + uint8_t read_bl_len = csd->v1.read_bl_len; + uint16_t c_size = (csd->v1.c_size_high << 10) + | (csd->v1.c_size_mid << 2) | csd->v1.c_size_low; + uint8_t c_size_mult = (csd->v1.c_size_mult_high << 1) + | csd->v1.c_size_mult_low; + return (uint32_t)(c_size + 1) << (c_size_mult + read_bl_len - 7); + } else if (csd->v2.csd_ver == 1) { + return (((uint32_t)csd->v2.c_size_high << 16) + + ((uint16_t)csd->v2.c_size_mid << 8) + csd->v2.c_size_low + 1) << 10; + } else { + return 0; + } +} +//----------------------------------------------------------------------------- +// fields are big endian +typedef struct SdStatus { + uint8_t busWidthSecureMode; + uint8_t reserved1; + uint8_t sdCardType[2]; + uint8_t sizeOfProtectedArea[4]; + uint8_t speedClass; + uint8_t performanceMove; + uint8_t auSize; + uint8_t eraseSize[2]; + uint8_t eraseTimeoutOffset; + uint8_t uhsSpeedAuSize; + uint8_t videoSpeed; + uint8_t vscAuSize[2]; + uint8_t susAddr[3]; + uint8_t reserved2[3]; + uint8_t reservedManufacturer[40]; +} SdStatus_t; +#endif // DOXYGEN_SHOULD_SKIP_THIS +#endif // SdCardInfo_h diff --git a/Firmware_V3/lib/SdFat/src/SdCard/SdCardInterface.h b/Firmware_V3/lib/SdFat/src/SdCard/SdCardInterface.h new file mode 100644 index 0000000..17562ba --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SdCard/SdCardInterface.h @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef SdCardInterface_h +#define SdCardInterface_h +#include "../common/BlockDeviceInterface.h" +#include "SdCardInfo.h" +/** + * \class SdCardInterface + * \brief Abstract interface for an SD card. + */ +class SdCardInterface : public BlockDeviceInterface { + public: + /** Erase a range of sectors. + * + * \param[in] firstSector The address of the first sector in the range. + * \param[in] lastSector The address of the last sector in the range. + * + * \return true for success or false for failure. + */ + virtual bool erase(uint32_t firstSector, uint32_t lastSector) = 0; + /** \return error code. */ + virtual uint8_t errorCode() const = 0; + /** \return error data. */ + virtual uint32_t errorData() const = 0; + /** \return true if card is busy. */ + virtual bool isBusy() = 0; + /** + * Read a card's CID register. + * + * \param[out] cid pointer to area for returned data. + * + * \return true for success or false for failure. + */ + virtual bool readCID(cid_t* cid) = 0; + /** + * Read a card's CSD register. + * + * \param[out] csd pointer to area for returned data. + * + * \return true for success or false for failure. + */ + virtual bool readCSD(csd_t* csd) = 0; + /** Read OCR register. + * + * \param[out] ocr Value of OCR register. + * \return true for success or false for failure. + */ + virtual bool readOCR(uint32_t* ocr) = 0; + /** + * Determine the size of an SD flash memory card. + * + * \return The number of 512 byte data sectors in the card + * or zero if an error occurs. + */ + virtual uint32_t sectorCount() = 0; + /** \return card status. */ + virtual uint32_t status() {return 0XFFFFFFFF;} + /** Return the card type: SD V1, SD V2 or SDHC/SDXC + * \return 0 - SD V1, 1 - SD V2, or 3 - SDHC/SDXC. + */ + virtual uint8_t type() const = 0; + /** Write one data sector in a multiple sector write sequence. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + + virtual bool writeData(const uint8_t* src) = 0; + /** Start a write multiple sectors sequence. + * + * \param[in] sector Address of first sector in sequence. + * + * \return true for success or false for failure. + */ + virtual bool writeStart(uint32_t sector) = 0; + /** End a write multiple sectors sequence. + * \return true for success or false for failure. + */ + virtual bool writeStop() = 0; +}; +#endif // SdCardInterface_h diff --git a/Firmware_V3/lib/SdFat/src/SdCard/SdSpiCard.cpp b/Firmware_V3/lib/SdFat/src/SdCard/SdSpiCard.cpp new file mode 100644 index 0000000..dcae8f7 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SdCard/SdSpiCard.cpp @@ -0,0 +1,869 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "SdSpiCard.h" +//============================================================================== +// Debug aids +#define DBG_PROFILE_STATS 0 +#if DBG_PROFILE_STATS + +#define DBG_TAG_LIST\ + DBG_TAG(DBG_CMD0_TIME, "CMD0 time")\ + DBG_TAG(DBG_ACMD41_TIME, "ACMD41 time")\ + DBG_TAG(DBG_CMD_BUSY, "cmd busy")\ + DBG_TAG(DBG_ERASE_BUSY, "erase busy")\ + DBG_TAG(DBG_WAIT_READ, "wait read")\ + DBG_TAG(DBG_WRITE_FLASH, "write flash")\ + DBG_TAG(DBG_WRITE_BUSY, "write busy")\ + DBG_TAG(DBG_WRITE_STOP, "write stop")\ + DBG_TAG(DBG_ACMD41_COUNT, "ACMD41 count")\ + DBG_TAG(DBG_CMD0_COUNT, "CMD0 count") + +#define DBG_TIME_DIM DBG_ACMD41_COUNT + +enum DbgTag { + #define DBG_TAG(tag, str) tag, + DBG_TAG_LIST + DBG_COUNT_DIM + #undef DBG_TAG +}; + +static uint32_t dbgCount[DBG_COUNT_DIM]; +static uint32_t dbgBgnTime[DBG_TIME_DIM]; +static uint32_t dbgMaxTime[DBG_TIME_DIM]; +static uint32_t dbgMinTime[DBG_TIME_DIM]; +static uint32_t dbgTotalTime[DBG_TIME_DIM]; +//------------------------------------------------------------------------------ +static void dbgBeginTime(DbgTag tag) { + dbgBgnTime[tag] = micros(); +} +//------------------------------------------------------------------------------ +static void dbgClearStats() { + for (int i = 0; i < DBG_COUNT_DIM; i++) { + dbgCount[i] = 0; + if (i < DBG_TIME_DIM) { + dbgMaxTime[i] = 0; + dbgMinTime[i] = 9999999; + dbgTotalTime[i] = 0; + } + } +} +//------------------------------------------------------------------------------ +static void dbgEndTime(DbgTag tag) { + uint32_t m = micros() - dbgBgnTime[tag]; + dbgTotalTime[tag] += m; + if (m > dbgMaxTime[tag]) { + dbgMaxTime[tag] = m; + } + if (m < dbgMinTime[tag]) { + dbgMinTime[tag] = m; + } + dbgCount[tag]++; +} +//------------------------------------------------------------------------------ +static void dbgEventCount(DbgTag tag) { + dbgCount[tag]++; +} +//------------------------------------------------------------------------------ +static void dbgPrintTagText(uint8_t tag) { + #define DBG_TAG(e, m) case e: Serial.print(F(m)); break; + switch (tag) { + DBG_TAG_LIST + } + #undef DBG_TAG +} +//------------------------------------------------------------------------------ +static void dbgPrintStats() { + Serial.println(); + Serial.println(F("=======================")); + Serial.println(F("item,event,min,max,avg")); + Serial.println(F("tag,count,usec,usec,usec")); + for (int i = 0; i < DBG_COUNT_DIM; i++) { + if (dbgCount[i]) { + dbgPrintTagText(i); + Serial.print(','); + Serial.print(dbgCount[i]); + if (i < DBG_TIME_DIM) { + Serial.print(','); + Serial.print(dbgMinTime[i]); + Serial.print(','); + Serial.print(dbgMaxTime[i]); + Serial.print(','); + Serial.print(dbgTotalTime[i]/dbgCount[i]); + } + Serial.println(); + } + } + Serial.println(F("=======================")); + Serial.println(); +} +#undef DBG_TAG_LIST +#define DBG_BEGIN_TIME(tag) dbgBeginTime(tag) +#define DBG_END_TIME(tag) dbgEndTime(tag) +#define DBG_EVENT_COUNT(tag) dbgEventCount(tag) +#else // DBG_PROFILE_STATS +#define DBG_BEGIN_TIME(tag) +#define DBG_END_TIME(tag) +#define DBG_EVENT_COUNT(tag) +static void dbgClearStats() {} +static void dbgPrintStats() {} +#endif // DBG_PROFILE_STATS +//============================================================================== +#if USE_SD_CRC +// CRC functions +//------------------------------------------------------------------------------ +static uint8_t CRC7(const uint8_t* data, uint8_t n) { + uint8_t crc = 0; + for (uint8_t i = 0; i < n; i++) { + uint8_t d = data[i]; + for (uint8_t j = 0; j < 8; j++) { + crc <<= 1; + if ((d & 0x80) ^ (crc & 0x80)) { + crc ^= 0x09; + } + d <<= 1; + } + } + return (crc << 1) | 1; +} +//------------------------------------------------------------------------------ +#if USE_SD_CRC == 1 +// Shift based CRC-CCITT +// uses the x^16,x^12,x^5,x^1 polynomial. +static uint16_t CRC_CCITT(const uint8_t* data, size_t n) { + uint16_t crc = 0; + for (size_t i = 0; i < n; i++) { + crc = (uint8_t)(crc >> 8) | (crc << 8); + crc ^= data[i]; + crc ^= (uint8_t)(crc & 0xff) >> 4; + crc ^= crc << 12; + crc ^= (crc & 0xff) << 5; + } + return crc; +} +#elif USE_SD_CRC > 1 // CRC_CCITT +//------------------------------------------------------------------------------ +// Table based CRC-CCITT +// uses the x^16,x^12,x^5,x^1 polynomial. +#ifdef __AVR__ +static const uint16_t crctab[] PROGMEM = { +#else // __AVR__ +static const uint16_t crctab[] = { +#endif // __AVR__ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, + 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, + 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, + 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, + 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, + 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, + 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, + 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, + 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, + 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, + 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, + 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, + 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, + 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 +}; +static uint16_t CRC_CCITT(const uint8_t* data, size_t n) { + uint16_t crc = 0; + for (size_t i = 0; i < n; i++) { +#ifdef __AVR__ + crc = pgm_read_word(&crctab[(crc >> 8 ^ data[i]) & 0XFF]) ^ (crc << 8); +#else // __AVR__ + crc = crctab[(crc >> 8 ^ data[i]) & 0XFF] ^ (crc << 8); +#endif // __AVR__ + } + return crc; +} +#endif // CRC_CCITT +#endif // USE_SD_CRC +//============================================================================== +// SdSpiCard member functions +//------------------------------------------------------------------------------ +bool SdSpiCard::begin(SdSpiConfig spiConfig) { + SdMillis_t t0 = SysCall::curTimeMS(); + m_spiActive = false; + m_errorCode = SD_CARD_ERROR_NONE; + m_type = 0; + m_csPin = spiConfig.csPin; +#if SPI_DRIVER_SELECT >= 2 + m_spiDriverPtr = spiConfig.spiPort; + if (!m_spiDriverPtr) { + error(SD_CARD_ERROR_INVALID_CARD_CONFIG); + goto fail; + } +#endif // SPI_DRIVER_SELECT + sdCsInit(m_csPin); + spiUnselect(); + spiSetSckSpeed(1000UL*SD_MAX_INIT_RATE_KHZ); + spiBegin(spiConfig); + uint32_t arg; +#if ENABLE_DEDICATED_SPI + m_curState = IDLE_STATE; + m_sharedSpi = spiOptionShared(spiConfig.options); +#else // ENABLE_DEDICATED_SPI + // m_sharedSpi is a static const bool in this case. + static_assert(m_sharedSpi == true, "m_sharedSpi bug"); +#endif // ENABLE_DEDICATED_SPI + spiStart(); + + // must supply min of 74 clock cycles with CS high. + spiUnselect(); + for (uint8_t i = 0; i < 10; i++) { + spiSend(0XFF); + } + spiSelect(); + DBG_BEGIN_TIME(DBG_CMD0_TIME); + // command to go idle in SPI mode + for (uint8_t i = 1;; i++) { + DBG_EVENT_COUNT(DBG_CMD0_COUNT); + if (cardCommand(CMD0, 0) == R1_IDLE_STATE) { + break; + } + if (i == SD_CMD0_RETRY) { + error(SD_CARD_ERROR_CMD0); + goto fail; + } + // stop multi-block write + spiSend(STOP_TRAN_TOKEN); + // finish block transfer + for (int i = 0; i < 520; i++) { + spiReceive(); + } + } + DBG_END_TIME(DBG_CMD0_TIME); +#if USE_SD_CRC + if (cardCommand(CMD59, 1) != R1_IDLE_STATE) { + error(SD_CARD_ERROR_CMD59); + goto fail; + } +#endif // USE_SD_CRC + + // check SD version + if (!(cardCommand(CMD8, 0x1AA) & R1_ILLEGAL_COMMAND)) { + type(SD_CARD_TYPE_SD2); + for (uint8_t i = 0; i < 4; i++) { + m_status = spiReceive(); + } + if (m_status != 0XAA) { + error(SD_CARD_ERROR_CMD8); + goto fail; + } + } else { + type(SD_CARD_TYPE_SD1); + } + // initialize card and send host supports SDHC if SD2 + arg = type() == SD_CARD_TYPE_SD2 ? 0X40000000 : 0; + DBG_BEGIN_TIME(DBG_ACMD41_TIME); + while (cardAcmd(ACMD41, arg) != R1_READY_STATE) { + DBG_EVENT_COUNT(DBG_ACMD41_COUNT); + // check for timeout + if (isTimedOut(t0, SD_INIT_TIMEOUT)) { + error(SD_CARD_ERROR_ACMD41); + goto fail; + } + } + DBG_END_TIME(DBG_ACMD41_TIME); + + // if SD2 read OCR register to check for SDHC card + if (type() == SD_CARD_TYPE_SD2) { + if (cardCommand(CMD58, 0)) { + error(SD_CARD_ERROR_CMD58); + goto fail; + } + if ((spiReceive() & 0XC0) == 0XC0) { + type(SD_CARD_TYPE_SDHC); + } + // Discard rest of ocr - contains allowed voltage range. + for (uint8_t i = 0; i < 3; i++) { + spiReceive(); + } + } + spiStop(); + spiSetSckSpeed(spiConfig.maxSck); + return true; + + fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +// send command and return error code. Return zero for OK +uint8_t SdSpiCard::cardCommand(uint8_t cmd, uint32_t arg) { +#if ENABLE_DEDICATED_SPI + if (m_curState != IDLE_STATE && !syncDevice()) { + return 0XFF; + } +#endif // ENABLE_DEDICATED_SPI + // select card + if (!m_spiActive) { + spiStart(); + } + // wait if busy unless CMD0 + if (cmd != CMD0) { + DBG_BEGIN_TIME(DBG_CMD_BUSY); + waitNotBusy(SD_CMD_TIMEOUT); + DBG_END_TIME(DBG_CMD_BUSY); + } + +#if USE_SD_CRC + // form message + uint8_t buf[6]; + buf[0] = (uint8_t)0x40U | cmd; + buf[1] = (uint8_t)(arg >> 24U); + buf[2] = (uint8_t)(arg >> 16U); + buf[3] = (uint8_t)(arg >> 8U); + buf[4] = (uint8_t)arg; + + // add CRC + buf[5] = CRC7(buf, 5); + + // send message + spiSend(buf, 6); +#else // USE_SD_CRC + // send command + spiSend(cmd | 0x40); + + // send argument + uint8_t* pa = reinterpret_cast(&arg); + for (int8_t i = 3; i >= 0; i--) { + spiSend(pa[i]); + } + + // send CRC - correct for CMD0 with arg zero or CMD8 with arg 0X1AA + spiSend(cmd == CMD0 ? 0X95 : 0X87); +#endif // USE_SD_CRC + + // discard first fill byte to avoid MISO pull-up problem. + spiReceive(); + + // there are 1-8 fill bytes before response. fill bytes should be 0XFF. + for (uint8_t i = 0; ((m_status = spiReceive()) & 0X80) && i < 10; i++) { + } + return m_status; +} +//------------------------------------------------------------------------------ +void SdSpiCard::dbgClearStats() {::dbgClearStats();} +//------------------------------------------------------------------------------ +void SdSpiCard::dbgPrintStats() {::dbgPrintStats();} +//------------------------------------------------------------------------------ +bool SdSpiCard::erase(uint32_t firstSector, uint32_t lastSector) { + csd_t csd; + if (!readCSD(&csd)) { + goto fail; + } + // check for single sector erase + if (!csd.v1.erase_blk_en) { + // erase size mask + uint8_t m = (csd.v1.sector_size_high << 1) | csd.v1.sector_size_low; + if ((firstSector & m) != 0 || ((lastSector + 1) & m) != 0) { + // error card can't erase specified area + error(SD_CARD_ERROR_ERASE_SINGLE_SECTOR); + goto fail; + } + } + if (m_type != SD_CARD_TYPE_SDHC) { + firstSector <<= 9; + lastSector <<= 9; + } + if (cardCommand(CMD32, firstSector) + || cardCommand(CMD33, lastSector) + || cardCommand(CMD38, 0)) { + error(SD_CARD_ERROR_ERASE); + goto fail; + } + DBG_BEGIN_TIME(DBG_ERASE_BUSY); + if (!waitNotBusy(SD_ERASE_TIMEOUT)) { + error(SD_CARD_ERROR_ERASE_TIMEOUT); + goto fail; + } + DBG_END_TIME(DBG_ERASE_BUSY); + spiStop(); + return true; + + fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::eraseSingleSectorEnable() { + csd_t csd; + return readCSD(&csd) ? csd.v1.erase_blk_en : false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::isBusy() { +#if ENABLE_DEDICATED_SPI + if (m_curState == READ_STATE) { + return false; + } +#endif // ENABLE_DEDICATED_SPI + bool rtn = true; + bool spiActive = m_spiActive; + if (!spiActive) { + spiStart(); + } + for (uint8_t i = 0; i < 8; i++) { + if (0XFF == spiReceive()) { + rtn = false; + break; + } + } + if (!spiActive) { + spiStop(); + } + return rtn; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::isTimedOut(SdMillis_t startMS, SdMillis_t timeoutMS) { +#if WDT_YIELD_TIME_MILLIS + static SdMillis_t last; + if ((SysCall::curTimeMS() - last) > WDT_YIELD_TIME_MILLIS) { + SysCall::yield(); + last = SysCall::curTimeMS(); + } +#endif // WDT_YIELD_TIME_MILLIS + return (SysCall::curTimeMS() - startMS) > timeoutMS; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readData(uint8_t* dst) { + return readData(dst, 512); +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readData(uint8_t* dst, size_t count) { +#if USE_SD_CRC + uint16_t crc; +#endif // USE_SD_CRC + + DBG_BEGIN_TIME(DBG_WAIT_READ); + // wait for start sector token + SdMillis_t t0 = SysCall::curTimeMS(); + while ((m_status = spiReceive()) == 0XFF) { + if (isTimedOut(t0, SD_READ_TIMEOUT)) { + error(SD_CARD_ERROR_READ_TIMEOUT); + goto fail; + } + } + DBG_END_TIME(DBG_WAIT_READ); + if (m_status != DATA_START_SECTOR) { + error(SD_CARD_ERROR_READ_TOKEN); + goto fail; + } + // transfer data + if ((m_status = spiReceive(dst, count))) { + error(SD_CARD_ERROR_DMA); + goto fail; + } + +#if USE_SD_CRC + // get crc + crc = (spiReceive() << 8) | spiReceive(); + if (crc != CRC_CCITT(dst, count)) { + error(SD_CARD_ERROR_READ_CRC); + goto fail; + } +#else // USE_SD_CRC + // discard crc + spiReceive(); + spiReceive(); +#endif // USE_SD_CRC + return true; + + fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readOCR(uint32_t* ocr) { + uint8_t* p = reinterpret_cast(ocr); + if (cardCommand(CMD58, 0)) { + error(SD_CARD_ERROR_CMD58); + goto fail; + } + for (uint8_t i = 0; i < 4; i++) { + p[3 - i] = spiReceive(); + } + spiStop(); + return true; + + fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +/** read CID or CSR register */ +bool SdSpiCard::readRegister(uint8_t cmd, void* buf) { + uint8_t* dst = reinterpret_cast(buf); + if (cardCommand(cmd, 0)) { + error(SD_CARD_ERROR_READ_REG); + goto fail; + } + if (!readData(dst, 16)) { + goto fail; + } + spiStop(); + return true; + + fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readSingle(uint32_t sector, uint8_t* dst) { + // use address if not SDHC card + if (type() != SD_CARD_TYPE_SDHC) { + sector <<= 9; + } + if (cardCommand(CMD17, sector)) { + error(SD_CARD_ERROR_CMD17); + goto fail; + } + if (!readData(dst, 512)) { + goto fail; + } + spiStop(); + return true; + + fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readStart(uint32_t sector) { + if (type() != SD_CARD_TYPE_SDHC) { + sector <<= 9; + } + if (cardCommand(CMD18, sector)) { + error(SD_CARD_ERROR_CMD18); + goto fail; + } +// spiStop(); + return true; + + fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readStatus(uint8_t* status) { + // retrun is R2 so read extra status byte. + if (cardAcmd(ACMD13, 0) || spiReceive()) { + error(SD_CARD_ERROR_ACMD13); + goto fail; + } + if (!readData(status, 64)) { + goto fail; + } + spiStop(); + return true; + + fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readSectors(uint32_t sector, uint8_t* dst, size_t ns) { +#if ENABLE_DEDICATED_SPI + if (m_curState != READ_STATE || sector != m_curSector) { + if (!readStart(sector)) { + goto fail; + } + m_curSector = sector; + m_curState = READ_STATE; + } + for (size_t i = 0; i < ns; i++, dst += 512) { + if (!readData(dst, 512)) { + goto fail; + } + } + m_curSector += ns; + return m_sharedSpi ? syncDevice() : true; +#else // ENABLE_DEDICATED_SPI + if (!readStart(sector)) { + goto fail; + } + for (size_t i = 0; i < ns; i++, dst += 512) { + if (!readData(dst, 512)) { + goto fail; + } + } + return readStop(); +#endif // ENABLE_DEDICATED_SPI + fail: + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::readStop() { + if (cardCommand(CMD12, 0)) { + error(SD_CARD_ERROR_CMD12); + goto fail; + } + spiStop(); + return true; + + fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +uint32_t SdSpiCard::sectorCount() { + csd_t csd; + return readCSD(&csd) ? sdCardCapacity(&csd) : 0; +} +//------------------------------------------------------------------------------ +void SdSpiCard::spiStart() { + if (!m_spiActive) { + spiActivate(); + spiSelect(); + m_spiActive = true; + } +} +//------------------------------------------------------------------------------ +void SdSpiCard::spiStop() { + if (m_spiActive) { + spiUnselect(); + spiSend(0XFF); + spiDeactivate(); + m_spiActive = false; + } +} +//------------------------------------------------------------------------------ +bool SdSpiCard::syncDevice() { +#if ENABLE_DEDICATED_SPI + // Insure no recursive loop with cardCommand(). + uint8_t state = m_curState; + m_curState = IDLE_STATE; + if (state == WRITE_STATE) { + return writeStop(); + } + if (state == READ_STATE) { + return readStop(); + } +#endif // ENABLE_DEDICATED_SPI + return true; +} +//------------------------------------------------------------------------------ +// wait for card to go not busy +bool SdSpiCard::waitNotBusy(SdMillis_t timeoutMS) { + SdMillis_t t0 = SysCall::curTimeMS(); +#if WDT_YIELD_TIME_MILLIS + // Call isTimedOut first to insure yield is called. + while (!isTimedOut(t0, timeoutMS)) { + if (spiReceive() == 0XFF) { + return true; + } + } + return false; +#else // WDT_YIELD_TIME_MILLIS + // Check not busy first since yield is not called in isTimedOut. + while (spiReceive() != 0XFF) { + if (isTimedOut(t0, timeoutMS)) { + return false; + } + } + return true; +#endif // WDT_YIELD_TIME_MILLIS +} +//------------------------------------------------------------------------------ +bool SdSpiCard::writeData(const uint8_t* src) { + // wait for previous write to finish + DBG_BEGIN_TIME(DBG_WRITE_BUSY); + if (!waitNotBusy(SD_WRITE_TIMEOUT)) { + error(SD_CARD_ERROR_WRITE_TIMEOUT); + goto fail; + } + DBG_END_TIME(DBG_WRITE_BUSY); + if (!writeData(WRITE_MULTIPLE_TOKEN, src)) { + goto fail; + } + return true; + + fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +// send one sector of data for write sector or write multiple sectors +bool SdSpiCard::writeData(uint8_t token, const uint8_t* src) { +#if USE_SD_CRC + uint16_t crc = CRC_CCITT(src, 512); +#else // USE_SD_CRC + uint16_t crc = 0XFFFF; +#endif // USE_SD_CRC + spiSend(token); + spiSend(src, 512); + spiSend(crc >> 8); + spiSend(crc & 0XFF); + + m_status = spiReceive(); + if ((m_status & DATA_RES_MASK) != DATA_RES_ACCEPTED) { + error(SD_CARD_ERROR_WRITE_DATA); + goto fail; + } + return true; + + fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::writeSingle(uint32_t sector, const uint8_t* src) { + // use address if not SDHC card + if (type() != SD_CARD_TYPE_SDHC) { + sector <<= 9; + } + if (cardCommand(CMD24, sector)) { + error(SD_CARD_ERROR_CMD24); + goto fail; + } + if (!writeData(DATA_START_SECTOR, src)) { + goto fail; + } + +#if CHECK_FLASH_PROGRAMMING + // wait for flash programming to complete + DBG_BEGIN_TIME(DBG_WRITE_FLASH); + if (!waitNotBusy(SD_WRITE_TIMEOUT)) { + error(SD_CARD_ERROR_WRITE_PROGRAMMING); + goto fail; + } + DBG_END_TIME(DBG_WRITE_FLASH); + // response is r2 so get and check two bytes for nonzero + if (cardCommand(CMD13, 0) || spiReceive()) { + error(SD_CARD_ERROR_CMD13); + goto fail; + } +#endif // CHECK_FLASH_PROGRAMMING + + spiStop(); + return true; + + fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::writeSectors(uint32_t sector, const uint8_t* src, size_t ns) { +#if ENABLE_DEDICATED_SPI + if (m_curState != WRITE_STATE || m_curSector != sector) { + if (!writeStart(sector)) { + goto fail; + } + m_curSector = sector; + m_curState = WRITE_STATE; + } + for (size_t i = 0; i < ns; i++, src += 512) { + if (!writeData(src)) { + goto fail; + } + } + m_curSector += ns; + return m_sharedSpi ? syncDevice() : true; +#else // ENABLE_DEDICATED_SPI + if (!writeStart(sector)) { + goto fail; + } + for (size_t i = 0; i < ns; i++, src += 512) { + if (!writeData(src)) { + goto fail; + } + } + return writeStop(); +#endif // ENABLE_DEDICATED_SPI + + fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::writeStart(uint32_t sector) { + // use address if not SDHC card + if (type() != SD_CARD_TYPE_SDHC) { + sector <<= 9; + } + if (cardCommand(CMD25, sector)) { + error(SD_CARD_ERROR_CMD25); + goto fail; + } + return true; + + fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::writeStart(uint32_t sector, uint32_t eraseCount) { + // send pre-erase count + if (cardAcmd(ACMD23, eraseCount)) { + error(SD_CARD_ERROR_ACMD23); + goto fail; + } + // use address if not SDHC card + if (type() != SD_CARD_TYPE_SDHC) { + sector <<= 9; + } + if (cardCommand(CMD25, sector)) { + error(SD_CARD_ERROR_CMD25); + goto fail; + } + return true; + + fail: + spiStop(); + return false; +} +//------------------------------------------------------------------------------ +bool SdSpiCard::writeStop() { + DBG_BEGIN_TIME(DBG_WRITE_STOP); + if (!waitNotBusy(SD_WRITE_TIMEOUT)) { + goto fail; + } + DBG_END_TIME(DBG_WRITE_STOP); + spiSend(STOP_TRAN_TOKEN); + spiStop(); + return true; + + fail: + error(SD_CARD_ERROR_STOP_TRAN); + spiStop(); + return false; +} diff --git a/Firmware_V3/lib/SdFat/src/SdCard/SdSpiCard.h b/Firmware_V3/lib/SdFat/src/SdCard/SdSpiCard.h new file mode 100644 index 0000000..a1ce6ce --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SdCard/SdSpiCard.h @@ -0,0 +1,368 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief SdSpiCard class for V2 SD/SDHC cards + */ +#ifndef SdSpiCard_h +#define SdSpiCard_h +#include +#include "../common/SysCall.h" +#include "SdCardInfo.h" +#include "SdCardInterface.h" +#include "../SpiDriver/SdSpiDriver.h" +//============================================================================== +/** + * \class SdSpiCard + * \brief Raw access to SD and SDHC flash memory cards via SPI protocol. + */ +#if HAS_SDIO_CLASS +class SdSpiCard : public SdCardInterface { +#elif USE_BLOCK_DEVICE_INTERFACE +class SdSpiCard : public BlockDeviceInterface { +#else // HAS_SDIO_CLASS +class SdSpiCard { +#endif // HAS_SDIO_CLASS + public: + /** Construct an instance of SdSpiCard. */ + SdSpiCard() {} + /** Initialize the SD card. + * \param[in] spiConfig SPI card configuration. + * \return true for success or false for failure. + */ + bool begin(SdSpiConfig spiConfig); + /** Clear debug stats. */ + void dbgClearStats(); + /** Print debug stats. */ + void dbgPrintStats(); + /** + * Determine the size of an SD flash memory card. + * + * \return The number of 512 byte data sectors in the card + * or zero if an error occurs. + */ + uint32_t sectorCount(); +#ifndef DOXYGEN_SHOULD_SKIP_THIS + // Use sectorCount(). cardSize() will be removed in the future. + uint32_t cardSize() __attribute__ ((deprecated)) {return sectorCount();} +#endif // DOXYGEN_SHOULD_SKIP_THIS + /** Erase a range of sectors. + * + * \param[in] firstSector The address of the first sector in the range. + * \param[in] lastSector The address of the last sector in the range. + * + * \note This function requests the SD card to do a flash erase for a + * range of sectors. The data on the card after an erase operation is + * either 0 or 1, depends on the card vendor. The card must support + * single sector erase. + * + * \return true for success or false for failure. + */ + bool erase(uint32_t firstSector, uint32_t lastSector); + /** Determine if card supports single sector erase. + * + * \return true is returned if single sector erase is supported. + * false is returned if single sector erase is not supported. + */ + bool eraseSingleSectorEnable(); + /** + * Set SD error code. + * \param[in] code value for error code. + */ + void error(uint8_t code) { + m_errorCode = code; + } + /** + * \return code for the last error. See SdCardInfo.h for a list of error codes. + */ + uint8_t errorCode() const { + return m_errorCode; + } + /** \return error data for last error. */ + uint32_t errorData() const { + return m_status; + } + /** + * Check for busy. MISO low indicates the card is busy. + * + * \return true if busy else false. + */ + bool isBusy(); + + /** + * Read a 512 byte sector from an SD card. + * + * \param[in] sector Logical sector to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return true for success or false for failure. + */ + bool readSector(uint32_t sector, uint8_t* dst) { +#if ENABLE_DEDICATED_SPI + return readSectors(sector, dst, 1); +#else // ENABLE_DEDICATED_SPI + return readSingle(sector, dst); +#endif // ENABLE_DEDICATED_SPI + } + /** + * Read a 512 byte sector from an SD card. + * + * \param[in] sector Logical sector to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return true for success or false for failure. + */ + bool readSingle(uint32_t sector, uint8_t* dst); + /** + * Read multiple 512 byte sectors from an SD card. + * + * \param[in] sector Logical sector to be read. + * \param[in] ns Number of sectors to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return true for success or false for failure. + */ + bool readSectors(uint32_t sector, uint8_t* dst, size_t ns); + /** + * Read a card's CID register. The CID contains card identification + * information such as Manufacturer ID, Product name, Product serial + * number and Manufacturing date. + * + * \param[out] cid pointer to area for returned data. + * + * \return true for success or false for failure. + */ + bool readCID(cid_t* cid) { + return readRegister(CMD10, cid); + } + /** + * Read a card's CSD register. The CSD contains Card-Specific Data that + * provides information regarding access to the card's contents. + * + * \param[out] csd pointer to area for returned data. + * + * \return true for success or false for failure. + */ + bool readCSD(csd_t* csd) { + return readRegister(CMD9, csd); + } + /** Read one data sector in a multiple sector read sequence + * + * \param[out] dst Pointer to the location for the data to be read. + * + * \return true for success or false for failure. + */ + bool readData(uint8_t* dst); + /** Read OCR register. + * + * \param[out] ocr Value of OCR register. + * \return true for success or false for failure. + */ + bool readOCR(uint32_t* ocr); + /** Start a read multiple sector sequence. + * + * \param[in] sector Address of first sector in sequence. + * + * \note This function is used with readData() and readStop() for optimized + * multiple sector reads. SPI chipSelect must be low for the entire sequence. + * + * \return true for success or false for failure. + */ + bool readStart(uint32_t sector); + /** Return the 64 byte card status + * \param[out] status location for 64 status bytes. + * \return true for success or false for failure. + */ + bool readStatus(uint8_t* status); + /** End a read multiple sectors sequence. + * + * \return true for success or false for failure. + */ + bool readStop(); + /** \return success if sync successful. Not for user apps. */ + bool syncDevice(); + /** Return the card type: SD V1, SD V2 or SDHC/SDXC + * \return 0 - SD V1, 1 - SD V2, or 3 - SDHC/SDXC. + */ + uint8_t type() const { + return m_type; + } + /** + * Writes a 512 byte sector to an SD card. + * + * \param[in] sector Logical sector to be written. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + bool writeSector(uint32_t sector, const uint8_t* src) { + if (m_sharedSpi) { + return writeSingle(sector, src); + } else { + return writeSectors(sector, src, 1); + } + } + /** + * Writes a 512 byte sector to an SD card. + * + * \param[in] sector Logical sector to be written. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + bool writeSingle(uint32_t sector, const uint8_t* src); + /** + * Write multiple 512 byte sectors to an SD card. + * + * \param[in] sector Logical sector to be written. + * \param[in] ns Number of sectors to be written. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + bool writeSectors(uint32_t sector, const uint8_t* src, size_t ns); + /** Write one data sector in a multiple sector write sequence. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + bool writeData(const uint8_t* src); + /** Start a write multiple sectors sequence. + * + * \param[in] sector Address of first sector in sequence. + * + * \note This function is used with writeData() and writeStop() + * for optimized multiple sector writes. + * + * \return true for success or false for failure. + */ + bool writeStart(uint32_t sector); + + /** Start a write multiple sector sequence with pre-erase. + * + * \param[in] sector Address of first sector in sequence. + * \param[in] eraseCount The number of sectors to be pre-erased. + * + * \note This function is used with writeData() and writeStop() + * for optimized multiple sector writes. + * + * \return true for success or false for failure. + */ + bool writeStart(uint32_t sector, uint32_t eraseCount); + /** End a write multiple sectors sequence. + * + * \return true for success or false for failure. + */ + bool writeStop(); + /** Set CS low and activate the card. */ + void spiStart(); + /** Set CS high and deactivate the card. */ + void spiStop(); + + private: + // private functions + uint8_t cardAcmd(uint8_t cmd, uint32_t arg) { + cardCommand(CMD55, 0); + return cardCommand(cmd, arg); + } + uint8_t cardCommand(uint8_t cmd, uint32_t arg); + bool isTimedOut(SdMillis_t startMS, SdMillis_t timeoutMS); + bool readData(uint8_t* dst, size_t count); + bool readRegister(uint8_t cmd, void* buf); + void spiSelect() { + sdCsWrite(m_csPin, false); + } + void type(uint8_t value) { + m_type = value; + } + void spiUnselect() { + sdCsWrite(m_csPin, true); + } + bool waitNotBusy(SdMillis_t timeoutMS); + bool writeData(uint8_t token, const uint8_t* src); + +#if SPI_DRIVER_SELECT < 2 + void spiActivate() { + m_spiDriver.activate(); + } + void spiBegin(SdSpiConfig spiConfig) { + m_spiDriver.begin(spiConfig); + } + void spiDeactivate() { + m_spiDriver.deactivate(); + } + uint8_t spiReceive() { + return m_spiDriver.receive(); + } + uint8_t spiReceive(uint8_t* buf, size_t n) { + return m_spiDriver.receive(buf, n); + } + void spiSend(uint8_t data) { + m_spiDriver.send(data); + } + void spiSend(const uint8_t* buf, size_t n) { + m_spiDriver.send(buf, n); + } + void spiSetSckSpeed(uint32_t maxSck) { + m_spiDriver.setSckSpeed(maxSck); + } + SdSpiDriver m_spiDriver; +#else // SPI_DRIVER_SELECT < 2 + void spiActivate() { + m_spiDriverPtr->activate(); + } + void spiBegin(SdSpiConfig spiConfig) { + m_spiDriverPtr->begin(spiConfig); + } + void spiDeactivate() { + m_spiDriverPtr->deactivate(); + } + uint8_t spiReceive() { + return m_spiDriverPtr->receive(); + } + uint8_t spiReceive(uint8_t* buf, size_t n) { + return m_spiDriverPtr->receive(buf, n); + } + void spiSend(uint8_t data) { + m_spiDriverPtr->send(data); + } + void spiSend(const uint8_t* buf, size_t n) { + m_spiDriverPtr->send(buf, n); + } + void spiSetSckSpeed(uint32_t maxSck) { + m_spiDriverPtr->setSckSpeed(maxSck); + } + SdSpiDriver* m_spiDriverPtr; +#endif // SPI_DRIVER_SELECT < 2 +#if ENABLE_DEDICATED_SPI + static const uint8_t IDLE_STATE = 0; + static const uint8_t READ_STATE = 1; + static const uint8_t WRITE_STATE = 2; + uint32_t m_curSector; + uint8_t m_curState; + bool m_sharedSpi = true; +#else // ENABLE_DEDICATED_SPI + static const bool m_sharedSpi = true; +#endif // ENABLE_DEDICATED_SPI + SdCsPin_t m_csPin; + uint8_t m_errorCode = SD_CARD_ERROR_INIT_NOT_CALLED; + bool m_spiActive; + uint8_t m_status; + uint8_t m_type = 0; +}; +#endif // SdSpiCard_h diff --git a/Firmware_V3/lib/SdFat/src/SdCard/SdioCard.h b/Firmware_V3/lib/SdFat/src/SdCard/SdioCard.h new file mode 100644 index 0000000..447248d --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SdCard/SdioCard.h @@ -0,0 +1,255 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef SdioCard_h +#define SdioCard_h +#include "../common/SysCall.h" +#include "SdCardInterface.h" + +#define FIFO_SDIO 0 +#define DMA_SDIO 1 +/** + * \class SdioConfig + * \brief SDIO card configuration. + */ +class SdioConfig { + public: + SdioConfig() {} + /** + * SdioConfig constructor. + * \param[in] opt SDIO options. + */ + explicit SdioConfig(uint8_t opt) : m_options(opt) {} + /** \return SDIO card options. */ + uint8_t options() {return m_options;} + /** \return true if DMA_SDIO. */ + bool useDma() {return m_options & DMA_SDIO;} + private: + uint8_t m_options = FIFO_SDIO; +}; +//------------------------------------------------------------------------------ +/** + * \class SdioCard + * \brief Raw SDIO access to SD and SDHC flash memory cards. + */ +class SdioCard : public SdCardInterface { + public: + /** Initialize the SD card. + * \param[in] sdioConfig SDIO card configuration. + * \return true for success or false for failure. + */ + bool begin(SdioConfig sdioConfig); + /** Disable an SDIO card. + * \return false - not implemented. + */ + bool end() {return false;} + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + // Use sectorCount(). cardSize() will be removed in the future. + uint32_t cardSize() __attribute__ ((deprecated)) {return sectorCount();} +#endif // DOXYGEN_SHOULD_SKIP_THIS + /** Erase a range of sectors. + * + * \param[in] firstSector The address of the first sector in the range. + * \param[in] lastSector The address of the last sector in the range. + * + * \note This function requests the SD card to do a flash erase for a + * range of sectors. The data on the card after an erase operation is + * either 0 or 1, depends on the card vendor. The card must support + * single sector erase. + * + * \return true for success or false for failure. + */ + bool erase(uint32_t firstSector, uint32_t lastSector); + /** + * \return code for the last error. See SdCardInfo.h for a list of error codes. + */ + uint8_t errorCode() const; + /** \return error data for last error. */ + uint32_t errorData() const; + /** \return error line for last error. Tmp function for debug. */ + uint32_t errorLine() const; + /** + * Check for busy with CMD13. + * + * \return true if busy else false. + */ + bool isBusy(); + /** \return the SD clock frequency in kHz. */ + uint32_t kHzSdClk(); + /** + * Read a 512 byte sector from an SD card. + * + * \param[in] sector Logical sector to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return true for success or false for failure. + */ + bool readSector(uint32_t sector, uint8_t* dst); + /** + * Read multiple 512 byte sectors from an SD card. + * + * \param[in] sector Logical sector to be read. + * \param[in] ns Number of sectors to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return true for success or false for failure. + */ + bool readSectors(uint32_t sector, uint8_t* dst, size_t ns); + /** + * Read a card's CID register. The CID contains card identification + * information such as Manufacturer ID, Product name, Product serial + * number and Manufacturing date. + * + * \param[out] cid pointer to area for returned data. + * + * \return true for success or false for failure. + */ + bool readCID(cid_t* cid); + /** + * Read a card's CSD register. The CSD contains Card-Specific Data that + * provides information regarding access to the card's contents. + * + * \param[out] csd pointer to area for returned data. + * + * \return true for success or false for failure. + */ + bool readCSD(csd_t* csd); + /** Read one data sector in a multiple sector read sequence + * + * \param[out] dst Pointer to the location for the data to be read. + * + * \return true for success or false for failure. + */ + bool readData(uint8_t* dst); + /** Read OCR register. + * + * \param[out] ocr Value of OCR register. + * \return true for success or false for failure. + */ + bool readOCR(uint32_t* ocr); + /** Start a read multiple sectors sequence. + * + * \param[in] sector Address of first sector in sequence. + * + * \note This function is used with readData() and readStop() for optimized + * multiple sector reads. SPI chipSelect must be low for the entire sequence. + * + * \return true for success or false for failure. + */ + bool readStart(uint32_t sector); + /** Start a read multiple sectors sequence. + * + * \param[in] sector Address of first sector in sequence. + * \param[in] count Maximum sector count. + * \note This function is used with readData() and readStop() for optimized + * multiple sector reads. SPI chipSelect must be low for the entire sequence. + * + * \return true for success or false for failure. + */ + bool readStart(uint32_t sector, uint32_t count); + /** End a read multiple sectors sequence. + * + * \return true for success or false for failure. + */ + bool readStop(); + /** \return SDIO card status. */ + uint32_t status(); + /** + * Determine the size of an SD flash memory card. + * + * \return The number of 512 byte data sectors in the card + * or zero if an error occurs. + */ + uint32_t sectorCount(); + /** + * Send CMD12 to stop read or write. + * + * \param[in] blocking If true, wait for command complete. + * + * \return true for success or false for failure. + */ + bool stopTransmission(bool blocking); + /** \return success if sync successful. Not for user apps. */ + bool syncDevice(); + /** Return the card type: SD V1, SD V2 or SDHC + * \return 0 - SD V1, 1 - SD V2, or 3 - SDHC. + */ + uint8_t type() const; + /** + * Writes a 512 byte sector to an SD card. + * + * \param[in] sector Logical sector to be written. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + bool writeSector(uint32_t sector, const uint8_t* src); + /** + * Write multiple 512 byte sectors to an SD card. + * + * \param[in] sector Logical sector to be written. + * \param[in] ns Number of sectors to be written. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + bool writeSectors(uint32_t sector, const uint8_t* src, size_t ns); + /** Write one data sector in a multiple sector write sequence. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + bool writeData(const uint8_t* src); + /** Start a write multiple sectors sequence. + * + * \param[in] sector Address of first sector in sequence. + * + * \note This function is used with writeData() and writeStop() + * for optimized multiple sector writes. + * + * \return true for success or false for failure. + */ + bool writeStart(uint32_t sector); + /** Start a write multiple sectors sequence. + * + * \param[in] sector Address of first sector in sequence. + * \param[in] count Maximum sector count. + * \note This function is used with writeData() and writeStop() + * for optimized multiple sector writes. + * + * \return true for success or false for failure. + */ + bool writeStart(uint32_t sector, uint32_t count); + + /** End a write multiple sectors sequence. + * + * \return true for success or false for failure. + */ + bool writeStop(); + + private: + static const uint8_t IDLE_STATE = 0; + static const uint8_t READ_STATE = 1; + static const uint8_t WRITE_STATE = 2; + uint32_t m_curSector; + SdioConfig m_sdioConfig; + uint8_t m_curState = IDLE_STATE; +}; +#endif // SdioCard_h diff --git a/Firmware_V3/lib/SdFat/src/SdCard/SdioTeensy.cpp b/Firmware_V3/lib/SdFat/src/SdCard/SdioTeensy.cpp new file mode 100644 index 0000000..1f4cdf6 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SdCard/SdioTeensy.cpp @@ -0,0 +1,1136 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) || defined(__IMXRT1062__) +#include "SdioTeensy.h" +#include "SdCardInfo.h" +#include "SdioCard.h" +//============================================================================== +// limit of K66 due to errata KINETIS_K_0N65N. +const uint32_t MAX_BLKCNT = 0XFFFF; +//============================================================================== +#define SDHC_PROCTL_DTW_4BIT 0x01 +const uint32_t FIFO_WML = 16; +const uint32_t CMD8_RETRIES = 3; +const uint32_t BUSY_TIMEOUT_MICROS = 1000000; +//============================================================================== +const uint32_t SDHC_IRQSTATEN_MASK = + SDHC_IRQSTATEN_DMAESEN | SDHC_IRQSTATEN_AC12ESEN | + SDHC_IRQSTATEN_DEBESEN | SDHC_IRQSTATEN_DCESEN | + SDHC_IRQSTATEN_DTOESEN | SDHC_IRQSTATEN_CIESEN | + SDHC_IRQSTATEN_CEBESEN | SDHC_IRQSTATEN_CCESEN | + SDHC_IRQSTATEN_CTOESEN | SDHC_IRQSTATEN_DINTSEN | + SDHC_IRQSTATEN_TCSEN | SDHC_IRQSTATEN_CCSEN; + +const uint32_t SDHC_IRQSTAT_CMD_ERROR = + SDHC_IRQSTAT_CIE | SDHC_IRQSTAT_CEBE | + SDHC_IRQSTAT_CCE | SDHC_IRQSTAT_CTOE; + +const uint32_t SDHC_IRQSTAT_DATA_ERROR = + SDHC_IRQSTAT_AC12E | SDHC_IRQSTAT_DEBE | + SDHC_IRQSTAT_DCE | SDHC_IRQSTAT_DTOE; + +const uint32_t SDHC_IRQSTAT_ERROR = + SDHC_IRQSTAT_DMAE | SDHC_IRQSTAT_CMD_ERROR | + SDHC_IRQSTAT_DATA_ERROR; + +const uint32_t SDHC_IRQSIGEN_MASK = + SDHC_IRQSIGEN_DMAEIEN | SDHC_IRQSIGEN_AC12EIEN | + SDHC_IRQSIGEN_DEBEIEN | SDHC_IRQSIGEN_DCEIEN | + SDHC_IRQSIGEN_DTOEIEN | SDHC_IRQSIGEN_CIEIEN | + SDHC_IRQSIGEN_CEBEIEN | SDHC_IRQSIGEN_CCEIEN | + SDHC_IRQSIGEN_CTOEIEN | SDHC_IRQSIGEN_TCIEN; +//============================================================================== +const uint32_t CMD_RESP_NONE = SDHC_XFERTYP_RSPTYP(0); + +const uint32_t CMD_RESP_R1 = SDHC_XFERTYP_CICEN | SDHC_XFERTYP_CCCEN | + SDHC_XFERTYP_RSPTYP(2); + +const uint32_t CMD_RESP_R1b = SDHC_XFERTYP_CICEN | SDHC_XFERTYP_CCCEN | + SDHC_XFERTYP_RSPTYP(3); + +const uint32_t CMD_RESP_R2 = SDHC_XFERTYP_CCCEN | SDHC_XFERTYP_RSPTYP(1); + +const uint32_t CMD_RESP_R3 = SDHC_XFERTYP_RSPTYP(2); + +const uint32_t CMD_RESP_R6 = CMD_RESP_R1; + +const uint32_t CMD_RESP_R7 = CMD_RESP_R1; + +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) +const uint32_t DATA_READ = SDHC_XFERTYP_DTDSEL | SDHC_XFERTYP_DPSEL; + +const uint32_t DATA_READ_DMA = DATA_READ | SDHC_XFERTYP_DMAEN; + +const uint32_t DATA_READ_MULTI_DMA = DATA_READ_DMA | SDHC_XFERTYP_MSBSEL | + SDHC_XFERTYP_AC12EN | SDHC_XFERTYP_BCEN; + +const uint32_t DATA_READ_MULTI_PGM = DATA_READ | SDHC_XFERTYP_MSBSEL | + SDHC_XFERTYP_BCEN; + +const uint32_t DATA_WRITE_DMA = SDHC_XFERTYP_DPSEL | SDHC_XFERTYP_DMAEN; + +const uint32_t DATA_WRITE_MULTI_DMA = DATA_WRITE_DMA | SDHC_XFERTYP_MSBSEL | + SDHC_XFERTYP_AC12EN | SDHC_XFERTYP_BCEN; + +const uint32_t DATA_WRITE_MULTI_PGM = SDHC_XFERTYP_DPSEL | SDHC_XFERTYP_MSBSEL | + SDHC_XFERTYP_BCEN; + +#elif defined(__IMXRT1062__) +// Use low bits for SDHC_MIX_CTRL since bits 15-0 of SDHC_XFERTYP are reserved. +const uint32_t SDHC_MIX_CTRL_MASK = SDHC_MIX_CTRL_DMAEN | SDHC_MIX_CTRL_BCEN | + SDHC_MIX_CTRL_AC12EN | + SDHC_MIX_CTRL_DDR_EN | + SDHC_MIX_CTRL_DTDSEL | + SDHC_MIX_CTRL_MSBSEL | + SDHC_MIX_CTRL_NIBBLE_POS | + SDHC_MIX_CTRL_AC23EN; + +const uint32_t DATA_READ = SDHC_MIX_CTRL_DTDSEL | SDHC_XFERTYP_DPSEL; + +const uint32_t DATA_READ_DMA = DATA_READ | SDHC_MIX_CTRL_DMAEN; + +const uint32_t DATA_READ_MULTI_DMA = DATA_READ_DMA | SDHC_MIX_CTRL_MSBSEL | + SDHC_MIX_CTRL_AC12EN | SDHC_MIX_CTRL_BCEN; + +const uint32_t DATA_READ_MULTI_PGM = DATA_READ | SDHC_MIX_CTRL_MSBSEL; + + +const uint32_t DATA_WRITE_DMA = SDHC_XFERTYP_DPSEL | SDHC_MIX_CTRL_DMAEN; + +const uint32_t DATA_WRITE_MULTI_DMA = DATA_WRITE_DMA | SDHC_MIX_CTRL_MSBSEL | + SDHC_MIX_CTRL_AC12EN | SDHC_MIX_CTRL_BCEN; + +const uint32_t DATA_WRITE_MULTI_PGM = SDHC_XFERTYP_DPSEL | SDHC_MIX_CTRL_MSBSEL; + +#endif // defined(__MK64FX512__) || defined(__MK66FX1M0__) + +const uint32_t ACMD6_XFERTYP = SDHC_XFERTYP_CMDINX(ACMD6) | CMD_RESP_R1; + +const uint32_t ACMD41_XFERTYP = SDHC_XFERTYP_CMDINX(ACMD41) | CMD_RESP_R3; + +const uint32_t CMD0_XFERTYP = SDHC_XFERTYP_CMDINX(CMD0) | CMD_RESP_NONE; + +const uint32_t CMD2_XFERTYP = SDHC_XFERTYP_CMDINX(CMD2) | CMD_RESP_R2; + +const uint32_t CMD3_XFERTYP = SDHC_XFERTYP_CMDINX(CMD3) | CMD_RESP_R6; + +const uint32_t CMD6_XFERTYP = SDHC_XFERTYP_CMDINX(CMD6) | CMD_RESP_R1 | + DATA_READ_DMA; + +const uint32_t CMD7_XFERTYP = SDHC_XFERTYP_CMDINX(CMD7) | CMD_RESP_R1b; + +const uint32_t CMD8_XFERTYP = SDHC_XFERTYP_CMDINX(CMD8) | CMD_RESP_R7; + +const uint32_t CMD9_XFERTYP = SDHC_XFERTYP_CMDINX(CMD9) | CMD_RESP_R2; + +const uint32_t CMD10_XFERTYP = SDHC_XFERTYP_CMDINX(CMD10) | CMD_RESP_R2; + +const uint32_t CMD11_XFERTYP = SDHC_XFERTYP_CMDINX(CMD11) | CMD_RESP_R1; + +const uint32_t CMD12_XFERTYP = SDHC_XFERTYP_CMDINX(CMD12) | CMD_RESP_R1b | + SDHC_XFERTYP_CMDTYP(3); + +const uint32_t CMD13_XFERTYP = SDHC_XFERTYP_CMDINX(CMD13) | CMD_RESP_R1; + +const uint32_t CMD17_DMA_XFERTYP = SDHC_XFERTYP_CMDINX(CMD17) | CMD_RESP_R1 | + DATA_READ_DMA; + +const uint32_t CMD18_DMA_XFERTYP = SDHC_XFERTYP_CMDINX(CMD18) | CMD_RESP_R1 | + DATA_READ_MULTI_DMA; + +const uint32_t CMD18_PGM_XFERTYP = SDHC_XFERTYP_CMDINX(CMD18) | CMD_RESP_R1 | + DATA_READ_MULTI_PGM; + +const uint32_t CMD24_DMA_XFERTYP = SDHC_XFERTYP_CMDINX(CMD24) | CMD_RESP_R1 | + DATA_WRITE_DMA; + +const uint32_t CMD25_DMA_XFERTYP = SDHC_XFERTYP_CMDINX(CMD25) | CMD_RESP_R1 | + DATA_WRITE_MULTI_DMA; + +const uint32_t CMD25_PGM_XFERTYP = SDHC_XFERTYP_CMDINX(CMD25) | CMD_RESP_R1 | + DATA_WRITE_MULTI_PGM; + +const uint32_t CMD32_XFERTYP = SDHC_XFERTYP_CMDINX(CMD32) | CMD_RESP_R1; + +const uint32_t CMD33_XFERTYP = SDHC_XFERTYP_CMDINX(CMD33) | CMD_RESP_R1; + +const uint32_t CMD38_XFERTYP = SDHC_XFERTYP_CMDINX(CMD38) | CMD_RESP_R1b; + +const uint32_t CMD55_XFERTYP = SDHC_XFERTYP_CMDINX(CMD55) | CMD_RESP_R1; + +//============================================================================== +static bool cardCommand(uint32_t xfertyp, uint32_t arg); +static void enableGPIO(bool enable); +static void enableDmaIrs(); +static void initSDHC(); +static bool isBusyCMD13(); +static bool isBusyCommandComplete(); +static bool isBusyCommandInhibit(); +static bool readReg16(uint32_t xfertyp, void* data); +static void setSdclk(uint32_t kHzMax); +static bool yieldTimeout(bool (*fcn)()); +static bool waitDmaStatus(); +static bool waitTimeout(bool (*fcn)()); +//------------------------------------------------------------------------------ +static bool (*m_busyFcn)() = 0; +static bool m_initDone = false; +static bool m_version2; +static bool m_highCapacity; +#if ENABLE_TEENSY_SDIO_MOD +static bool m_transferActive = false; +#endif // ENABLE_TEENSY_SDIO_MOD +static uint8_t m_errorCode = SD_CARD_ERROR_INIT_NOT_CALLED; +static uint32_t m_errorLine = 0; +static uint32_t m_rca; +static volatile bool m_dmaBusy = false; +static volatile uint32_t m_irqstat; +static uint32_t m_sdClkKhz = 0; +static uint32_t m_ocr; +static cid_t m_cid; +static csd_t m_csd; +//============================================================================== +#define DBG_TRACE Serial.print("TRACE."); Serial.println(__LINE__); delay(200); +#define USE_DEBUG_MODE 0 +#if USE_DEBUG_MODE +#define DBG_IRQSTAT() if (SDHC_IRQSTAT) {Serial.print(__LINE__);\ + Serial.print(" IRQSTAT "); Serial.println(SDHC_IRQSTAT, HEX);} +static void printRegs(uint32_t line) { + uint32_t blkattr = SDHC_BLKATTR; + uint32_t xfertyp = SDHC_XFERTYP; + uint32_t prsstat = SDHC_PRSSTAT; + uint32_t proctl = SDHC_PROCTL; + uint32_t irqstat = SDHC_IRQSTAT; + Serial.print("\nLINE: "); + Serial.println(line); + Serial.print("BLKATTR "); + Serial.println(blkattr, HEX); + Serial.print("XFERTYP "); + Serial.print(xfertyp, HEX); + Serial.print(" CMD"); + Serial.print(xfertyp >> 24); + Serial.print(" TYP"); + Serial.print((xfertyp >> 2) & 3); + if (xfertyp & SDHC_XFERTYP_DPSEL) {Serial.print(" DPSEL");} + Serial.println(); + Serial.print("PRSSTAT "); + Serial.print(prsstat, HEX); + if (prsstat & SDHC_PRSSTAT_BREN) {Serial.print(" BREN");} + if (prsstat & SDHC_PRSSTAT_BWEN) {Serial.print(" BWEN");} + if (prsstat & SDHC_PRSSTAT_RTA) {Serial.print(" RTA");} + if (prsstat & SDHC_PRSSTAT_WTA) {Serial.print(" WTA");} + if (prsstat & SDHC_PRSSTAT_SDOFF) {Serial.print(" SDOFF");} + if (prsstat & SDHC_PRSSTAT_PEROFF) {Serial.print(" PEROFF");} + if (prsstat & SDHC_PRSSTAT_HCKOFF) {Serial.print(" HCKOFF");} + if (prsstat & SDHC_PRSSTAT_IPGOFF) {Serial.print(" IPGOFF");} + if (prsstat & SDHC_PRSSTAT_SDSTB) {Serial.print(" SDSTB");} + if (prsstat & SDHC_PRSSTAT_DLA) {Serial.print(" DLA");} + if (prsstat & SDHC_PRSSTAT_CDIHB) {Serial.print(" CDIHB");} + if (prsstat & SDHC_PRSSTAT_CIHB) {Serial.print(" CIHB");} + Serial.println(); + Serial.print("PROCTL "); + Serial.print(proctl, HEX); + if (proctl & SDHC_PROCTL_SABGREQ) Serial.print(" SABGREQ"); + Serial.print(" EMODE"); + Serial.print((proctl >>4) & 3); + Serial.print(" DWT"); + Serial.print((proctl >>1) & 3); + Serial.println(); + Serial.print("IRQSTAT "); + Serial.print(irqstat, HEX); + if (irqstat & SDHC_IRQSTAT_BGE) {Serial.print(" BGE");} + if (irqstat & SDHC_IRQSTAT_TC) {Serial.print(" TC");} + if (irqstat & SDHC_IRQSTAT_CC) {Serial.print(" CC");} + Serial.print("\nm_irqstat "); + Serial.println(m_irqstat, HEX); +} +#else // USE_DEBUG_MODE +#define DBG_IRQSTAT() +#endif // USE_DEBUG_MODE +//============================================================================== +// Error function and macro. +#define sdError(code) setSdErrorCode(code, __LINE__) +inline bool setSdErrorCode(uint8_t code, uint32_t line) { + m_errorCode = code; + m_errorLine = line; +#if USE_DEBUG_MODE + printRegs(line); +#endif // USE_DEBUG_MODE + return false; +} +//============================================================================== +// ISR +static void sdIrs() { + SDHC_IRQSIGEN = 0; + m_irqstat = SDHC_IRQSTAT; + SDHC_IRQSTAT = m_irqstat; +#if defined(__IMXRT1062__) + SDHC_MIX_CTRL &= ~(SDHC_MIX_CTRL_AC23EN | SDHC_MIX_CTRL_DMAEN); +#endif + m_dmaBusy = false; +} +//============================================================================== +// GPIO and clock functions. +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) +//------------------------------------------------------------------------------ +static void enableGPIO(bool enable) { + const uint32_t PORT_CLK = PORT_PCR_MUX(4) | PORT_PCR_DSE; + const uint32_t PORT_CMD_DATA = PORT_CLK | PORT_PCR_PE | PORT_PCR_PS; + const uint32_t PORT_PUP = PORT_PCR_MUX(1) | PORT_PCR_PE | PORT_PCR_PS; + + PORTE_PCR0 = enable ? PORT_CMD_DATA : PORT_PUP; // SDHC_D1 + PORTE_PCR1 = enable ? PORT_CMD_DATA : PORT_PUP; // SDHC_D0 + PORTE_PCR2 = enable ? PORT_CLK : PORT_PUP; // SDHC_CLK + PORTE_PCR3 = enable ? PORT_CMD_DATA : PORT_PUP; // SDHC_CMD + PORTE_PCR4 = enable ? PORT_CMD_DATA : PORT_PUP; // SDHC_D3 + PORTE_PCR5 = enable ? PORT_CMD_DATA : PORT_PUP; // SDHC_D2 +} +//------------------------------------------------------------------------------ +static void initClock() { +#ifdef HAS_KINETIS_MPU + // Allow SDHC Bus Master access. + MPU_RGDAAC0 |= 0x0C000000; +#endif // HAS_KINETIS_MPU + // Enable SDHC clock. + SIM_SCGC3 |= SIM_SCGC3_SDHC; +} +static uint32_t baseClock() { return F_CPU;} + +#elif defined(__IMXRT1062__) +//------------------------------------------------------------------------------ +static void gpioMux(uint8_t mode) { + IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B0_04 = mode; // DAT2 + IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B0_05 = mode; // DAT3 + IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B0_00 = mode; // CMD + IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B0_01 = mode; // CLK + IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B0_02 = mode; // DAT0 + IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B0_03 = mode; // DAT1 +} +//------------------------------------------------------------------------------ +// add speed strength args? +static void enableGPIO(bool enable) { + const uint32_t CLOCK_MASK = IOMUXC_SW_PAD_CTL_PAD_PKE | +#if defined(ARDUINO_TEENSY41) + IOMUXC_SW_PAD_CTL_PAD_DSE(7) | +#else // defined(ARDUINO_TEENSY41) + IOMUXC_SW_PAD_CTL_PAD_DSE(4) | ///// WHG +#endif // defined(ARDUINO_TEENSY41) + IOMUXC_SW_PAD_CTL_PAD_SPEED(2); + + const uint32_t DATA_MASK = CLOCK_MASK | IOMUXC_SW_PAD_CTL_PAD_PUE | + IOMUXC_SW_PAD_CTL_PAD_PUS(1); + if (enable) { + gpioMux(0); + IOMUXC_SW_PAD_CTL_PAD_GPIO_SD_B0_04 = DATA_MASK; // DAT2 + IOMUXC_SW_PAD_CTL_PAD_GPIO_SD_B0_05 = DATA_MASK; // DAT3 + IOMUXC_SW_PAD_CTL_PAD_GPIO_SD_B0_00 = DATA_MASK; // CMD + IOMUXC_SW_PAD_CTL_PAD_GPIO_SD_B0_01 = CLOCK_MASK; // CLK + IOMUXC_SW_PAD_CTL_PAD_GPIO_SD_B0_02 = DATA_MASK; // DAT0 + IOMUXC_SW_PAD_CTL_PAD_GPIO_SD_B0_03 = DATA_MASK; // DAT1 + } else { + gpioMux(5); + } +} +//------------------------------------------------------------------------------ +static void initClock() { + /* set PDF_528 PLL2PFD0 */ + CCM_ANALOG_PFD_528 |= (1 << 7); + CCM_ANALOG_PFD_528 &= ~(0x3F << 0); + CCM_ANALOG_PFD_528 |= ((24) & 0x3F << 0); // 12 - 35 + CCM_ANALOG_PFD_528 &= ~(1 << 7); + + /* Enable USDHC clock. */ + CCM_CCGR6 |= CCM_CCGR6_USDHC1(CCM_CCGR_ON); + CCM_CSCDR1 &= ~(CCM_CSCDR1_USDHC1_CLK_PODF_MASK); + CCM_CSCMR1 |= CCM_CSCMR1_USDHC1_CLK_SEL; // PLL2PFD0 +// CCM_CSCDR1 |= CCM_CSCDR1_USDHC1_CLK_PODF((7)); / &0x7 WHG + CCM_CSCDR1 |= CCM_CSCDR1_USDHC1_CLK_PODF((1)); +} +//------------------------------------------------------------------------------ +static uint32_t baseClock() { + uint32_t divider = ((CCM_CSCDR1 >> 11) & 0x7) + 1; + return (528000000U * 3)/((CCM_ANALOG_PFD_528 & 0x3F)/6)/divider; +} +#endif // defined(__MK64FX512__) || defined(__MK66FX1M0__) +//============================================================================== +// Static functions. +static bool cardAcmd(uint32_t rca, uint32_t xfertyp, uint32_t arg) { + return cardCommand(CMD55_XFERTYP, rca) && cardCommand (xfertyp, arg); +} +//------------------------------------------------------------------------------ +static bool cardCommand(uint32_t xfertyp, uint32_t arg) { + DBG_IRQSTAT(); + if (waitTimeout(isBusyCommandInhibit)) { + return false; // Caller will set errorCode. + } + SDHC_CMDARG = arg; +#if defined(__IMXRT1062__) + // Set MIX_CTRL if data transfer. + if (xfertyp & SDHC_XFERTYP_DPSEL) { + SDHC_MIX_CTRL &= ~SDHC_MIX_CTRL_MASK; + SDHC_MIX_CTRL |= xfertyp & SDHC_MIX_CTRL_MASK; + } + xfertyp &= ~SDHC_MIX_CTRL_MASK; +#endif // defined(__IMXRT1062__) + SDHC_XFERTYP = xfertyp; + if (waitTimeout(isBusyCommandComplete)) { + return false; // Caller will set errorCode. + } + m_irqstat = SDHC_IRQSTAT; + SDHC_IRQSTAT = m_irqstat; + + return (m_irqstat & SDHC_IRQSTAT_CC) && + !(m_irqstat & SDHC_IRQSTAT_CMD_ERROR); +} +//------------------------------------------------------------------------------ +static bool cardCMD6(uint32_t arg, uint8_t* status) { + // CMD6 returns 64 bytes. + if (waitTimeout(isBusyCMD13)) { + return sdError(SD_CARD_ERROR_CMD13); + } + enableDmaIrs(); + SDHC_DSADDR = (uint32_t)status; + SDHC_BLKATTR = SDHC_BLKATTR_BLKCNT(1) | SDHC_BLKATTR_BLKSIZE(64); + SDHC_IRQSIGEN = SDHC_IRQSIGEN_MASK; + if (!cardCommand(CMD6_XFERTYP, arg)) { + return sdError(SD_CARD_ERROR_CMD6); + } + if (!waitDmaStatus()) { + return sdError(SD_CARD_ERROR_DMA); + } + return true; +} +//------------------------------------------------------------------------------ +static void enableDmaIrs() { + m_dmaBusy = true; + m_irqstat = 0; +} +//------------------------------------------------------------------------------ +static void initSDHC() { + initClock(); + + // Disable GPIO clock. + enableGPIO(false); + +#if defined (__IMXRT1062__) + SDHC_MIX_CTRL |= 0x80000000; +#endif // (__IMXRT1062__) + + // Reset SDHC. Use default Water Mark Level of 16. + SDHC_SYSCTL |= SDHC_SYSCTL_RSTA | SDHC_SYSCTL_SDCLKFS(0x80); + + while (SDHC_SYSCTL & SDHC_SYSCTL_RSTA) { + } + + // Set initial SCK rate. + setSdclk(SD_MAX_INIT_RATE_KHZ); + + enableGPIO(true); + + // Enable desired IRQSTAT bits. + SDHC_IRQSTATEN = SDHC_IRQSTATEN_MASK; + + attachInterruptVector(IRQ_SDHC, sdIrs); + NVIC_SET_PRIORITY(IRQ_SDHC, 6*16); + NVIC_ENABLE_IRQ(IRQ_SDHC); + + // Send 80 clocks to card. + SDHC_SYSCTL |= SDHC_SYSCTL_INITA; + while (SDHC_SYSCTL & SDHC_SYSCTL_INITA) { + } +} +//------------------------------------------------------------------------------ +static uint32_t statusCMD13() { + return cardCommand(CMD13_XFERTYP, m_rca) ? SDHC_CMDRSP0 : 0; +} +//------------------------------------------------------------------------------ +static bool isBusyCMD13() { + return !(statusCMD13() & CARD_STATUS_READY_FOR_DATA); +} +//------------------------------------------------------------------------------ +static bool isBusyCommandComplete() { + return !(SDHC_IRQSTAT & (SDHC_IRQSTAT_CC | SDHC_IRQSTAT_CMD_ERROR)); +} +//------------------------------------------------------------------------------ +static bool isBusyCommandInhibit() { + return SDHC_PRSSTAT & SDHC_PRSSTAT_CIHB; +} +//------------------------------------------------------------------------------ +static bool isBusyDat() { + return SDHC_PRSSTAT & (1 << 24) ? false : true; +} +//------------------------------------------------------------------------------ +static bool isBusyDMA() { + return m_dmaBusy; +} +//------------------------------------------------------------------------------ +static bool isBusyFifoRead() { + return !(SDHC_PRSSTAT & SDHC_PRSSTAT_BREN); +} +//------------------------------------------------------------------------------ +static bool isBusyFifoWrite() { + return !(SDHC_PRSSTAT & SDHC_PRSSTAT_BWEN); +} +//------------------------------------------------------------------------------ +static bool isBusyTransferComplete() { + return !(SDHC_IRQSTAT & (SDHC_IRQSTAT_TC | SDHC_IRQSTAT_ERROR)); +} +//------------------------------------------------------------------------------ +static bool rdWrSectors(uint32_t xfertyp, + uint32_t sector, uint8_t* buf, size_t n) { + if ((3 & (uint32_t)buf) || n == 0) { + return sdError(SD_CARD_ERROR_DMA); + } + if (yieldTimeout(isBusyCMD13)) { + return sdError(SD_CARD_ERROR_CMD13); + } + enableDmaIrs(); + SDHC_DSADDR = (uint32_t)buf; + SDHC_BLKATTR = SDHC_BLKATTR_BLKCNT(n) | SDHC_BLKATTR_BLKSIZE(512); + SDHC_IRQSIGEN = SDHC_IRQSIGEN_MASK; + if (!cardCommand(xfertyp, m_highCapacity ? sector : 512*sector)) { + return false; + } + return waitDmaStatus(); +} +//------------------------------------------------------------------------------ +// Read 16 byte CID or CSD register. +static bool readReg16(uint32_t xfertyp, void* data) { + uint8_t* d = reinterpret_cast(data); + if (!cardCommand(xfertyp, m_rca)) { + return false; // Caller will set errorCode. + } + uint32_t sr[] = {SDHC_CMDRSP0, SDHC_CMDRSP1, SDHC_CMDRSP2, SDHC_CMDRSP3}; + for (int i = 0; i < 15; i++) { + d[14 - i] = sr[i/4] >> 8*(i%4); + } + d[15] = 0; + return true; +} +//------------------------------------------------------------------------------ +static void setSdclk(uint32_t kHzMax) { + const uint32_t DVS_LIMIT = 0X10; + const uint32_t SDCLKFS_LIMIT = 0X100; + uint32_t dvs = 1; + uint32_t sdclkfs = 1; + uint32_t maxSdclk = 1000*kHzMax; + uint32_t base = baseClock(); + + while ((base/(sdclkfs*DVS_LIMIT) > maxSdclk) && (sdclkfs < SDCLKFS_LIMIT)) { + sdclkfs <<= 1; + } + while ((base/(sdclkfs*dvs) > maxSdclk) && (dvs < DVS_LIMIT)) { + dvs++; + } + m_sdClkKhz = base/(1000*sdclkfs*dvs); + sdclkfs >>= 1; + dvs--; +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) + // Disable SDHC clock. + SDHC_SYSCTL &= ~SDHC_SYSCTL_SDCLKEN; +#endif // defined(__MK64FX512__) || defined(__MK66FX1M0__) + + // Change dividers. + uint32_t sysctl = SDHC_SYSCTL & ~(SDHC_SYSCTL_DTOCV_MASK + | SDHC_SYSCTL_DVS_MASK | SDHC_SYSCTL_SDCLKFS_MASK); + + SDHC_SYSCTL = sysctl | SDHC_SYSCTL_DTOCV(0x0E) | SDHC_SYSCTL_DVS(dvs) + | SDHC_SYSCTL_SDCLKFS(sdclkfs); + + // Wait until the SDHC clock is stable. + while (!(SDHC_PRSSTAT & SDHC_PRSSTAT_SDSTB)) { + } + +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) + // Enable the SDHC clock. + SDHC_SYSCTL |= SDHC_SYSCTL_SDCLKEN; +#endif // defined(__MK64FX512__) || defined(__MK66FX1M0__) +} +//------------------------------------------------------------------------------ +static bool transferStop() { + // This fix allows CDIHB to be cleared in Tennsy 3.x without a reset. + SDHC_PROCTL &= ~SDHC_PROCTL_SABGREQ; + if (!cardCommand(CMD12_XFERTYP, 0)) { + return sdError(SD_CARD_ERROR_CMD12); + } +// if (yieldTimeout(isBusyCMD13)) { + if (yieldTimeout(isBusyDat)) { + return sdError(SD_CARD_ERROR_CMD13); + } + if (SDHC_PRSSTAT & SDHC_PRSSTAT_CDIHB) { + // This should not happen after above fix. + // Save registers before reset DAT lines. + uint32_t irqsststen = SDHC_IRQSTATEN; + uint32_t proctl = SDHC_PROCTL & ~SDHC_PROCTL_SABGREQ; + // Do reset to clear CDIHB. Should be a better way! + SDHC_SYSCTL |= SDHC_SYSCTL_RSTD; + // Restore registers. + SDHC_IRQSTATEN = irqsststen; + SDHC_PROCTL = proctl; + } + return true; +} +//------------------------------------------------------------------------------ +// Return true if timeout occurs. +static bool yieldTimeout(bool (*fcn)()) { + m_busyFcn = fcn; + uint32_t m = micros(); + while (fcn()) { + if ((micros() - m) > BUSY_TIMEOUT_MICROS) { + m_busyFcn = 0; + return true; + } + SysCall::yield(); + } + m_busyFcn = 0; + return false; // Caller will set errorCode. +} +//------------------------------------------------------------------------------ +static bool waitDmaStatus() { + if (yieldTimeout(isBusyDMA)) { + return false; // Caller will set errorCode. + } + return (m_irqstat & SDHC_IRQSTAT_TC) && !(m_irqstat & SDHC_IRQSTAT_ERROR); +} +//------------------------------------------------------------------------------ +// Return true if timeout occurs. +static bool waitTimeout(bool (*fcn)()) { + uint32_t m = micros(); + while (fcn()) { + if ((micros() - m) > BUSY_TIMEOUT_MICROS) { + return true; + } + } + return false; // Caller will set errorCode. +} +#if ENABLE_TEENSY_SDIO_MOD +//------------------------------------------------------------------------------ +static bool waitTransferComplete() { + if (!m_transferActive) { + return true; + } + bool timeOut = waitTimeout(isBusyTransferComplete); + m_transferActive = false; + m_irqstat = SDHC_IRQSTAT; + SDHC_IRQSTAT = m_irqstat; + if (timeOut || (m_irqstat & SDHC_IRQSTAT_ERROR)) { + return sdError(SD_CARD_ERROR_TRANSFER_COMPLETE); + } + return true; +} +#endif // ENABLE_TEENSY_SDIO_MOD +//============================================================================== +// Start of SdioCard member functions. +//============================================================================== +bool SdioCard::begin(SdioConfig sdioConfig) { + uint32_t kHzSdClk; + uint32_t arg; + m_sdioConfig = sdioConfig; + m_curState = IDLE_STATE; + m_initDone = false; + m_errorCode = SD_CARD_ERROR_NONE; + m_highCapacity = false; + m_version2 = false; + + // initialize controller. + initSDHC(); + if (!cardCommand(CMD0_XFERTYP, 0)) { + return sdError(SD_CARD_ERROR_CMD0); + } + // Try several times for case of reset delay. + for (uint32_t i = 0; i < CMD8_RETRIES; i++) { + if (cardCommand(CMD8_XFERTYP, 0X1AA)) { + if (SDHC_CMDRSP0 != 0X1AA) { + return sdError(SD_CARD_ERROR_CMD8); + } + m_version2 = true; + break; + } + } + arg = m_version2 ? 0X40300000 : 0x00300000; + int m = micros(); + do { + if (!cardAcmd(0, ACMD41_XFERTYP, arg) || + ((micros() - m) > BUSY_TIMEOUT_MICROS)) { + return sdError(SD_CARD_ERROR_ACMD41); + } + } while ((SDHC_CMDRSP0 & 0x80000000) == 0); + m_ocr = SDHC_CMDRSP0; + if (SDHC_CMDRSP0 & 0x40000000) { + // Is high capacity. + m_highCapacity = true; + } + if (!cardCommand(CMD2_XFERTYP, 0)) { + return sdError(SD_CARD_ERROR_CMD2); + } + if (!cardCommand(CMD3_XFERTYP, 0)) { + return sdError(SD_CARD_ERROR_CMD3); + } + m_rca = SDHC_CMDRSP0 & 0xFFFF0000; + + if (!readReg16(CMD9_XFERTYP, &m_csd)) { + return sdError(SD_CARD_ERROR_CMD9); + } + if (!readReg16(CMD10_XFERTYP, &m_cid)) { + return sdError(SD_CARD_ERROR_CMD10); + } + if (!cardCommand(CMD7_XFERTYP, m_rca)) { + return sdError(SD_CARD_ERROR_CMD7); + } + // Set card to bus width four. + if (!cardAcmd(m_rca, ACMD6_XFERTYP, 2)) { + return sdError(SD_CARD_ERROR_ACMD6); + } + // Set SDHC to bus width four. + SDHC_PROCTL &= ~SDHC_PROCTL_DTW_MASK; + SDHC_PROCTL |= SDHC_PROCTL_DTW(SDHC_PROCTL_DTW_4BIT); + + SDHC_WML = SDHC_WML_RDWML(FIFO_WML) | SDHC_WML_WRWML(FIFO_WML); + + // Determine if High Speed mode is supported and set frequency. + // Check status[16] for error 0XF or status[16] for new mode 0X1. + uint8_t status[64]; + if (cardCMD6(0X00FFFFFF, status) && (2 & status[13]) && + cardCMD6(0X80FFFFF1, status) && (status[16] & 0XF) == 1) { + kHzSdClk = 50000; + } else { + kHzSdClk = 25000; + } + // Disable GPIO. + enableGPIO(false); + + // Set the SDHC SCK frequency. + setSdclk(kHzSdClk); + + // Enable GPIO. + enableGPIO(true); + m_initDone = true; + return true; +} +//------------------------------------------------------------------------------ +bool SdioCard::erase(uint32_t firstSector, uint32_t lastSector) { +#if ENABLE_TEENSY_SDIO_MOD + if (m_curState != IDLE_STATE && !syncDevice()) { + return false; + } +#endif // ENABLE_TEENSY_SDIO_MOD + // check for single sector erase + if (!m_csd.v1.erase_blk_en) { + // erase size mask + uint8_t m = (m_csd.v1.sector_size_high << 1) | m_csd.v1.sector_size_low; + if ((firstSector & m) != 0 || ((lastSector + 1) & m) != 0) { + // error card can't erase specified area + return sdError(SD_CARD_ERROR_ERASE_SINGLE_SECTOR); + } + } + if (!m_highCapacity) { + firstSector <<= 9; + lastSector <<= 9; + } + if (!cardCommand(CMD32_XFERTYP, firstSector)) { + return sdError(SD_CARD_ERROR_CMD32); + } + if (!cardCommand(CMD33_XFERTYP, lastSector)) { + return sdError(SD_CARD_ERROR_CMD33); + } + if (!cardCommand(CMD38_XFERTYP, 0)) { + return sdError(SD_CARD_ERROR_CMD38); + } + if (waitTimeout(isBusyCMD13)) { + return sdError(SD_CARD_ERROR_ERASE_TIMEOUT); + } + return true; +} +//------------------------------------------------------------------------------ +uint8_t SdioCard::errorCode() const { + return m_errorCode; +} +//------------------------------------------------------------------------------ +uint32_t SdioCard::errorData() const { + return m_irqstat; +} +//------------------------------------------------------------------------------ +uint32_t SdioCard::errorLine() const { + return m_errorLine; +} +//------------------------------------------------------------------------------ +bool SdioCard::isBusy() { +#if ENABLE_TEENSY_SDIO_MOD + if (m_sdioConfig.useDma()) { + return m_busyFcn ? m_busyFcn() : m_initDone && isBusyCMD13(); + } else { + if (m_transferActive) { + if (isBusyTransferComplete()) { + return true; + } +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) + if ((SDHC_BLKATTR & 0XFFFF0000) != 0) { + return false; + } + m_transferActive = false; + stopTransmission(false); + return true; +#else // defined(__MK64FX512__) || defined(__MK66FX1M0__) + return false; +#endif // defined(__MK64FX512__) || defined(__MK66FX1M0__) + } + // Use DAT0 low as busy. + return SDHC_PRSSTAT & (1 << 24) ? false : true; + } +#else // ENABLE_TEENSY_SDIO_MOD + return m_busyFcn ? m_busyFcn() : m_initDone && isBusyCMD13(); +#endif // ENABLE_TEENSY_SDIO_MOD +} +//------------------------------------------------------------------------------ +uint32_t SdioCard::kHzSdClk() { + return m_sdClkKhz; +} +//------------------------------------------------------------------------------ +bool SdioCard::readCID(cid_t* cid) { + memcpy(cid, &m_cid, 16); + return true; +} +//------------------------------------------------------------------------------ +bool SdioCard::readCSD(csd_t* csd) { + memcpy(csd, &m_csd, 16); + return true; +} +//------------------------------------------------------------------------------ +bool SdioCard::readData(uint8_t* dst) { + DBG_IRQSTAT(); + uint32_t* p32 = reinterpret_cast(dst); + + if (!(SDHC_PRSSTAT & SDHC_PRSSTAT_RTA)) { + SDHC_PROCTL &= ~SDHC_PROCTL_SABGREQ; + noInterrupts(); + SDHC_PROCTL |= SDHC_PROCTL_CREQ; + SDHC_PROCTL |= SDHC_PROCTL_SABGREQ; + interrupts(); + } + if (waitTimeout(isBusyFifoRead)) { + return sdError(SD_CARD_ERROR_READ_FIFO); + } + for (uint32_t iw = 0 ; iw < 512/(4*FIFO_WML); iw++) { + while (0 == (SDHC_PRSSTAT & SDHC_PRSSTAT_BREN)) { + } + for (uint32_t i = 0; i < FIFO_WML; i++) { + p32[i] = SDHC_DATPORT; + } + p32 += FIFO_WML; + } + if (waitTimeout(isBusyTransferComplete)) { + return sdError(SD_CARD_ERROR_READ_TIMEOUT); + } + m_irqstat = SDHC_IRQSTAT; + SDHC_IRQSTAT = m_irqstat; + return (m_irqstat & SDHC_IRQSTAT_TC) && !(m_irqstat & SDHC_IRQSTAT_ERROR); +} +//------------------------------------------------------------------------------ +bool SdioCard::readOCR(uint32_t* ocr) { + *ocr = m_ocr; + return true; +} +//------------------------------------------------------------------------------ +bool SdioCard::readSector(uint32_t sector, uint8_t* dst) { + if (m_sdioConfig.useDma()) { + uint8_t aligned[512]; + + uint8_t* ptr = (uint32_t)dst & 3 ? aligned : dst; + + if (!rdWrSectors(CMD17_DMA_XFERTYP, sector, ptr, 1)) { + return sdError(SD_CARD_ERROR_CMD17); + } + if (ptr != dst) { + memcpy(dst, aligned, 512); + } + } else { +#if ENABLE_TEENSY_SDIO_MOD + if (!waitTransferComplete()) { + return false; + } +#endif // ENABLE_TEENSY_SDIO_MOD + if (m_curState != READ_STATE || sector != m_curSector) { + if (!syncDevice()) { + return false; + } + if (!readStart(sector)) { + return false; + } + m_curSector = sector; + m_curState = READ_STATE; + } + if (!readData(dst)) { + return false; + } +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) + if ((SDHC_BLKATTR & 0XFFFF0000) == 0) { + if (!syncDevice()) { + return false; + } + } +#endif // defined(__MK64FX512__) || defined(__MK66FX1M0__) + m_curSector++; + } + return true; +} +//------------------------------------------------------------------------------ +bool SdioCard::readSectors(uint32_t sector, uint8_t* dst, size_t n) { + if (m_sdioConfig.useDma()) { + if ((uint32_t)dst & 3) { + for (size_t i = 0; i < n; i++, sector++, dst += 512) { + if (!readSector(sector, dst)) { + return false; // readSector will set errorCode. + } + } + return true; + } + if (!rdWrSectors(CMD18_DMA_XFERTYP, sector, dst, n)) { + return sdError(SD_CARD_ERROR_CMD18); + } + } else { + for (size_t i = 0; i < n; i++) { + if (!readSector(sector + i, dst + i*512UL)) { + return false; + } + } + } + return true; +} +//------------------------------------------------------------------------------ +// SDHC will do Auto CMD12 after count sectors. +bool SdioCard::readStart(uint32_t sector) { + DBG_IRQSTAT(); + if (yieldTimeout(isBusyCMD13)) { + return sdError(SD_CARD_ERROR_CMD13); + } + SDHC_PROCTL |= SDHC_PROCTL_SABGREQ; +#if defined(__IMXRT1062__) + // Infinite transfer. + SDHC_BLKATTR = SDHC_BLKATTR_BLKSIZE(512); +#else // defined(__IMXRT1062__) + // Errata - can't do infinite transfer. + SDHC_BLKATTR = SDHC_BLKATTR_BLKCNT(MAX_BLKCNT) | SDHC_BLKATTR_BLKSIZE(512); +#endif // defined(__IMXRT1062__) + + if (!cardCommand(CMD18_PGM_XFERTYP, m_highCapacity ? sector : 512*sector)) { + return sdError(SD_CARD_ERROR_CMD18); + } + return true; +} +//------------------------------------------------------------------------------ +bool SdioCard::readStop() { + return transferStop(); +} +//------------------------------------------------------------------------------ +uint32_t SdioCard::sectorCount() { + return sdCardCapacity(&m_csd); +} +//------------------------------------------------------------------------------ +uint32_t SdioCard::status() { + return statusCMD13(); +} +//------------------------------------------------------------------------------ +bool SdioCard::stopTransmission(bool blocking) { + m_curState = IDLE_STATE; + // This fix allows CDIHB to be cleared in Tennsy 3.x without a reset. + SDHC_PROCTL &= ~SDHC_PROCTL_SABGREQ; + if (!cardCommand(CMD12_XFERTYP, 0)) { + return sdError(SD_CARD_ERROR_CMD12); + } + if (blocking) { + if (yieldTimeout(isBusyDat)) { + return sdError(SD_CARD_ERROR_CMD13); + } + } + return true; +} +//------------------------------------------------------------------------------ +bool SdioCard::syncDevice() { +#if ENABLE_TEENSY_SDIO_MOD + if (!waitTransferComplete()) { + return false; + } + if (m_curState != IDLE_STATE) { + return stopTransmission(true); + } +#else // ENABLE_TEENSY_SDIO_MOD + if (m_curState == READ_STATE) { + m_curState = IDLE_STATE; + if (!readStop()) { + return false; + } + } else if (m_curState == WRITE_STATE) { + m_curState = IDLE_STATE; + if (!writeStop()) { + return false; + } + } +#endif // ENABLE_TEENSY_SDIO_MOD + return true; +} +//------------------------------------------------------------------------------ +uint8_t SdioCard::type() const { + return m_version2 ? m_highCapacity ? + SD_CARD_TYPE_SDHC : SD_CARD_TYPE_SD2 : SD_CARD_TYPE_SD1; +} +//------------------------------------------------------------------------------ +bool SdioCard::writeData(const uint8_t* src) { + DBG_IRQSTAT(); +#if ENABLE_TEENSY_SDIO_MOD + if (!waitTransferComplete()) { + return false; + } +#endif // ENABLE_TEENSY_SDIO_MOD + const uint32_t* p32 = reinterpret_cast(src); + if (!(SDHC_PRSSTAT & SDHC_PRSSTAT_WTA)) { + SDHC_PROCTL &= ~SDHC_PROCTL_SABGREQ; + SDHC_PROCTL |= SDHC_PROCTL_CREQ; + } + SDHC_PROCTL |= SDHC_PROCTL_SABGREQ; + if (waitTimeout(isBusyFifoWrite)) { + return sdError(SD_CARD_ERROR_WRITE_FIFO); + } + for (uint32_t iw = 0 ; iw < 512/(4*FIFO_WML); iw++) { + while (0 == (SDHC_PRSSTAT & SDHC_PRSSTAT_BWEN)) { + } + for (uint32_t i = 0; i < FIFO_WML; i++) { + SDHC_DATPORT = p32[i]; + } + p32 += FIFO_WML; + } +#if ENABLE_TEENSY_SDIO_MOD + m_transferActive = true; + return true; +#else // ENABLE_TEENSY_SDIO_MOD + if (waitTimeout(isBusyTransferComplete)) { + return sdError(SD_CARD_ERROR_WRITE_TIMEOUT); + } + m_irqstat = SDHC_IRQSTAT; + SDHC_IRQSTAT = m_irqstat; + return (m_irqstat & SDHC_IRQSTAT_TC) && !(m_irqstat & SDHC_IRQSTAT_ERROR); +#endif // ENABLE_TEENSY_SDIO_MOD +} +//------------------------------------------------------------------------------ +bool SdioCard::writeSector(uint32_t sector, const uint8_t* src) { + if (m_sdioConfig.useDma()) { + uint8_t* ptr; + uint8_t aligned[512]; + if (3 & (uint32_t)src) { + ptr = aligned; + memcpy(aligned, src, 512); + } else { + ptr = const_cast(src); + } + if (!rdWrSectors(CMD24_DMA_XFERTYP, sector, ptr, 1)) { + return sdError(SD_CARD_ERROR_CMD24); + } + } else { +#if ENABLE_TEENSY_SDIO_MOD + if (!waitTransferComplete()) { + return false; + } +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) + // End transfer with CMD12 if required. + if ((SDHC_BLKATTR & 0XFFFF0000) == 0) { + if (!syncDevice()) { + return false; + } + } +#endif // defined(__MK64FX512__) || defined(__MK66FX1M0__) +#endif // ENABLE_TEENSY_SDIO_MOD + if (m_curState != WRITE_STATE || m_curSector != sector) { + if (!syncDevice()) { + return false; + } + if (!writeStart(sector )) { + return false; + } + m_curSector = sector; + m_curState = WRITE_STATE; + } + if (!writeData(src)) { + return false; + } + m_curSector++; +#if !ENABLE_TEENSY_SDIO_MOD +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) + // End transfer with CMD12 if required. + if ((SDHC_BLKATTR & 0XFFFF0000) == 0) { + if (!syncDevice()) { + return false; + } + } +#endif // defined(__MK64FX512__) || defined(__MK66FX1M0__) +#endif // !ENABLE_TEENSY_SDIO_MOD + } + return true; +} +//------------------------------------------------------------------------------ +bool SdioCard::writeSectors(uint32_t sector, const uint8_t* src, size_t n) { + if (m_sdioConfig.useDma()) { + uint8_t* ptr = const_cast(src); + if (3 & (uint32_t)ptr) { + for (size_t i = 0; i < n; i++, sector++, ptr += 512) { + if (!writeSector(sector, ptr)) { + return false; // writeSector will set errorCode. + } + } + return true; + } + if (!rdWrSectors(CMD25_DMA_XFERTYP, sector, ptr, n)) { + return sdError(SD_CARD_ERROR_CMD25); + } + } else { + for (size_t i = 0; i < n; i++) { + if (!writeSector(sector + i, src + i*512UL)) { + return false; + } + } + } + return true; +} +//------------------------------------------------------------------------------ +bool SdioCard::writeStart(uint32_t sector) { + if (yieldTimeout(isBusyCMD13)) { + return sdError(SD_CARD_ERROR_CMD13); + } + SDHC_PROCTL &= ~SDHC_PROCTL_SABGREQ; + +#if defined(__IMXRT1062__) + // Infinite transfer. + SDHC_BLKATTR = SDHC_BLKATTR_BLKSIZE(512); +#else // defined(__IMXRT1062__) + // Errata - can't do infinite transfer. + SDHC_BLKATTR = SDHC_BLKATTR_BLKCNT(MAX_BLKCNT) | SDHC_BLKATTR_BLKSIZE(512); +#endif // defined(__IMXRT1062__) + if (!cardCommand(CMD25_PGM_XFERTYP, m_highCapacity ? sector : 512*sector)) { + return sdError(SD_CARD_ERROR_CMD25); + } + return true; +} +//------------------------------------------------------------------------------ +bool SdioCard::writeStop() { + return transferStop(); +} +#endif // defined(__MK64FX512__) defined(__MK66FX1M0__) defined(__IMXRT1062__) diff --git a/Firmware_V3/lib/SdFat/src/SdCard/SdioTeensy.h b/Firmware_V3/lib/SdFat/src/SdCard/SdioTeensy.h new file mode 100644 index 0000000..69573dc --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SdCard/SdioTeensy.h @@ -0,0 +1,277 @@ +#ifndef SdioTeensy_h +#define SdioTeensy_h + +// From Paul's SD.h driver. + +#if defined(__IMXRT1062__) +#define MAKE_REG_MASK(m,s) (((uint32_t)(((uint32_t)(m) << s)))) +#define MAKE_REG_GET(x,m,s) (((uint32_t)(((uint32_t)(x)>>s) & m))) +#define MAKE_REG_SET(x,m,s) (((uint32_t)(((uint32_t)(x) & m) << s))) + +#define SDHC_BLKATTR_BLKSIZE_MASK MAKE_REG_MASK(0x1FFF,0) //uint32_t)(((n) & 0x1FFF)<<0) // Transfer Block Size Mask +#define SDHC_BLKATTR_BLKSIZE(n) MAKE_REG_SET(n,0x1FFF,0) //uint32_t)(((n) & 0x1FFF)<<0) // Transfer Block Size +#define SDHC_BLKATTR_BLKCNT_MASK MAKE_REG_MASK(0x1FFF,16) //((uint32_t)0x1FFF<<16) +#define SDHC_BLKATTR_BLKCNT(n) MAKE_REG_SET(n,0x1FFF,16) //(uint32_t)(((n) & 0x1FFF)<<16) // Blocks Count For Current Transfer + +#define SDHC_XFERTYP_CMDINX(n) MAKE_REG_SET(n,0x3F,24) //(uint32_t)(((n) & 0x3F)<<24)// Command Index +#define SDHC_XFERTYP_CMDTYP(n) MAKE_REG_SET(n,0x3,22) //(uint32_t)(((n) & 0x3)<<22) // Command Type +#define SDHC_XFERTYP_DPSEL MAKE_REG_MASK(0x1,21) //((uint32_t)0x00200000) // Data Present Select +#define SDHC_XFERTYP_CICEN MAKE_REG_MASK(0x1,20) //((uint32_t)0x00100000) // Command Index Check Enable +#define SDHC_XFERTYP_CCCEN MAKE_REG_MASK(0x1,19) //((uint32_t)0x00080000) // Command CRC Check Enable +#define SDHC_XFERTYP_RSPTYP(n) MAKE_REG_SET(n,0x3,16) //(uint32_t)(((n) & 0x3)<<16) // Response Type Select +#define SDHC_XFERTYP_MSBSEL MAKE_REG_MASK(0x1,5) //((uint32_t)0x00000020) // Multi/Single Block Select +#define SDHC_XFERTYP_DTDSEL MAKE_REG_MASK(0x1,4) //((uint32_t)0x00000010) // Data Transfer Direction Select +#define SDHC_XFERTYP_AC12EN MAKE_REG_MASK(0x1,2) //((uint32_t)0x00000004) // Auto CMD12 Enable +#define SDHC_XFERTYP_BCEN MAKE_REG_MASK(0x1,1) //((uint32_t)0x00000002) // Block Count Enable +#define SDHC_XFERTYP_DMAEN MAKE_REG_MASK(0x3,0) //((uint32_t)0x00000001) // DMA Enable + +#define SDHC_PRSSTAT_DLSL_MASK MAKE_REG_MASK(0xFF,24) //((uint32_t)0xFF000000) // DAT Line Signal Level +#define SDHC_PRSSTAT_CLSL MAKE_REG_MASK(0x1,23) //((uint32_t)0x00800000) // CMD Line Signal Level +#define SDHC_PRSSTAT_WPSPL MAKE_REG_MASK(0x1,19) // +#define SDHC_PRSSTAT_CDPL MAKE_REG_MASK(0x1,18) // +#define SDHC_PRSSTAT_CINS MAKE_REG_MASK(0x1,16) //((uint32_t)0x00010000) // Card Inserted +#define SDHC_PRSSTAT_TSCD MAKE_REG_MASK(0x1,15) +#define SDHC_PRSSTAT_RTR MAKE_REG_MASK(0x1,12) +#define SDHC_PRSSTAT_BREN MAKE_REG_MASK(0x1,11) //((uint32_t)0x00000800) // Buffer Read Enable +#define SDHC_PRSSTAT_BWEN MAKE_REG_MASK(0x1,10) //((uint32_t)0x00000400) // Buffer Write Enable +#define SDHC_PRSSTAT_RTA MAKE_REG_MASK(0x1,9) //((uint32_t)0x00000200) // Read Transfer Active +#define SDHC_PRSSTAT_WTA MAKE_REG_MASK(0x1,8) //((uint32_t)0x00000100) // Write Transfer Active +#define SDHC_PRSSTAT_SDOFF MAKE_REG_MASK(0x1,7) //((uint32_t)0x00000080) // SD Clock Gated Off Internally +#define SDHC_PRSSTAT_PEROFF MAKE_REG_MASK(0x1,6) //((uint32_t)0x00000040) // SDHC clock Gated Off Internally +#define SDHC_PRSSTAT_HCKOFF MAKE_REG_MASK(0x1,5) //((uint32_t)0x00000020) // System Clock Gated Off Internally +#define SDHC_PRSSTAT_IPGOFF MAKE_REG_MASK(0x1,4) //((uint32_t)0x00000010) // Bus Clock Gated Off Internally +#define SDHC_PRSSTAT_SDSTB MAKE_REG_MASK(0x1,3) //((uint32_t)0x00000008) // SD Clock Stable +#define SDHC_PRSSTAT_DLA MAKE_REG_MASK(0x1,2) //((uint32_t)0x00000004) // Data Line Active +#define SDHC_PRSSTAT_CDIHB MAKE_REG_MASK(0x1,1) //((uint32_t)0x00000002) // Command Inhibit (DAT) +#define SDHC_PRSSTAT_CIHB MAKE_REG_MASK(0x1,0) //((uint32_t)0x00000001) // Command Inhibit (CMD) + +#define SDHC_PROTCT_NONEXACT_BLKRD MAKE_REG_MASK(0x1,30) // +#define SDHC_PROTCT_BURST_LENEN(n) MAKE_REG_SET(n,0x7,12) // +#define SDHC_PROCTL_WECRM MAKE_REG_MASK(0x1,26) //((uint32_t)0x04000000) // Wakeup Event Enable On SD Card Removal +#define SDHC_PROCTL_WECINS MAKE_REG_MASK(0x1,25) //((uint32_t)0x02000000) // Wakeup Event Enable On SD Card Insertion +#define SDHC_PROCTL_WECINT MAKE_REG_MASK(0x1,24) //((uint32_t)0x01000000) // Wakeup Event Enable On Card Interrupt +#define SDHC_PROCTL_RD_DONE_NOBLK MAKE_REG_MASK(0x1,20) // +#define SDHC_PROCTL_IABG MAKE_REG_MASK(0x1,19) //((uint32_t)0x00080000) // Interrupt At Block Gap +#define SDHC_PROCTL_RWCTL MAKE_REG_MASK(0x1,18) //((uint32_t)0x00040000) // Read Wait Control +#define SDHC_PROCTL_CREQ MAKE_REG_MASK(0x1,17) //((uint32_t)0x00020000) // Continue Request +#define SDHC_PROCTL_SABGREQ MAKE_REG_MASK(0x1,16) //((uint32_t)0x00010000) // Stop At Block Gap Request +#define SDHC_PROCTL_DMAS(n) MAKE_REG_SET(n,0x3,8) //(uint32_t)(((n) & 0x3)<<8) // DMA Select +#define SDHC_PROCTL_CDSS MAKE_REG_MASK(0x1,7) //((uint32_t)0x00000080) // Card Detect Signal Selection +#define SDHC_PROCTL_CDTL MAKE_REG_MASK(0x1,6) //((uint32_t)0x00000040) // Card Detect Test Level +#define SDHC_PROCTL_EMODE(n) MAKE_REG_SET(n,0x3,4) //(uint32_t)(((n) & 0x3)<<4) // Endian Mode +#define SDHC_PROCTL_EMODE_MASK MAKE_REG_MASK(0x3,4) //(uint32_t)((0x3)<<4) // Endian Mode +#define SDHC_PROCTL_D3CD MAKE_REG_MASK(0x1,3) //((uint32_t)0x00000008) // DAT3 As Card Detection Pin +#define SDHC_PROCTL_DTW(n) MAKE_REG_SET(n,0x3,1) //(uint32_t)(((n) & 0x3)<<1) // Data Transfer Width, 0=1bit, 1=4bit, 2=8bit +#define SDHC_PROCTL_DTW_MASK MAKE_REG_MASK(0x3,1) //((uint32_t)0x00000006) +#define SDHC_PROCTL_LCTL MAKE_REG_MASK(0x1,0) //((uint32_t)0x00000001) // LED Control + +#define SDHC_SYSCTL_RSTT MAKE_REG_MASK(0x1,28) // +#define SDHC_SYSCTL_INITA MAKE_REG_MASK(0x1,27) //((uint32_t)0x08000000) // Initialization Active +#define SDHC_SYSCTL_RSTD MAKE_REG_MASK(0x1,26) //((uint32_t)0x04000000) // Software Reset For DAT Line +#define SDHC_SYSCTL_RSTC MAKE_REG_MASK(0x1,25) //((uint32_t)0x02000000) // Software Reset For CMD Line +#define SDHC_SYSCTL_RSTA MAKE_REG_MASK(0x1,24) //((uint32_t)0x01000000) // Software Reset For ALL +#define SDHC_SYSCTL_DTOCV(n) MAKE_REG_SET(n,0xF,16) //(uint32_t)(((n) & 0xF)<<16) // Data Timeout Counter Value +#define SDHC_SYSCTL_DTOCV_MASK MAKE_REG_MASK(0xF,16) //((uint32_t)0x000F0000) +#define SDHC_SYSCTL_SDCLKFS(n) MAKE_REG_SET(n,0xFF,8) //(uint32_t)(((n) & 0xFF)<<8) // SDCLK Frequency Select +#define SDHC_SYSCTL_SDCLKFS_MASK MAKE_REG_MASK(0xFF,8) //((uint32_t)0x0000FF00) +#define SDHC_SYSCTL_DVS(n) MAKE_REG_SET(n,0xF,4) //(uint32_t)(((n) & 0xF)<<4) // Divisor +#define SDHC_SYSCTL_DVS_MASK MAKE_REG_MASK(0xF,4) //((uint32_t)0x000000F0) + +#define SDHC_SYSCTL_SDCLKEN ((uint32_t)0x00000008) // SD Clock Enable +#define SDHC_SYSCTL_PEREN ((uint32_t)0x00000004) // Peripheral Clock Enable +#define SDHC_SYSCTL_HCKEN ((uint32_t)0x00000002) // System Clock Enable +#define SDHC_SYSCTL_IPGEN ((uint32_t)0x00000001) // IPG Clock Enable + +#define SDHC_IRQSTAT_DMAE MAKE_REG_MASK(0x1,28) //((uint32_t)0x10000000) // DMA Error +#define SDHC_IRQSTAT_TNE MAKE_REG_MASK(0x1,26) // +#define SDHC_IRQSTAT_AC12E MAKE_REG_MASK(0x1,24) //((uint32_t)0x01000000) // Auto CMD12 Error +#define SDHC_IRQSTAT_DEBE MAKE_REG_MASK(0x1,22) //((uint32_t)0x00400000) // Data End Bit Error +#define SDHC_IRQSTAT_DCE MAKE_REG_MASK(0x1,21) //((uint32_t)0x00200000) // Data CRC Error +#define SDHC_IRQSTAT_DTOE MAKE_REG_MASK(0x1,20) //((uint32_t)0x00100000) // Data Timeout Error +#define SDHC_IRQSTAT_CIE MAKE_REG_MASK(0x1,19) //((uint32_t)0x00080000) // Command Index Error +#define SDHC_IRQSTAT_CEBE MAKE_REG_MASK(0x1,18) //((uint32_t)0x00040000) // Command End Bit Error +#define SDHC_IRQSTAT_CCE MAKE_REG_MASK(0x1,17) //((uint32_t)0x00020000) // Command CRC Error +#define SDHC_IRQSTAT_CTOE MAKE_REG_MASK(0x1,16) //((uint32_t)0x00010000) // Command Timeout Error +#define SDHC_IRQSTAT_TP MAKE_REG_MASK(0x1,14) // +#define SDHC_IRQSTAT_RTE MAKE_REG_MASK(0x1,12) // +#define SDHC_IRQSTAT_CINT MAKE_REG_MASK(0x1,8) //((uint32_t)0x00000100) // Card Interrupt +#define SDHC_IRQSTAT_CRM MAKE_REG_MASK(0x1,7) //((uint32_t)0x00000080) // Card Removal +#define SDHC_IRQSTAT_CINS MAKE_REG_MASK(0x1,6) //((uint32_t)0x00000040) // Card Insertion +#define SDHC_IRQSTAT_BRR MAKE_REG_MASK(0x1,5) //((uint32_t)0x00000020) // Buffer Read Ready +#define SDHC_IRQSTAT_BWR MAKE_REG_MASK(0x1,4) //((uint32_t)0x00000010) // Buffer Write Ready +#define SDHC_IRQSTAT_DINT MAKE_REG_MASK(0x1,3) //((uint32_t)0x00000008) // DMA Interrupt +#define SDHC_IRQSTAT_BGE MAKE_REG_MASK(0x1,2) //((uint32_t)0x00000004) // Block Gap Event +#define SDHC_IRQSTAT_TC MAKE_REG_MASK(0x1,1) //((uint32_t)0x00000002) // Transfer Complete +#define SDHC_IRQSTAT_CC MAKE_REG_MASK(0x1,0) //((uint32_t)0x00000001) // Command Complete + +#define SDHC_IRQSTATEN_DMAESEN MAKE_REG_MASK(0x1,28) //((uint32_t)0x10000000) // DMA Error Status Enable +#define SDHC_IRQSTATEN_TNESEN MAKE_REG_MASK(0x1,26) // +#define SDHC_IRQSTATEN_AC12ESEN MAKE_REG_MASK(0x1,24) //((uint32_t)0x01000000) // Auto CMD12 Error Status Enable +#define SDHC_IRQSTATEN_DEBESEN MAKE_REG_MASK(0x1,22) //((uint32_t)0x00400000) // Data End Bit Error Status Enable +#define SDHC_IRQSTATEN_DCESEN MAKE_REG_MASK(0x1,21) //((uint32_t)0x00200000) // Data CRC Error Status Enable +#define SDHC_IRQSTATEN_DTOESEN MAKE_REG_MASK(0x1,20) //((uint32_t)0x00100000) // Data Timeout Error Status Enable +#define SDHC_IRQSTATEN_CIESEN MAKE_REG_MASK(0x1,19) //((uint32_t)0x00080000) // Command Index Error Status Enable +#define SDHC_IRQSTATEN_CEBESEN MAKE_REG_MASK(0x1,18) //((uint32_t)0x00040000) // Command End Bit Error Status Enable +#define SDHC_IRQSTATEN_CCESEN MAKE_REG_MASK(0x1,17) //((uint32_t)0x00020000) // Command CRC Error Status Enable +#define SDHC_IRQSTATEN_CTOESEN MAKE_REG_MASK(0x1,16) //((uint32_t)0x00010000) // Command Timeout Error Status Enable +#define SDHC_IRQSTATEN_TPSEN MAKE_REG_MASK(0x1,14) // +#define SDHC_IRQSTATEN_RTESEN MAKE_REG_MASK(0x1,12) // +#define SDHC_IRQSTATEN_CINTSEN MAKE_REG_MASK(0x1,8) //((uint32_t)0x00000100) // Card Interrupt Status Enable +#define SDHC_IRQSTATEN_CRMSEN MAKE_REG_MASK(0x1,7) //((uint32_t)0x00000080) // Card Removal Status Enable +#define SDHC_IRQSTATEN_CINSEN MAKE_REG_MASK(0x1,6) //((uint32_t)0x00000040) // Card Insertion Status Enable +#define SDHC_IRQSTATEN_BRRSEN MAKE_REG_MASK(0x1,5) //((uint32_t)0x00000020) // Buffer Read Ready Status Enable +#define SDHC_IRQSTATEN_BWRSEN MAKE_REG_MASK(0x1,4) //((uint32_t)0x00000010) // Buffer Write Ready Status Enable +#define SDHC_IRQSTATEN_DINTSEN MAKE_REG_MASK(0x1,3) //((uint32_t)0x00000008) // DMA Interrupt Status Enable +#define SDHC_IRQSTATEN_BGESEN MAKE_REG_MASK(0x1,2) //((uint32_t)0x00000004) // Block Gap Event Status Enable +#define SDHC_IRQSTATEN_TCSEN MAKE_REG_MASK(0x1,1) //((uint32_t)0x00000002) // Transfer Complete Status Enable +#define SDHC_IRQSTATEN_CCSEN MAKE_REG_MASK(0x1,0) //((uint32_t)0x00000001) // Command Complete Status Enable + +#define SDHC_IRQSIGEN_DMAEIEN MAKE_REG_MASK(0x1,28) //((uint32_t)0x10000000) // DMA Error Interrupt Enable +#define SDHC_IRQSIGEN_TNEIEN MAKE_REG_MASK(0x1,26) // +#define SDHC_IRQSIGEN_AC12EIEN MAKE_REG_MASK(0x1,24) //((uint32_t)0x01000000) // Auto CMD12 Error Interrupt Enable +#define SDHC_IRQSIGEN_DEBEIEN MAKE_REG_MASK(0x1,22) //((uint32_t)0x00400000) // Data End Bit Error Interrupt Enable +#define SDHC_IRQSIGEN_DCEIEN MAKE_REG_MASK(0x1,21) //((uint32_t)0x00200000) // Data CRC Error Interrupt Enable +#define SDHC_IRQSIGEN_DTOEIEN MAKE_REG_MASK(0x1,20) //((uint32_t)0x00100000) // Data Timeout Error Interrupt Enable +#define SDHC_IRQSIGEN_CIEIEN MAKE_REG_MASK(0x1,19) //((uint32_t)0x00080000) // Command Index Error Interrupt Enable +#define SDHC_IRQSIGEN_CEBEIEN MAKE_REG_MASK(0x1,18) //((uint32_t)0x00040000) // Command End Bit Error Interrupt Enable +#define SDHC_IRQSIGEN_CCEIEN MAKE_REG_MASK(0x1,17) //((uint32_t)0x00020000) // Command CRC Error Interrupt Enable +#define SDHC_IRQSIGEN_CTOEIEN MAKE_REG_MASK(0x1,16) //((uint32_t)0x00010000) // Command Timeout Error Interrupt Enable +#define SDHC_IRQSIGEN_TPIEN MAKE_REG_MASK(0x1,14) // +#define SDHC_IRQSIGEN_RTEIEN MAKE_REG_MASK(0x1,12) // +#define SDHC_IRQSIGEN_CINTIEN MAKE_REG_MASK(0x1,8) //((uint32_t)0x00000100) // Card Interrupt Interrupt Enable +#define SDHC_IRQSIGEN_CRMIEN MAKE_REG_MASK(0x1,7) //((uint32_t)0x00000080) // Card Removal Interrupt Enable +#define SDHC_IRQSIGEN_CINSIEN MAKE_REG_MASK(0x1,6) //((uint32_t)0x00000040) // Card Insertion Interrupt Enable +#define SDHC_IRQSIGEN_BRRIEN MAKE_REG_MASK(0x1,5) //((uint32_t)0x00000020) // Buffer Read Ready Interrupt Enable +#define SDHC_IRQSIGEN_BWRIEN MAKE_REG_MASK(0x1,4) //((uint32_t)0x00000010) // Buffer Write Ready Interrupt Enable +#define SDHC_IRQSIGEN_DINTIEN MAKE_REG_MASK(0x1,3) //((uint32_t)0x00000008) // DMA Interrupt Interrupt Enable +#define SDHC_IRQSIGEN_BGEIEN MAKE_REG_MASK(0x1,2) //((uint32_t)0x00000004) // Block Gap Event Interrupt Enable +#define SDHC_IRQSIGEN_TCIEN MAKE_REG_MASK(0x1,1) //((uint32_t)0x00000002) // Transfer Complete Interrupt Enable +#define SDHC_IRQSIGEN_CCIEN MAKE_REG_MASK(0x1,0) //((uint32_t)0x00000001) // Command Complete Interrupt Enable + +#define SDHC_AC12ERR_SMPLCLK_SEL MAKE_REG_MASK(0x1,23) // +#define SDHC_AC12ERR_EXEC_TUNING MAKE_REG_MASK(0x1,22) // +#define SDHC_AC12ERR_CNIBAC12E MAKE_REG_MASK(0x1,7) //((uint32_t)0x00000080) // Command Not Issued By Auto CMD12 Error +#define SDHC_AC12ERR_AC12IE MAKE_REG_MASK(0x1,4) //((uint32_t)0x00000010) // Auto CMD12 Index Error +#define SDHC_AC12ERR_AC12CE MAKE_REG_MASK(0x1,3) //((uint32_t)0x00000008) // Auto CMD12 CRC Error +#define SDHC_AC12ERR_AC12EBE MAKE_REG_MASK(0x1,2) //((uint32_t)0x00000004) // Auto CMD12 End Bit Error +#define SDHC_AC12ERR_AC12TOE MAKE_REG_MASK(0x1,1) //((uint32_t)0x00000002) // Auto CMD12 Timeout Error +#define SDHC_AC12ERR_AC12NE MAKE_REG_MASK(0x1,0) //((uint32_t)0x00000001) // Auto CMD12 Not Executed + +#define SDHC_HTCAPBLT_VS18 MAKE_REG_MASK(0x1,26) // +#define SDHC_HTCAPBLT_VS30 MAKE_REG_MASK(0x1,25) // +#define SDHC_HTCAPBLT_VS33 MAKE_REG_MASK(0x1,24) // +#define SDHC_HTCAPBLT_SRS MAKE_REG_MASK(0x1,23) // +#define SDHC_HTCAPBLT_DMAS MAKE_REG_MASK(0x1,22) // +#define SDHC_HTCAPBLT_HSS MAKE_REG_MASK(0x1,21) // +#define SDHC_HTCAPBLT_ADMAS MAKE_REG_MASK(0x1,20) // +#define SDHC_HTCAPBLT_MBL_VAL MAKE_REG_GET((USDHC1_HOST_CTRL_CAP),0x7,16) // +#define SDHC_HTCAPBLT_RETUN_MODE MAKE_REG_GET((USDHC1_HOST_CTRL_CAP),0x3,14) // +#define SDHC_HTCAPBLT_TUNE_SDR50 MAKE_REG_MASK(0x1,13) // +#define SDHC_HTCAPBLT_TIME_RETUN(n) MAKE_REG_SET(n,0xF,8) // + +#define SDHC_WML_WR_BRSTLEN_MASK MAKE_REG_MASK(0x1F,24) // +#define SDHC_WML_RD_BRSTLEN_MASK MAKE_REG_MASK(0x1F,8) // +#define SDHC_WML_WR_WML_MASK MAKE_REG_MASK(0xFF,16) // +#define SDHC_WML_RD_WML_MASK MAKE_REG_MASK(0xFF,0) // +#define SDHC_WML_WR_BRSTLEN(n) MAKE_REG_SET(n,0x1F,24) //(uint32_t)(((n) & 0x7F)<<16) // Write Burst Len +#define SDHC_WML_RD_BRSTLEN(n) MAKE_REG_SET(n,0x1F,8) //(uint32_t)(((n) & 0x7F)<<0) // Read Burst Len +#define SDHC_WML_WR_WML(n) MAKE_REG_SET(n,0xFF,16) //(uint32_t)(((n) & 0x7F)<<16) // Write Watermark Level +#define SDHC_WML_RD_WML(n) MAKE_REG_SET(n,0xFF,0) //(uint32_t)(((n) & 0x7F)<<0) // Read Watermark Level +#define SDHC_WML_WRWML(n) MAKE_REG_SET(n,0xFF,16) //(uint32_t)(((n) & 0x7F)<<16) // Write Watermark Level +#define SDHC_WML_RDWML(n) MAKE_REG_SET(n,0xFF,0) //(uint32_t)(((n) & 0x7F)<<0) // Read Watermark Level + +// Teensy 4.0 only +#define SDHC_MIX_CTRL_DMAEN MAKE_REG_MASK(0x1,0) // +#define SDHC_MIX_CTRL_BCEN MAKE_REG_MASK(0x1,1) // +#define SDHC_MIX_CTRL_AC12EN MAKE_REG_MASK(0x1,2) // +#define SDHC_MIX_CTRL_DDR_EN MAKE_REG_MASK(0x1,3) // +#define SDHC_MIX_CTRL_DTDSEL MAKE_REG_MASK(0x1,4) // +#define SDHC_MIX_CTRL_MSBSEL MAKE_REG_MASK(0x1,5) // +#define SDHC_MIX_CTRL_NIBBLE_POS MAKE_REG_MASK(0x1,6) // +#define SDHC_MIX_CTRL_AC23EN MAKE_REG_MASK(0x1,7) // + +#define SDHC_FEVT_CINT MAKE_REG_MASK(0x1,31) //((uint32_t)0x80000000) // Force Event Card Interrupt +#define SDHC_FEVT_DMAE MAKE_REG_MASK(0x1,28) //((uint32_t)0x10000000) // Force Event DMA Error +#define SDHC_FEVT_AC12E MAKE_REG_MASK(0x1,24) //((uint32_t)0x01000000) // Force Event Auto CMD12 Error +#define SDHC_FEVT_DEBE MAKE_REG_MASK(0x1,22) //((uint32_t)0x00400000) // Force Event Data End Bit Error +#define SDHC_FEVT_DCE MAKE_REG_MASK(0x1,21) //((uint32_t)0x00200000) // Force Event Data CRC Error +#define SDHC_FEVT_DTOE MAKE_REG_MASK(0x1,20) //((uint32_t)0x00100000) // Force Event Data Timeout Error +#define SDHC_FEVT_CIE MAKE_REG_MASK(0x1,19) //((uint32_t)0x00080000) // Force Event Command Index Error +#define SDHC_FEVT_CEBE MAKE_REG_MASK(0x1,18) //((uint32_t)0x00040000) // Force Event Command End Bit Error +#define SDHC_FEVT_CCE MAKE_REG_MASK(0x1,17) //((uint32_t)0x00020000) // Force Event Command CRC Error +#define SDHC_FEVT_CTOE MAKE_REG_MASK(0x1,16) //((uint32_t)0x00010000) // Force Event Command Timeout Error +#define SDHC_FEVT_CNIBAC12E MAKE_REG_MASK(0x1,7) //((uint32_t)0x00000080) // Force Event Command Not Executed By Auto Command 12 Error +#define SDHC_FEVT_AC12IE MAKE_REG_MASK(0x1,4) //((uint32_t)0x00000010) // Force Event Auto Command 12 Index Error +#define SDHC_FEVT_AC12EBE MAKE_REG_MASK(0x1,3) //((uint32_t)0x00000008) // Force Event Auto Command 12 End Bit Error +#define SDHC_FEVT_AC12CE MAKE_REG_MASK(0x1,2) //((uint32_t)0x00000004) // Force Event Auto Command 12 CRC Error +#define SDHC_FEVT_AC12TOE MAKE_REG_MASK(0x1,1) //((uint32_t)0x00000002) // Force Event Auto Command 12 Time Out Error +#define SDHC_FEVT_AC12NE MAKE_REG_MASK(0x1,0) //((uint32_t)0x00000001) // Force Event Auto Command 12 Not Executed + +#define SDHC_ADMAES_ADMADCE MAKE_REG_MASK(0x1,3) //((uint32_t)0x00000008) +#define SDHC_ADMAES_ADMALME MAKE_REG_MASK(0x1,2) //((uint32_t)0x00000004) +#define SDHC_ADMAES_ADMAES_MASK MAKE_REG_MASK(0x3,0) //((uint32_t)0x00000003) + +#define SDHC_MMCBOOT_BOOTBLKCNT(n) MAKE_REG_MASK(0xFF,16) //(uint32_t)(((n) & 0xFFF)<<16) // stop at block gap value of automatic mode +#define SDHC_MMCBOOT_AUTOSABGEN MAKE_REG_MASK(0x1,7) //((uint32_t)0x00000080) // enable auto stop at block gap function +#define SDHC_MMCBOOT_BOOTEN MAKE_REG_MASK(0x1,6) //((uint32_t)0x00000040) // Boot Mode Enable +#define SDHC_MMCBOOT_BOOTMODE MAKE_REG_MASK(0x1,5) //((uint32_t)0x00000020) // Boot Mode Select +#define SDHC_MMCBOOT_BOOTACK MAKE_REG_MASK(0x1,4) //((uint32_t)0x00000010) // Boot Ack Mode Select +#define SDHC_MMCBOOT_DTOCVACK(n) MAKE_REG_MASK(0xF,0) //(uint32_t)(((n) & 0xF)<<0) // Boot ACK Time Out Counter Value +//#define SDHC_HOSTVER (*(volatile uint32_t*)0x400B10FC) // Host Controller Version + +#define CCM_ANALOG_PFD_528_PFD0_FRAC_MASK 0x3f +#define CCM_ANALOG_PFD_528_PFD0_FRAC(n) ((n) & CCM_ANALOG_PFD_528_PFD0_FRAC_MASK) +#define CCM_ANALOG_PFD_528_PFD1_FRAC_MASK (0x3f<<8) +#define CCM_ANALOG_PFD_528_PFD1_FRAC(n) (((n)<<8) & CCM_ANALOG_PFD_528_PFD1_FRAC_MASK) +#define CCM_ANALOG_PFD_528_PFD2_FRAC_MASK (0x3f<<16) +#define CCM_ANALOG_PFD_528_PFD2_FRAC(n) (((n)<<16) & CCM_ANALOG_PFD_528_PFD2_FRAC_MASK) +#define CCM_ANALOG_PFD_528_PFD3_FRAC_MASK ((0x3f<<24) +#define CCM_ANALOG_PFD_528_PFD3_FRAC(n) (((n)<<24) & CCM_ANALOG_PFD_528_PFD3_FRAC_MASK) + +#define SDHC_DSADDR (USDHC1_DS_ADDR ) // DMA System Address register +#define SDHC_BLKATTR (USDHC1_BLK_ATT) // Block Attributes register +#define SDHC_CMDARG (USDHC1_CMD_ARG) // Command Argument register +#define SDHC_XFERTYP (USDHC1_CMD_XFR_TYP) // Transfer Type register +#define SDHC_CMDRSP0 (USDHC1_CMD_RSP0) // Command Response 0 +#define SDHC_CMDRSP1 (USDHC1_CMD_RSP1) // Command Response 1 +#define SDHC_CMDRSP2 (USDHC1_CMD_RSP2) // Command Response 2 +#define SDHC_CMDRSP3 (USDHC1_CMD_RSP3) // Command Response 3 +#define SDHC_DATPORT (USDHC1_DATA_BUFF_ACC_PORT) // Buffer Data Port register +#define SDHC_PRSSTAT (USDHC1_PRES_STATE) // Present State register +#define SDHC_PROCTL (USDHC1_PROT_CTRL) // Protocol Control register +#define SDHC_SYSCTL (USDHC1_SYS_CTRL) // System Control register +#define SDHC_IRQSTAT (USDHC1_INT_STATUS) // Interrupt Status register +#define SDHC_IRQSTATEN (USDHC1_INT_STATUS_EN) // Interrupt Status Enable register +#define SDHC_IRQSIGEN (USDHC1_INT_SIGNAL_EN) // Interrupt Signal Enable register +#define SDHC_AC12ERR (USDHC1_AUTOCMD12_ERR_STATUS) // Auto CMD12 Error Status Register +#define SDHC_HTCAPBLT (USDHC1_HOST_CTRL_CAP) // Host Controller Capabilities +#define SDHC_WML (USDHC1_WTMK_LVL) // Watermark Level Register +#define SDHC_MIX_CTRL (USDHC1_MIX_CTRL) // Mixer Control +#define SDHC_FEVT (USDHC1_FORCE_EVENT) // Force Event register +#define SDHC_ADMAES (USDHC1_ADMA_ERR_STATUS) // ADMA Error Status register +#define SDHC_ADSADDR (USDHC1_ADMA_SYS_ADDR) // ADMA System Addressregister +#define SDHC_VENDOR (USDHC1_VEND_SPEC) // Vendor Specific register +#define SDHC_MMCBOOT (USDHC1_MMC_BOOT) // MMC Boot register +#define SDHC_VENDOR2 (USDHC2_VEND_SPEC2) // Vendor Specific2 register +// +#define IRQ_SDHC IRQ_SDHC1 + +#define SDHC_MAX_DVS (0xF + 1U) +#define SDHC_MAX_CLKFS (0xFF + 1U) +#define SDHC_PREV_DVS(x) ((x) -= 1U) +#define SDHC_PREV_CLKFS(x, y) ((x) >>= (y)) + +#define CCM_CSCDR1_USDHC1_CLK_PODF_MASK (0x7<<11) +#define CCM_CSCDR1_USDHC1_CLK_PODF(n) (((n)&0x7)<<11) + +#define IOMUXC_SW_PAD_CTL_PAD_SRE ((0x1<)<0) +#define IOMUXC_SW_PAD_CTL_PAD_PKE ((0x1)<<12) +#define IOMUXC_SW_PAD_CTL_PAD_PUE ((0x1)<<13) +#define IOMUXC_SW_PAD_CTL_PAD_HYS ((0x1)<<16) +#define IOMUXC_SW_PAD_CTL_PAD_SPEED(n) (((n)&0x3)<<6) +#define IOMUXC_SW_PAD_CTL_PAD_PUS(n) (((n)&0x3)<<14) +#define IOMUXC_SW_PAD_CTL_PAD_PUS_MASK ((0x3)<<14) +#define IOMUXC_SW_PAD_CTL_PAD_DSE(n) (((n)&0x7)<<3) +#define IOMUXC_SW_PAD_CTL_PAD_DSE_MASK ((0x7)<<3) +#endif // defined(__IMXRT1062__) +#endif // SdioTeensy_h \ No newline at end of file diff --git a/Firmware_V3/lib/SdFat/src/SdFat.h b/Firmware_V3/lib/SdFat/src/SdFat.h new file mode 100644 index 0000000..429682f --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SdFat.h @@ -0,0 +1,491 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef SdFat_h +#define SdFat_h +/** + * \file + * \brief main SdFs include file. + */ +#include "common/SysCall.h" +#include "SdCard/SdCard.h" +#include "ExFatLib/ExFatLib.h" +#include "FatLib/FatLib.h" +#include "FsLib/FsLib.h" +#if INCLUDE_SDIOS +#include "sdios.h" +#endif // INCLUDE_SDIOS +//------------------------------------------------------------------------------ +/** SdFat version for cpp use. */ +#define SD_FAT_VERSION 20006 +/** SdFat version as string. */ +#define SD_FAT_VERSION_STR "2.0.6" +//============================================================================== +/** + * \class SdBase + * \brief base SD file system template class. + */ +template +class SdBase : public Vol { + public: + //---------------------------------------------------------------------------- + /** Initialize SD card and file system. + * + * \param[in] csPin SD card chip select pin. + * \return true for success or false for failure. + */ + bool begin(SdCsPin_t csPin = SS) { +#ifdef BUILTIN_SDCARD + if (csPin == BUILTIN_SDCARD) { + return begin(SdioConfig(FIFO_SDIO)); + } +#endif // BUILTIN_SDCARD + return begin(SdSpiConfig(csPin, SHARED_SPI)); + } + //---------------------------------------------------------------------------- + /** Initialize SD card and file system. + * + * \param[in] csPin SD card chip select pin. + * \param[in] maxSck Maximum SCK frequency. + * \return true for success or false for failure. + */ + bool begin(SdCsPin_t csPin, uint32_t maxSck) { + return begin(SdSpiConfig(csPin, SHARED_SPI, maxSck)); + } + //---------------------------------------------------------------------------- + /** Initialize SD card and file system for SPI mode. + * + * \param[in] spiConfig SPI configuration. + * \return true for success or false for failure. + */ + bool begin(SdSpiConfig spiConfig) { + return cardBegin(spiConfig) && Vol::begin(m_card); + } + //--------------------------------------------------------------------------- + /** Initialize SD card and file system for SDIO mode. + * + * \param[in] sdioConfig SDIO configuration. + * \return true for success or false for failure. + */ + bool begin(SdioConfig sdioConfig) { + return cardBegin(sdioConfig) && Vol::begin(m_card); + } + //---------------------------------------------------------------------------- + /** \return Pointer to SD card object. */ + SdCard* card() {return m_card;} + //---------------------------------------------------------------------------- + /** Initialize SD card in SPI mode. + * + * \param[in] spiConfig SPI configuration. + * \return true for success or false for failure. + */ + bool cardBegin(SdSpiConfig spiConfig) { + m_card = m_cardFactory.newCard(spiConfig); + return m_card && !m_card->errorCode(); + } + //---------------------------------------------------------------------------- + /** Initialize SD card in SDIO mode. + * + * \param[in] sdioConfig SDIO configuration. + * \return true for success or false for failure. + */ + bool cardBegin(SdioConfig sdioConfig) { + m_card = m_cardFactory.newCard(sdioConfig); + return m_card && !m_card->errorCode(); + } + //---------------------------------------------------------------------------- + /** %Print error info and halt. + * + * \param[in] pr Print destination. + */ + void errorHalt(print_t* pr) { + if (sdErrorCode()) { + pr->print(F("SdError: 0X")); + pr->print(sdErrorCode(), HEX); + pr->print(F(",0X")); + pr->println(sdErrorData(), HEX); + } else if (!Vol::fatType()) { + pr->println(F("Check SD format.")); + } + SysCall::halt(); + } + //---------------------------------------------------------------------------- + /** %Print error info and halt. + * + * \param[in] pr Print destination. + * \param[in] msg Message to print. + */ + void errorHalt(print_t* pr, const char* msg) { + pr->print(F("error: ")); + pr->println(msg); + errorHalt(pr); + } + //---------------------------------------------------------------------------- + /** %Print msg and halt. + * + * \param[in] pr Print destination. + * \param[in] msg Message to print. + */ + void errorHalt(print_t* pr, const __FlashStringHelper* msg) { + pr->print(F("error: ")); + pr->println(msg); + errorHalt(pr); + } + //---------------------------------------------------------------------------- + /** %Print error info and halt. + * + * \param[in] pr Print destination. + */ + void initErrorHalt(print_t* pr) { + initErrorPrint(pr); + SysCall::halt(); + } + //---------------------------------------------------------------------------- + /** %Print error info and halt. + * + * \param[in] pr Print destination. + * \param[in] msg Message to print. + */ + void initErrorHalt(print_t* pr, const char* msg) { + pr->println(msg); + initErrorHalt(pr); + } + //---------------------------------------------------------------------------- + /** %Print error info and halt. + * + * \param[in] pr Print destination. + * \param[in] msg Message to print. + */ + void initErrorHalt(Print* pr, const __FlashStringHelper* msg) { + pr->println(msg); + initErrorHalt(pr); + } + //---------------------------------------------------------------------------- + /** Print error details after begin() fails. + * + * \param[in] pr Print destination. + */ + void initErrorPrint(Print* pr) { + pr->println(F("begin() failed")); + if (sdErrorCode()) { + pr->println(F("Do not reformat the SD.")); + if (sdErrorCode() == SD_CARD_ERROR_CMD0) { + pr->println(F("No card, wrong chip select pin, or wiring error?")); + } + } + errorPrint(pr); + } + //---------------------------------------------------------------------------- + /** %Print volume FAT/exFAT type. + * + * \param[in] pr Print destination. + */ + void printFatType(print_t* pr) { + if (Vol::fatType() == FAT_TYPE_EXFAT) { + pr->print(F("exFAT")); + } else { + pr->print(F("FAT")); + pr->print(Vol::fatType()); + } + } + //---------------------------------------------------------------------------- + /** %Print SD errorCode and errorData. + * + * \param[in] pr Print destination. + */ + void errorPrint(print_t* pr) { + if (sdErrorCode()) { + pr->print(F("SdError: 0X")); + pr->print(sdErrorCode(), HEX); + pr->print(F(",0X")); + pr->println(sdErrorData(), HEX); + } else if (!Vol::fatType()) { + pr->println(F("Check SD format.")); + } + } + //---------------------------------------------------------------------------- + /** %Print msg, any SD error code. + * + * \param[in] pr Print destination. + * \param[in] msg Message to print. + */ + void errorPrint(print_t* pr, char const* msg) { + pr->print(F("error: ")); + pr->println(msg); + errorPrint(pr); + } + + /** %Print msg, any SD error code. + * + * \param[in] pr Print destination. + * \param[in] msg Message to print. + */ + void errorPrint(Print* pr, const __FlashStringHelper* msg) { + pr->print(F("error: ")); + pr->println(msg); + errorPrint(pr); + } + //---------------------------------------------------------------------------- + /** %Print error info and return. + * + * \param[in] pr Print destination. + */ + void printSdError(print_t* pr) { + if (sdErrorCode()) { + if (sdErrorCode() == SD_CARD_ERROR_CMD0) { + pr->println(F("No card, wrong chip select pin, or wiring error?")); + } + pr->print(F("SD error: ")); + printSdErrorSymbol(pr, sdErrorCode()); + pr->print(F(" = 0x")); + pr->print(sdErrorCode(), HEX); + pr->print(F(",0x")); + pr->println(sdErrorData(), HEX); + } else if (!Vol::fatType()) { + pr->println(F("Check SD format.")); + } + } + //---------------------------------------------------------------------------- + /** \return SD card error code. */ + uint8_t sdErrorCode() { + if (m_card) { + return m_card->errorCode(); + } + return SD_CARD_ERROR_INVALID_CARD_CONFIG; + } + //---------------------------------------------------------------------------- + /** \return SD card error data. */ + uint8_t sdErrorData() {return m_card ? m_card->errorData() : 0;} + //---------------------------------------------------------------------------- + /** \return pointer to base volume */ + Vol* vol() {return reinterpret_cast(this);} + //---------------------------------------------------------------------------- + /** Initialize file system after call to cardBegin. + * + * \return true for success or false for failure. + */ + bool volumeBegin() { + return Vol::begin(m_card); + } +#if ENABLE_ARDUINO_SERIAL + /** Print error details after begin() fails. */ + void initErrorPrint() { + initErrorPrint(&Serial); + } + //---------------------------------------------------------------------------- + /** %Print msg to Serial and halt. + * + * \param[in] msg Message to print. + */ + void errorHalt(const __FlashStringHelper* msg) { + errorHalt(&Serial, msg); + } + //---------------------------------------------------------------------------- + /** %Print error info to Serial and halt. */ + void errorHalt() {errorHalt(&Serial);} + //---------------------------------------------------------------------------- + /** %Print error info and halt. + * + * \param[in] msg Message to print. + */ + void errorHalt(const char* msg) {errorHalt(&Serial, msg);} + //---------------------------------------------------------------------------- + /** %Print error info and halt. */ + void initErrorHalt() {initErrorHalt(&Serial);} + //---------------------------------------------------------------------------- + /** %Print msg, any SD error code. + * + * \param[in] msg Message to print. + */ + void errorPrint(const char* msg) {errorPrint(&Serial, msg);} + /** %Print msg, any SD error code. + * + * \param[in] msg Message to print. + */ + void errorPrint(const __FlashStringHelper* msg) {errorPrint(&Serial, msg);} + //---------------------------------------------------------------------------- + /** %Print error info and halt. + * + * \param[in] msg Message to print. + */ + void initErrorHalt(const char* msg) {initErrorHalt(&Serial, msg);} + //---------------------------------------------------------------------------- + /** %Print error info and halt. + * + * \param[in] msg Message to print. + */ + void initErrorHalt(const __FlashStringHelper* msg) { + initErrorHalt(&Serial, msg); + } +#endif // ENABLE_ARDUINO_SERIAL + //---------------------------------------------------------------------------- + private: + SdCard* m_card; + SdCardFactory m_cardFactory; +}; +//------------------------------------------------------------------------------ +/** + * \class SdFat32 + * \brief SD file system class for FAT volumes. + */ +class SdFat32 : public SdBase { + public: + /** Format a SD card FAT32/FAT16. + * + * \param[in] pr Optional Print information. + * \return true for success or false for failure. + */ + bool format(print_t* pr = nullptr) { + FatFormatter fmt; + uint8_t* cache = cacheClear(); + if (!cache) { + return false; + } + return fmt.format(card(), cache, pr); + } +}; +//------------------------------------------------------------------------------ +/** + * \class SdExFat + * \brief SD file system class for exFAT volumes. + */ +class SdExFat : public SdBase { + public: + /** Format a SD card exFAT. + * + * \param[in] pr Optional Print information. + * \return true for success or false for failure. + */ + bool format(print_t* pr = nullptr) { + ExFatFormatter fmt; + uint8_t* cache = cacheClear(); + if (!cache) { + return false; + } + return fmt.format(card(), cache, pr); + } +}; +//------------------------------------------------------------------------------ +/** + * \class SdFs + * \brief SD file system class for FAT16, FAT32, and exFAT volumes. + */ +class SdFs : public SdBase { + public: + /** Format a SD card FAT or exFAT. + * + * \param[in] pr Optional Print information. + * \return true for success or false for failure. + */ + bool format(print_t* pr = nullptr) { + static_assert(sizeof(m_volMem) >= 512, "m_volMem too small"); + uint32_t sectorCount = card()->sectorCount(); + if (sectorCount == 0) { + return false; + } + end(); + if (sectorCount > 67108864) { + ExFatFormatter fmt; + return fmt.format(card(), reinterpret_cast(m_volMem), pr); + } else { + FatFormatter fmt; + return fmt.format(card(), reinterpret_cast(m_volMem), pr); + } + } +}; +//------------------------------------------------------------------------------ +#if SDFAT_FILE_TYPE == 1 +/** Select type for SdFat. */ +typedef SdFat32 SdFat; +/** Select type for File. */ +#if !defined(__has_include) || !__has_include() +typedef File32 File; +#endif +/** Select type for SdBaseFile. */ +typedef FatFile SdBaseFile; +#elif SDFAT_FILE_TYPE == 2 +typedef SdExFat SdFat; +#if !defined(__has_include) || !__has_include() +typedef ExFile File; +#endif +typedef ExFatFile SdBaseFile; +#elif SDFAT_FILE_TYPE == 3 +typedef SdFs SdFat; +#if !defined(__has_include) || !__has_include() +typedef FsFile File; +#endif +typedef FsBaseFile SdBaseFile; +#else // SDFAT_FILE_TYPE +#error Invalid SDFAT_FILE_TYPE +#endif // SDFAT_FILE_TYPE +/** + * \class SdFile + * \brief FAT16/FAT32 file with Print. + */ +class SdFile : public PrintFile { + public: + SdFile() {} + /** Create an open SdFile. + * \param[in] path path for file. + * \param[in] oflag open flags. + */ + SdFile(const char* path, oflag_t oflag) { + open(path, oflag); + } + /** Set the date/time callback function + * + * \param[in] dateTime The user's call back function. The callback + * function is of the form: + * + * \code + * void dateTime(uint16_t* date, uint16_t* time) { + * uint16_t year; + * uint8_t month, day, hour, minute, second; + * + * // User gets date and time from GPS or real-time clock here + * + * // return date using FS_DATE macro to format fields + * *date = FS_DATE(year, month, day); + * + * // return time using FS_TIME macro to format fields + * *time = FS_TIME(hour, minute, second); + * } + * \endcode + * + * Sets the function that is called when a file is created or when + * a file's directory entry is modified by sync(). All timestamps, + * access, creation, and modify, are set when a file is created. + * sync() maintains the last access date and last modify date/time. + * + */ + static void dateTimeCallback( + void (*dateTime)(uint16_t* date, uint16_t* time)) { + FsDateTime::setCallback(dateTime); + } + /** Cancel the date/time callback function. */ + static void dateTimeCallbackCancel() { + FsDateTime::clearCallback(); + } +}; +#endif // SdFat_h diff --git a/Firmware_V3/lib/SdFat/src/SdFatConfig.h b/Firmware_V3/lib/SdFat/src/SdFatConfig.h new file mode 100644 index 0000000..3f7be8c --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SdFatConfig.h @@ -0,0 +1,362 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief configuration definitions + */ +#ifndef SdFatConfig_h +#define SdFatConfig_h +#include +#ifdef __AVR__ +#include +#endif // __AVR__ +/** For Debug - must be one */ +#define ENABLE_ARDUINO_FEATURES 1 +/** For Debug - must be one */ +#define ENABLE_ARDUINO_SERIAL 1 +/** For Debug - must be one */ +#define ENABLE_ARDUINO_STRING 1 +//------------------------------------------------------------------------------ +/** Set zero to disable mod for non-blocking write. */ +#define ENABLE_TEENSY_SDIO_MOD 0 +//------------------------------------------------------------------------------ +/** Set USE_BLOCK_DEVICE_INTERFACE nonzero to use generic block device */ +#define USE_BLOCK_DEVICE_INTERFACE 0 +//------------------------------------------------------------------------------ +#if ENABLE_ARDUINO_FEATURES +#include "Arduino.h" +#ifdef PLATFORM_ID +// Only defined if a Particle device. +#include "application.h" +#endif // PLATFORM_ID +#endif // ENABLE_ARDUINO_FEATURES +//------------------------------------------------------------------------------ +/** + * Set INCLUDE_SDIOS nonzero to include sdios.h in SdFat.h. + * sdios.h provides C++ style IO Streams. + */ +#define INCLUDE_SDIOS 0 +//------------------------------------------------------------------------------ +/** + * Set USE_FAT_FILE_FLAG_CONTIGUOUS nonzero to optimize access to + * contiguous files. + */ +#define USE_FAT_FILE_FLAG_CONTIGUOUS 1 +//------------------------------------------------------------------------------ +/** + * File types for SdFat, File, SdFile, SdBaseFile, fstream, + * ifstream, and ofstream. + * + * Set SDFAT_FILE_TYPE to: + * + * 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT. + */ +#if defined(__AVR__) && FLASHEND < 0X8000 +// 32K AVR boards. +#define SDFAT_FILE_TYPE 1 +#elif defined(__arm__) +// ARM boards usually have plenty of memory +#define SDFAT_FILE_TYPE 3 +#else // defined(__AVR__) && FLASHEND < 0X8000 +// All other boards. +#define SDFAT_FILE_TYPE 1 +#endif // defined(__AVR__) && FLASHEND < 0X8000 +//------------------------------------------------------------------------------ +/** + * Set ENABLE_DEDICATED_SPI non-zero to enable dedicated use of the SPI bus. + * Selecting dedicated SPI in SdSpiConfig() will produce better + * performance by using very large multi-block transfers to and + * from the SD card. + * + * Enabling dedicated SPI will cost some extra flash and RAM. + */ +#if defined(__AVR__) && FLASHEND < 0X8000 +// 32K AVR boards. +#define ENABLE_DEDICATED_SPI 1 +#else // defined(__AVR__) && FLASHEND < 0X8000 +// All other boards. +#define ENABLE_DEDICATED_SPI 1 +#endif // defined(__AVR__) && FLASHEND < 0X8000 +//------------------------------------------------------------------------------ +/** + * If the symbol SPI_DRIVER_SELECT is: + * + * 0 - An optimized custom SPI driver is used if it exists + * else the standard library driver is used. + * + * 1 - The standard library driver is always used. + * + * 2 - An external SPI driver of SoftSpiDriver template class is always used. + * + * 3 - An external SPI driver derived from SdSpiBaseClass is always used. + */ +#define SPI_DRIVER_SELECT 0 +/** + * If USE_SPI_ARRAY_TRANSFER is non-zero and the standard SPI library is + * use, the array transfer function, transfer(buf, size), will be used. + * This option will allocate up to a 512 byte temporary buffer for send. + * This may be faster for some boards. Do not use this with AVR boards. + */ +#define USE_SPI_ARRAY_TRANSFER 0 +//------------------------------------------------------------------------------ +/** + * SD_CHIP_SELECT_MODE defines how the functions + * void sdCsInit(SdCsPin_t pin) {pinMode(pin, OUTPUT);} + * and + * void sdCsWrite(SdCsPin_t pin, bool level) {digitalWrite(pin, level);} + * are defined. + * + * 0 - Internal definition is a strong symbol and can't be replaced. + * + * 1 - Internal definition is a weak symbol and can be replaced. + * + * 2 - No internal definition and must be defined in the application. + */ +#define SD_CHIP_SELECT_MODE 0 +/** Type for card chip select pin. */ +typedef uint8_t SdCsPin_t; +//------------------------------------------------------------------------------ +/** + * SD maximum initialization clock rate. + */ +#define SD_MAX_INIT_RATE_KHZ 400 +//------------------------------------------------------------------------------ +/** + * Set USE_LONG_FILE_NAMES nonzero to use long file names (LFN) in FAT16/FAT32. + * exFAT always uses long file names. + * + * Long File Name are limited to a maximum length of 255 characters. + * + * This implementation allows 7-bit characters in the range + * 0X20 to 0X7E except the following characters are not allowed: + * + * < (less than) + * > (greater than) + * : (colon) + * " (double quote) + * / (forward slash) + * \ (backslash) + * | (vertical bar or pipe) + * ? (question mark) + * * (asterisk) + * + */ +#define USE_LONG_FILE_NAMES 1 +//------------------------------------------------------------------------------ +/** + * Set the default file time stamp when a RTC callback is not used. + * A valid date and time is required by the FAT/exFAT standard. + * + * The default below is YYYY-01-01 00:00:00 midnight where YYYY is + * the compile year from the __DATE__ macro. This is easy to recognize + * as a placeholder for a correct date/time. + * + * The full compile date is: + * FS_DATE(compileYear(), compileMonth(), compileDay()) + * + * The full compile time is: + * FS_TIME(compileHour(), compileMinute(), compileSecond()) + */ +#define FS_DEFAULT_DATE FS_DATE(compileYear(), 1, 1) +/** 00:00:00 midnight */ +#define FS_DEFAULT_TIME FS_TIME(0, 0, 0) +//------------------------------------------------------------------------------ +/** + * If CHECK_FLASH_PROGRAMMING is zero, overlap of single sector flash + * programming and other operations will be allowed for faster write + * performance. + * + * Some cards will not sleep in low power mode unless CHECK_FLASH_PROGRAMMING + * is non-zero. + */ +#define CHECK_FLASH_PROGRAMMING 0 +//------------------------------------------------------------------------------ +/** + * Set MAINTAIN_FREE_CLUSTER_COUNT nonzero to keep the count of free clusters + * updated. This will increase the speed of the freeClusterCount() call + * after the first call. Extra flash will be required. + */ +#define MAINTAIN_FREE_CLUSTER_COUNT 0 +//------------------------------------------------------------------------------ +/** + * To enable SD card CRC checking for SPI, set USE_SD_CRC nonzero. + * + * Set USE_SD_CRC to 1 to use a smaller CRC-CCITT function. This function + * is slower for AVR but may be fast for ARM and other processors. + * + * Set USE_SD_CRC to 2 to used a larger table driven CRC-CCITT function. This + * function is faster for AVR but may be slower for ARM and other processors. + */ +#define USE_SD_CRC 0 +//------------------------------------------------------------------------------ +/** If the symbol USE_FCNTL_H is nonzero, open flags for access modes O_RDONLY, + * O_WRONLY, O_RDWR and the open modifiers O_APPEND, O_CREAT, O_EXCL, O_SYNC + * will be defined by including the system file fcntl.h. + */ +#if defined(__AVR__) +// AVR fcntl.h does not define open flags. +#define USE_FCNTL_H 0 +#elif defined(PLATFORM_ID) +// Particle boards - use fcntl.h. +#define USE_FCNTL_H 1 +#elif defined(__arm__) +// ARM gcc defines open flags. +#define USE_FCNTL_H 1 +#elif defined(ESP32) +#define USE_FCNTL_H 1 +#else // defined(__AVR__) +#define USE_FCNTL_H 0 +#endif // defined(__AVR__) +//------------------------------------------------------------------------------ +/** + * Handle Watchdog Timer for WiFi modules. + * + * Yield will be called before accessing the SPI bus if it has been more + * than WDT_YIELD_TIME_MILLIS milliseconds since the last yield call by SdFat. + */ +#if defined(PLATFORM_ID) || defined(ESP8266) +// If Particle device or ESP8266 call yield. +#define WDT_YIELD_TIME_MILLIS 100 +#else // defined(PLATFORM_ID) || defined(ESP8266) +#define WDT_YIELD_TIME_MILLIS 0 +#endif // defined(PLATFORM_ID) || defined(ESP8266) +//------------------------------------------------------------------------------ +/** + * Set FAT12_SUPPORT nonzero to enable use if FAT12 volumes. + * FAT12 has not been well tested and requires additional flash. + */ +#define FAT12_SUPPORT 0 +//------------------------------------------------------------------------------ +/** + * Set DESTRUCTOR_CLOSES_FILE nonzero to close a file in its destructor. + * + * Causes use of lots of heap in ARM. + */ +#define DESTRUCTOR_CLOSES_FILE 0 +//------------------------------------------------------------------------------ +/** + * Call flush for endl if ENDL_CALLS_FLUSH is nonzero + * + * The standard for iostreams is to call flush. This is very costly for + * SdFat. Each call to flush causes 2048 bytes of I/O to the SD. + * + * SdFat has a single 512 byte buffer for SD I/O so it must write the current + * data sector to the SD, read the directory sector from the SD, update the + * directory entry, write the directory sector to the SD and read the data + * sector back into the buffer. + * + * The SD flash memory controller is not designed for this many rewrites + * so performance may be reduced by more than a factor of 100. + * + * If ENDL_CALLS_FLUSH is zero, you must call flush and/or close to force + * all data to be written to the SD. + */ +#define ENDL_CALLS_FLUSH 0 +//------------------------------------------------------------------------------ +/** + * Set USE_SIMPLE_LITTLE_ENDIAN nonzero for little endian processors + * with no memory alignment restrictions. + */ +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ && !defined(__SAMD21G18A__)\ + && !defined(__MKL26Z64__) && !defined(ESP8266) +#define USE_SIMPLE_LITTLE_ENDIAN 1 +#else // __BYTE_ORDER_ +#define USE_SIMPLE_LITTLE_ENDIAN 0 +#endif // __BYTE_ORDER_ +//------------------------------------------------------------------------------ +/** + * Set USE_SEPARATE_FAT_CACHE nonzero to use a second 512 byte cache + * for FAT16/FAT32 table entries. This improves performance for large + * writes that are not a multiple of 512 bytes. + */ +#ifdef __arm__ +#define USE_SEPARATE_FAT_CACHE 1 +#else // __arm__ +#define USE_SEPARATE_FAT_CACHE 0 +#endif // __arm__ +//------------------------------------------------------------------------------ +/** + * Set USE_EXFAT_BITMAP_CACHE nonzero to use a second 512 byte cache + * for exFAT bitmap entries. This improves performance for large + * writes that are not a multiple of 512 bytes. + */ +#ifdef __arm__ +#define USE_EXFAT_BITMAP_CACHE 1 +#else // __arm__ +#define USE_EXFAT_BITMAP_CACHE 0 +#endif // __arm__ +//------------------------------------------------------------------------------ +/** + * Set USE_MULTI_SECTOR_IO nonzero to use multi-sector SD read/write. + * + * Don't use mult-sector read/write on small AVR boards. + */ +#if defined(RAMEND) && RAMEND < 3000 +#define USE_MULTI_SECTOR_IO 0 +#else // RAMEND +#define USE_MULTI_SECTOR_IO 1 +#endif // RAMEND +//------------------------------------------------------------------------------ +/** Enable SDIO driver if available. */ +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) +// Pseudo pin select for SDIO. +#ifndef BUILTIN_SDCARD +#define BUILTIN_SDCARD 254 +#endif // BUILTIN_SDCARD +// SPI for built-in card. +#ifndef SDCARD_SPI +#define SDCARD_SPI SPI1 +#define SDCARD_MISO_PIN 59 +#define SDCARD_MOSI_PIN 61 +#define SDCARD_SCK_PIN 60 +#define SDCARD_SS_PIN 62 +#endif // SDCARD_SPI +#define HAS_SDIO_CLASS 1 +#endif // defined(__MK64FX512__) || defined(__MK66FX1M0__) +#if defined(__IMXRT1062__) +#define HAS_SDIO_CLASS 1 +#endif // defined(__IMXRT1062__) +//------------------------------------------------------------------------------ +/** + * Determine the default SPI configuration. + */ +#if defined(ARDUINO_ARCH_APOLLO3)\ + || defined(__AVR__)\ + || defined(ESP8266) || defined(ESP32)\ + || defined(PLATFORM_ID)\ + || defined(ARDUINO_SAM_DUE)\ + || defined(__STM32F1__) || defined(__STM32F4__)\ + || (defined(CORE_TEENSY) && defined(__arm__)) +#define SD_HAS_CUSTOM_SPI 1 +#else // SD_HAS_CUSTOM_SPI +// Use standard SPI library. +#define SD_HAS_CUSTOM_SPI 0 +#endif // SD_HAS_CUSTOM_SPI +//------------------------------------------------------------------------------ +#ifndef HAS_SDIO_CLASS +/** Default is no SDIO. */ +#define HAS_SDIO_CLASS 0 +#endif // HAS_SDIO_CLASS +#endif // SdFatConfig_h diff --git a/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiArduinoDriver.h b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiArduinoDriver.h new file mode 100644 index 0000000..0fe088f --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiArduinoDriver.h @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief SpiDriver classes for Arduino compatible systems. + */ +#ifndef SdSpiArduinoDriver_h +#define SdSpiArduinoDriver_h +//============================================================================== +#if SPI_DRIVER_SELECT == 0 && SD_HAS_CUSTOM_SPI +#define SD_USE_CUSTOM_SPI +#endif // SPI_DRIVER_SELECT == 0 && SD_HAS_CUSTOM_SPI +/** + * \class SdSpiArduinoDriver + * \brief Optimized SPI class for access to SD and SDHC flash memory cards. + */ +class SdSpiArduinoDriver { + public: + /** Activate SPI hardware. */ + void activate(); + /** Initialize the SPI bus. + * + * \param[in] spiConfig SD card configuration. + */ + void begin(SdSpiConfig spiConfig); + /** Deactivate SPI hardware. */ + void deactivate(); + /** End use of SPI driver after begin() call. */ + void end(); + /** Receive a byte. + * + * \return The byte. + */ + uint8_t receive(); + /** Receive multiple bytes. + * + * \param[out] buf Buffer to receive the data. + * \param[in] count Number of bytes to receive. + * + * \return Zero for no error or nonzero error code. + */ + uint8_t receive(uint8_t* buf, size_t count); + /** Send a byte. + * + * \param[in] data Byte to send + */ + void send(uint8_t data); + /** Send multiple bytes. + * + * \param[in] buf Buffer for data to be sent. + * \param[in] count Number of bytes to send. + */ + void send(const uint8_t* buf, size_t count); + /** Save high speed SPISettings after SD initialization. + * + * \param[in] maxSck Maximum SCK frequency. + */ + void setSckSpeed(uint32_t maxSck) { + m_spiSettings = SPISettings(maxSck, MSBFIRST, SPI_MODE0); + } + + private: + SPIClass *m_spi; + SPISettings m_spiSettings; +}; +/** Typedef for use of SdSpiArduinoDriver */ +typedef SdSpiArduinoDriver SdSpiDriver; +//------------------------------------------------------------------------------ +#ifndef SD_USE_CUSTOM_SPI +#include "SdSpiLibDriver.h" +#elif defined(__AVR__) +#include "SdSpiAvr.h" +#endif // __AVR__ +#endif // SdSpiArduinoDriver_h diff --git a/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiArtemis.cpp b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiArtemis.cpp new file mode 100644 index 0000000..a6a47a4 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiArtemis.cpp @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "SdSpiDriver.h" +#if defined(SD_USE_CUSTOM_SPI) && defined(ARDUINO_ARCH_APOLLO3) +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::activate() { + m_spi->beginTransaction(m_spiSettings); +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) { + if (spiConfig.spiPort) { + m_spi = spiConfig.spiPort; + } else { + m_spi = &SPI; + } + m_spi->begin(); +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::deactivate() { + m_spi->endTransaction(); +} +//------------------------------------------------------------------------------ +uint8_t SdSpiArduinoDriver::receive() { + return m_spi->transfer(0XFF); +} +//------------------------------------------------------------------------------ +uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) { + memset(buf, 0XFF, count); + m_spi->transfer(buf, count); + return 0; +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::send(uint8_t data) { + m_spi->transfer(data); +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::send(const uint8_t* buf, size_t count) { + // If not a multiple of four. Command with CRC used six byte send. + while (count%4) { + send(*buf++); + count--; + } + // Convert byte array to 4 byte array. + uint32_t myArray[count/4]; // NOLINT + for (int x = 0; x < count/4; x++) { + myArray[x] = ((uint32_t)buf[(x * 4) + 3] << (8 * 3)) | + ((uint32_t)buf[(x * 4) + 2] << (8 * 2)) | + ((uint32_t)buf[(x * 4) + 1] << (8 * 1)) | + ((uint32_t)buf[(x * 4) + 0] << (8 * 0)); + } + m_spi->transfer(reinterpret_cast(myArray), count); +} +#endif // defined(SD_USE_CUSTOM_SPI) && defined(ARDUINO_ARCH_APOLLO3) diff --git a/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiAvr.h b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiAvr.h new file mode 100644 index 0000000..eefd3e1 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiAvr.h @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef SdSpiAvr_h +#define SdSpiAvr_h +// Use of in-line for AVR to save flash. +#define nop asm volatile ("nop\n\t") +//------------------------------------------------------------------------------ +inline void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) { + (void)spiConfig; + SPI.begin(); +} +//------------------------------------------------------------------------------ +inline void SdSpiArduinoDriver::activate() { + SPI.beginTransaction(m_spiSettings); +} +//------------------------------------------------------------------------------ +inline void SdSpiArduinoDriver::deactivate() { + SPI.endTransaction(); +} +//------------------------------------------------------------------------------ +inline uint8_t SdSpiArduinoDriver::receive() { + return SPI.transfer(0XFF); +} +//------------------------------------------------------------------------------ +inline uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) { + if (count == 0) { + return 0; + } + uint8_t* pr = buf; + SPDR = 0XFF; + while (--count > 0) { + while (!(SPSR & _BV(SPIF))) {} + uint8_t in = SPDR; + SPDR = 0XFF; + *pr++ = in; + // nops to optimize loop for 16MHz CPU 8 MHz SPI + nop; + nop; + } + while (!(SPSR & _BV(SPIF))) {} + *pr = SPDR; + return 0; +} +//------------------------------------------------------------------------------ +inline void SdSpiArduinoDriver::send(uint8_t data) { + SPI.transfer(data); +} +//------------------------------------------------------------------------------ +inline void SdSpiArduinoDriver::send(const uint8_t* buf , size_t count) { + if (count == 0) { + return; + } + SPDR = *buf++; + while (--count > 0) { + uint8_t b = *buf++; + while (!(SPSR & (1 << SPIF))) {} + SPDR = b; + // nops to optimize loop for 16MHz CPU 8 MHz SPI + nop; + nop; + } + while (!(SPSR & (1 << SPIF))) {} +} +#endif // SdSpiAvr_h diff --git a/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiBareUnoDriver.h b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiBareUnoDriver.h new file mode 100644 index 0000000..99ba6be --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiBareUnoDriver.h @@ -0,0 +1,200 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef SdSpiBareUnoDriver_h +#define SdSpiBareUnoDriver_h +/** + * \file + * \brief Driver to test with no Arduino includes. + */ + +#include +#include "../common/SysCall.h" +#define nop asm volatile ("nop\n\t") +#ifndef HIGH +#define HIGH 1 +#endif // HIGH +#ifndef LOW +#define LOW 0 +#endif // LOW +#ifndef INPUT +#define INPUT 0 +#endif // INPUT +#ifndef OUTPUT +#define OUTPUT 1 +#endif // OUTPUT + +inline uint8_t unoBit(uint8_t pin) { + return 1 << (pin < 8 ? pin : pin < 14 ? pin - 8 : pin - 14); +} +inline uint8_t unoDigitalRead(uint8_t pin) { + volatile uint8_t* reg = pin < 8 ? &PIND : pin < 14 ? &PINB : &PINC; + return *reg & unoBit(pin); +} +inline void unoDigitalWrite(uint8_t pin, uint8_t value) { + volatile uint8_t* port = pin < 8 ? &PORTD : pin < 14 ? &PORTB : &PORTC; + uint8_t bit = unoBit(pin); + cli(); + if (value) { + *port |= bit; + } else { + *port &= ~bit; + } + sei(); +} + +inline void unoPinMode(uint8_t pin, uint8_t mode) { + uint8_t bit = unoBit(pin); + volatile uint8_t* reg = pin < 8 ? &DDRD : pin < 14 ? &DDRB : &DDRC; + + cli(); + if (mode == OUTPUT) { + *reg |= bit; + } else { + *reg &= ~bit; + // handle INPUT pull-up + unoDigitalWrite(pin, mode != INPUT); + } + sei(); +} + +#define UNO_SS 10 +#define UNO_MOSI 11 +#define UNO_MISO 12 +#define UNO_SCK 13 +//------------------------------------------------------------------------------ +/** + * \class SdSpiDriverBareUno + * \brief Optimized SPI class for access to SD and SDHC flash memory cards. + */ +class SdSpiDriverBareUno { + public: + /** Activate SPI hardware. */ + void activate() {} + /** deactivate SPI driver. */ + void end() {} + /** Deactivate SPI hardware. */ + void deactivate() {} + /** Initialize the SPI bus. + * + * \param[in] spiConfig SD card configuration. + */ + void begin(SdSpiConfig spiConfig) { + m_csPin = spiConfig.csPin; + unoPinMode(m_csPin, OUTPUT); + unoDigitalWrite(m_csPin, HIGH); + unoDigitalWrite(UNO_SS, HIGH); + unoPinMode(UNO_SS, OUTPUT); + SPCR |= _BV(MSTR); + SPCR |= _BV(SPE); + SPSR = 0; + unoPinMode(UNO_SCK, OUTPUT); + unoPinMode(UNO_MOSI, OUTPUT); + } + /** Receive a byte. + * + * \return The byte. + */ + uint8_t receive() { + return transfer(0XFF); + } + /** Receive multiple bytes. + * + * \param[out] buf Buffer to receive the data. + * \param[in] count Number of bytes to receive. + * + * \return Zero for no error or nonzero error code. + */ + uint8_t receive(uint8_t* buf, size_t count) { + if (count == 0) { + return 0; + } + uint8_t* pr = buf; + SPDR = 0XFF; + while (--count > 0) { + while (!(SPSR & _BV(SPIF))) {} + uint8_t in = SPDR; + SPDR = 0XFF; + *pr++ = in; + // nops to optimize loop for 16MHz CPU 8 MHz SPI + nop; + nop; + } + while (!(SPSR & _BV(SPIF))) {} + *pr = SPDR; + return 0; + } + /** Send a byte. + * + * \param[in] data Byte to send + */ + void send(uint8_t data) { + transfer(data); + } + /** Send multiple bytes. + * + * \param[in] buf Buffer for data to be sent. + * \param[in] count Number of bytes to send. + */ + void send(const uint8_t* buf, size_t count) { + if (count == 0) { + return; + } + SPDR = *buf++; + while (--count > 0) { + uint8_t b = *buf++; + while (!(SPSR & (1 << SPIF))) {} + SPDR = b; + // nops to optimize loop for 16MHz CPU 8 MHz SPI + nop; + nop; + } + while (!(SPSR & (1 << SPIF))) {} + } + /** Set CS low. */ + void select() { + unoDigitalWrite(m_csPin, LOW); + } + /** Save high speed SPISettings after SD initialization. + * + * \param[in] spiConfig SPI options. + */ + void setSckSpeed(uint32_t maxSck) { + (void)maxSck; + SPSR |= 1 << SPI2X; + } + static uint8_t transfer(uint8_t data) { + SPDR = data; + while (!(SPSR & _BV(SPIF))) {} // wait + return SPDR; + } + /** Set CS high. */ + void unselect() { + unoDigitalWrite(m_csPin, HIGH); + } + + private: + SdCsPin_t m_csPin; +}; +#endif // SdSpiBareUnoDriver_h diff --git a/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiBaseClass.h b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiBaseClass.h new file mode 100644 index 0000000..1957327 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiBaseClass.h @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief Base class for external SPI driver. + */ +#ifndef SdSpiBaseClass_h +#define SdSpiBaseClass_h +/** + * \class SdSpiBaseClass + * \brief Base class for external SPI drivers + */ +class SdSpiBaseClass { + public: + /** Activate SPI hardware. */ + virtual void activate() {} + /** Initialize the SPI bus. + * + * \param[in] config SPI configuration. + */ + virtual void begin(SdSpiConfig config) = 0; + /** Deactivate SPI hardware. */ + virtual void deactivate() {} + /** Receive a byte. + * + * \return The byte. + */ + virtual uint8_t receive() = 0; + /** Receive multiple bytes. + * + * \param[out] buf Buffer to receive the data. + * \param[in] count Number of bytes to receive. + * + * \return Zero for no error or nonzero error code. + */ + virtual uint8_t receive(uint8_t* buf, size_t count) = 0; + /** Send a byte. + * + * \param[in] data Byte to send + */ + virtual void send(uint8_t data) = 0; + /** Send multiple bytes. + * + * \param[in] buf Buffer for data to be sent. + * \param[in] count Number of bytes to send. + */ + virtual void send(const uint8_t* buf, size_t count) = 0; + /** Save high speed SPISettings after SD initialization. + * + * \param[in] maxSck Maximum SCK frequency. + */ + virtual void setSckSpeed(uint32_t maxSck) {(void)maxSck;} +}; +#endif // SdSpiBaseClass_h diff --git a/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiChipSelect.cpp b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiChipSelect.cpp new file mode 100644 index 0000000..5b52632 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiChipSelect.cpp @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "SdSpiDriver.h" +#if ENABLE_ARDUINO_FEATURES +#if SD_CHIP_SELECT_MODE == 0 +//------------------------------------------------------------------------------ +void sdCsInit(SdCsPin_t pin) { + pinMode(pin, OUTPUT); +} +//------------------------------------------------------------------------------ +void sdCsWrite(SdCsPin_t pin, bool level) { + digitalWrite(pin, level); +} +#elif SD_CHIP_SELECT_MODE == 1 +//------------------------------------------------------------------------------ +__attribute__((weak)) +void sdCsInit(SdCsPin_t pin) { + pinMode(pin, OUTPUT); +} +//------------------------------------------------------------------------------ +__attribute__((weak)) +void sdCsWrite(SdCsPin_t pin, bool level) { + digitalWrite(pin, level); +} +#endif // SD_CHIP_SELECT_MODE == 0 +#endif // ENABLE_ARDUINO_FEATURES diff --git a/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiDriver.h b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiDriver.h new file mode 100644 index 0000000..95b5bf9 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiDriver.h @@ -0,0 +1,155 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief SpiDriver classes + */ +#ifndef SdSpiDriver_h +#define SdSpiDriver_h +#include "../common/SysCall.h" +/** + * Initialize SD chip select pin. + * + * \param[in] pin SD card chip select pin. + */ +void sdCsInit(SdCsPin_t pin); +/** + * Initialize SD chip select pin. + * + * \param[in] pin SD card chip select pin. + * \param[in] level SD card chip select level. + */ +void sdCsWrite(SdCsPin_t pin, bool level); +//------------------------------------------------------------------------------ +/** SPI bus is share with other devices. */ +const uint8_t SHARED_SPI = 0; +#if ENABLE_DEDICATED_SPI +/** The SD is the only device on the SPI bus. */ +const uint8_t DEDICATED_SPI = 1; +/** + * \param[in] opt option field of SdSpiConfig. + * \return true for shared SPI. + */ +inline bool spiOptionShared(uint8_t opt) {return !(opt & DEDICATED_SPI);} +#else // ENABLE_DEDICATED_SPI +/** + * \param[in] opt option field of SdSpiConfig. + * \return true for shared SPI. + */ +inline bool spiOptionShared(uint8_t opt) {(void)opt; return true;} +#endif // ENABLE_DEDICATED_SPI +//------------------------------------------------------------------------------ +/** SPISettings for SCK frequency in Hz. */ +#define SD_SCK_HZ(maxSpeed) (maxSpeed) +/** SPISettings for SCK frequency in MHz. */ +#define SD_SCK_MHZ(maxMhz) (1000000UL*(maxMhz)) +// SPI divisor constants - obsolete. +/** Set SCK to max rate. */ +#define SPI_FULL_SPEED SD_SCK_MHZ(50) +/** Set SCK rate to 16 MHz for Due */ +#define SPI_DIV3_SPEED SD_SCK_MHZ(16) +/** Set SCK rate to 4 MHz for AVR. */ +#define SPI_HALF_SPEED SD_SCK_MHZ(4) +/** Set SCK rate to 8 MHz for Due */ +#define SPI_DIV6_SPEED SD_SCK_MHZ(8) +/** Set SCK rate to 2 MHz for AVR. */ +#define SPI_QUARTER_SPEED SD_SCK_MHZ(2) +/** Set SCK rate to 1 MHz for AVR. */ +#define SPI_EIGHTH_SPEED SD_SCK_MHZ(1) +/** Set SCK rate to 500 kHz for AVR. */ +#define SPI_SIXTEENTH_SPEED SD_SCK_HZ(500000) +//------------------------------------------------------------------------------ +#if SPI_DRIVER_SELECT < 2 +#include "SPI.h" +/** Port type for Arduino SPI hardware driver. */ +typedef SPIClass SpiPort_t; +#elif SPI_DRIVER_SELECT == 2 +class SdSpiSoftDriver; +/** Port type for software SPI driver. */ +typedef SdSpiSoftDriver SpiPort_t; +#elif SPI_DRIVER_SELECT == 3 +class SdSpiBaseClass; +/** Port type for extrernal SPI driver. */ +typedef SdSpiBaseClass SpiPort_t; +#else // SPI_DRIVER_SELECT +typedef void* SpiPort_t; +#endif // SPI_DRIVER_SELECT +//------------------------------------------------------------------------------ +/** + * \class SdSpiConfig + * \brief SPI card configuration. + */ +class SdSpiConfig { + public: + /** SdSpiConfig constructor. + * + * \param[in] cs Chip select pin. + * \param[in] opt Options. + * \param[in] maxSpeed Maximum SCK frequency. + * \param[in] port The SPI port to use. + */ + SdSpiConfig(SdCsPin_t cs, uint8_t opt, uint32_t maxSpeed, SpiPort_t* port) : + csPin(cs), options(opt), maxSck(maxSpeed), spiPort(port) {} + + /** SdSpiConfig constructor. + * + * \param[in] cs Chip select pin. + * \param[in] opt Options. + * \param[in] maxSpeed Maximum SCK frequency. + */ + SdSpiConfig(SdCsPin_t cs, uint8_t opt, uint32_t maxSpeed) : + csPin(cs), options(opt), maxSck(maxSpeed) {} + /** SdSpiConfig constructor. + * + * \param[in] cs Chip select pin. + * \param[in] opt Options. + */ + SdSpiConfig(SdCsPin_t cs, uint8_t opt) : csPin(cs), options(opt) {} + /** SdSpiConfig constructor. + * + * \param[in] cs Chip select pin. + */ + explicit SdSpiConfig(SdCsPin_t cs) : csPin(cs) {} + + /** Chip select pin. */ + const SdCsPin_t csPin; + /** Options */ + const uint8_t options = 0; + /** Max SCK frequency */ + const uint32_t maxSck = SD_SCK_MHZ(50); + /** SPI port */ + SpiPort_t* spiPort = nullptr; +}; +#if SPI_DRIVER_SELECT < 2 +#include "SdSpiArduinoDriver.h" +#elif SPI_DRIVER_SELECT == 2 +#include "SdSpiSoftDriver.h" +#elif SPI_DRIVER_SELECT == 3 +#include "SdSpiBaseClass.h" +typedef SdSpiBaseClass SdSpiDriver; +#else // SPI_DRIVER_SELECT +#error Invalid SPI_DRIVER_SELECT +#endif // SPI_DRIVER_SELECT +#endif // SdSpiDriver_h diff --git a/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiDue.cpp b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiDue.cpp new file mode 100644 index 0000000..fa5e71c --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiDue.cpp @@ -0,0 +1,212 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "SdSpiDriver.h" +#if defined(SD_USE_CUSTOM_SPI) && defined(ARDUINO_SAM_DUE) +/* Use SAM3X DMAC if nonzero */ +#define USE_SAM3X_DMAC 1 +/* Use extra Bus Matrix arbitration fix if nonzero */ +#define USE_SAM3X_BUS_MATRIX_FIX 1 +/* Time in ms for DMA receive timeout */ +#define SAM3X_DMA_TIMEOUT 100 +/* chip select register number */ +#define SPI_CHIP_SEL 3 +/* DMAC receive channel */ +#define SPI_DMAC_RX_CH 1 +/* DMAC transmit channel */ +#define SPI_DMAC_TX_CH 0 +/* DMAC Channel HW Interface Number for SPI TX. */ +#define SPI_TX_IDX 1 +/* DMAC Channel HW Interface Number for SPI RX. */ +#define SPI_RX_IDX 2 +//------------------------------------------------------------------------------ +/* Disable DMA Controller. */ +static void dmac_disable() { + DMAC->DMAC_EN &= (~DMAC_EN_ENABLE); +} +/* Enable DMA Controller. */ +static void dmac_enable() { + DMAC->DMAC_EN = DMAC_EN_ENABLE; +} +/* Disable DMA Channel. */ +static void dmac_channel_disable(uint32_t ul_num) { + DMAC->DMAC_CHDR = DMAC_CHDR_DIS0 << ul_num; +} +/* Enable DMA Channel. */ +static void dmac_channel_enable(uint32_t ul_num) { + DMAC->DMAC_CHER = DMAC_CHER_ENA0 << ul_num; +} +/* Poll for transfer complete. */ +static bool dmac_channel_transfer_done(uint32_t ul_num) { + return (DMAC->DMAC_CHSR & (DMAC_CHSR_ENA0 << ul_num)) ? false : true; +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) { + (void)spiConfig; + SPI.begin(); +#if USE_SAM3X_DMAC + pmc_enable_periph_clk(ID_DMAC); + dmac_disable(); + DMAC->DMAC_GCFG = DMAC_GCFG_ARB_CFG_FIXED; + dmac_enable(); +#if USE_SAM3X_BUS_MATRIX_FIX + MATRIX->MATRIX_WPMR = 0x4d415400; + MATRIX->MATRIX_MCFG[1] = 1; + MATRIX->MATRIX_MCFG[2] = 1; + MATRIX->MATRIX_SCFG[0] = 0x01000010; + MATRIX->MATRIX_SCFG[1] = 0x01000010; + MATRIX->MATRIX_SCFG[7] = 0x01000010; +#endif // USE_SAM3X_BUS_MATRIX_FIX +#endif // USE_SAM3X_DMAC +} +//------------------------------------------------------------------------------ +// start RX DMA +static void spiDmaRX(uint8_t* dst, uint16_t count) { + dmac_channel_disable(SPI_DMAC_RX_CH); + DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_SADDR = (uint32_t)&SPI0->SPI_RDR; + DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_DADDR = (uint32_t)dst; + DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_DSCR = 0; + DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_CTRLA = count | + DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_DST_WIDTH_BYTE; + DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_CTRLB = DMAC_CTRLB_SRC_DSCR | + DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_PER2MEM_DMA_FC | + DMAC_CTRLB_SRC_INCR_FIXED | DMAC_CTRLB_DST_INCR_INCREMENTING; + DMAC->DMAC_CH_NUM[SPI_DMAC_RX_CH].DMAC_CFG = DMAC_CFG_SRC_PER(SPI_RX_IDX) | + DMAC_CFG_SRC_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ASAP_CFG; + dmac_channel_enable(SPI_DMAC_RX_CH); +} +//------------------------------------------------------------------------------ +// start TX DMA +static void spiDmaTX(const uint8_t* src, uint16_t count) { + static uint8_t ff = 0XFF; + uint32_t src_incr = DMAC_CTRLB_SRC_INCR_INCREMENTING; + if (!src) { + src = &ff; + src_incr = DMAC_CTRLB_SRC_INCR_FIXED; + } + dmac_channel_disable(SPI_DMAC_TX_CH); + DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_SADDR = (uint32_t)src; + DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_DADDR = (uint32_t)&SPI0->SPI_TDR; + DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_DSCR = 0; + DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CTRLA = count | + DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_DST_WIDTH_BYTE; + + DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CTRLB = DMAC_CTRLB_SRC_DSCR | + DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_MEM2PER_DMA_FC | + src_incr | DMAC_CTRLB_DST_INCR_FIXED; + + DMAC->DMAC_CH_NUM[SPI_DMAC_TX_CH].DMAC_CFG = DMAC_CFG_DST_PER(SPI_TX_IDX) | + DMAC_CFG_DST_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ALAP_CFG; + + dmac_channel_enable(SPI_DMAC_TX_CH); +} +//------------------------------------------------------------------------------ +// initialize SPI controller +void SdSpiArduinoDriver::activate() { + SPI.beginTransaction(m_spiSettings); + + Spi* pSpi = SPI0; + // Save the divisor + uint32_t scbr = pSpi->SPI_CSR[SPI_CHIP_SEL] & 0XFF00; + // Disable SPI + pSpi->SPI_CR = SPI_CR_SPIDIS; + // reset SPI + pSpi->SPI_CR = SPI_CR_SWRST; + // no mode fault detection, set master mode + pSpi->SPI_MR = SPI_PCS(SPI_CHIP_SEL) | SPI_MR_MODFDIS | SPI_MR_MSTR; + // mode 0, 8-bit, + pSpi->SPI_CSR[SPI_CHIP_SEL] = scbr | SPI_CSR_CSAAT | SPI_CSR_NCPHA; + // enable SPI + pSpi->SPI_CR |= SPI_CR_SPIEN; +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::deactivate() { + SPI.endTransaction(); +} +//------------------------------------------------------------------------------ +static inline uint8_t spiTransfer(uint8_t b) { + Spi* pSpi = SPI0; + + pSpi->SPI_TDR = b; + while ((pSpi->SPI_SR & SPI_SR_RDRF) == 0) {} + b = pSpi->SPI_RDR; + return b; +} +//------------------------------------------------------------------------------ +uint8_t SdSpiArduinoDriver::receive() { + return spiTransfer(0XFF); +} +//------------------------------------------------------------------------------ +uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) { + Spi* pSpi = SPI0; + int rtn = 0; +#if USE_SAM3X_DMAC + // clear overrun error + while (pSpi->SPI_SR & (SPI_SR_OVRES | SPI_SR_RDRF)) {pSpi->SPI_RDR;} + spiDmaRX(buf, count); + spiDmaTX(0, count); + + uint32_t m = millis(); + while (!dmac_channel_transfer_done(SPI_DMAC_RX_CH)) { + if ((millis() - m) > SAM3X_DMA_TIMEOUT) { + dmac_channel_disable(SPI_DMAC_RX_CH); + dmac_channel_disable(SPI_DMAC_TX_CH); + rtn = 2; + break; + } + } + if (pSpi->SPI_SR & SPI_SR_OVRES) { + rtn |= 1; + } +#else // USE_SAM3X_DMAC + for (size_t i = 0; i < count; i++) { + pSpi->SPI_TDR = 0XFF; + while ((pSpi->SPI_SR & SPI_SR_RDRF) == 0) {} + buf[i] = pSpi->SPI_RDR; + } +#endif // USE_SAM3X_DMAC + return rtn; +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::send(uint8_t data) { + spiTransfer(data); +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::send(const uint8_t* buf , size_t count) { + Spi* pSpi = SPI0; +#if USE_SAM3X_DMAC + spiDmaTX(buf, count); + while (!dmac_channel_transfer_done(SPI_DMAC_TX_CH)) {} +#else // #if USE_SAM3X_DMAC + while ((pSpi->SPI_SR & SPI_SR_TXEMPTY) == 0) {} + for (size_t i = 0; i < count; i++) { + pSpi->SPI_TDR = buf[i]; + while ((pSpi->SPI_SR & SPI_SR_TDRE) == 0) {} + } +#endif // #if USE_SAM3X_DMAC + while ((pSpi->SPI_SR & SPI_SR_TXEMPTY) == 0) {} + // leave RDR empty + while (pSpi->SPI_SR & (SPI_SR_OVRES | SPI_SR_RDRF)) {pSpi->SPI_RDR;} +} +#endif // defined(SD_USE_CUSTOM_SPI) && defined(ARDUINO_SAM_DUE) diff --git a/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiESP.cpp b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiESP.cpp new file mode 100644 index 0000000..73bc24e --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiESP.cpp @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "SdSpiDriver.h" +#if defined(SD_USE_CUSTOM_SPI) && (defined(ESP8266) || defined(ESP32)) +#define ESP_UNALIGN_OK 1 +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::activate() { + m_spi->beginTransaction(m_spiSettings); +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) { + if (spiConfig.spiPort) { + m_spi = spiConfig.spiPort; +#if defined(SDCARD_SPI) && defined(SDCARD_SS_PIN) + } else if (spiConfig.csPin == SDCARD_SS_PIN) { + m_spi = &SDCARD_SPI; +#endif // defined(SDCARD_SPI) && defined(SDCARD_SS_PIN) + } else { + m_spi = &SPI; + } + m_spi->begin(); +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::deactivate() { + m_spi->endTransaction(); +} +//------------------------------------------------------------------------------ +uint8_t SdSpiArduinoDriver::receive() { + return m_spi->transfer(0XFF); +} +//------------------------------------------------------------------------------ +uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) { +#if ESP_UNALIGN_OK + m_spi->transferBytes(nullptr, buf, count); +#else // ESP_UNALIGN_OK + // Adjust to 32-bit alignment. + while ((reinterpret_cast(buf) & 0X3) && count) { + *buf++ = m_spi->transfer(0xff); + count--; + } + // Do multiple of four byte transfers. + size_t n4 = 4*(count/4); + if (n4) { + m_spi->transferBytes(nullptr, buf, n4); + } + // Transfer up to three remaining bytes. + for (buf += n4, count -= n4; count; count--) { + *buf++ = m_spi->transfer(0xff); + } +#endif // ESP_UNALIGN_OK + return 0; +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::send(uint8_t data) { + m_spi->transfer(data); +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::send(const uint8_t* buf , size_t count) { +#if !ESP_UNALIGN_OK + // Adjust to 32-bit alignment. + while ((reinterpret_cast(buf) & 0X3) && count) { + SPI.transfer(*buf++); + count--; + } +#endif // #if ESP_UNALIGN_OK + + m_spi->transferBytes(const_cast(buf), nullptr, count); +} +#endif // defined(SD_USE_CUSTOM_SPI) && (defined(ESP8266) || defined(ESP32)) diff --git a/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiLibDriver.h b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiLibDriver.h new file mode 100644 index 0000000..c5d96d6 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiLibDriver.h @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief Class using only simple SPI library functions. + */ +#ifndef SdSpiLibDriver_h +#define SdSpiLibDriver_h +//------------------------------------------------------------------------------ +inline void SdSpiArduinoDriver::activate() { + m_spi->beginTransaction(m_spiSettings); +} +//------------------------------------------------------------------------------ +inline void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) { + if (spiConfig.spiPort) { + m_spi = spiConfig.spiPort; +#if defined(SDCARD_SPI) && defined(SDCARD_SS_PIN) + } else if (spiConfig.csPin == SDCARD_SS_PIN) { + m_spi = &SDCARD_SPI; +#endif // defined(SDCARD_SPI) && defined(SDCARD_SS_PIN) + } else { + m_spi = &SPI; + } + m_spi->begin(); +} +//------------------------------------------------------------------------------ +inline void SdSpiArduinoDriver::deactivate() { + m_spi->endTransaction(); +} +//------------------------------------------------------------------------------ +inline uint8_t SdSpiArduinoDriver::receive() { + return m_spi->transfer( 0XFF); +} +//------------------------------------------------------------------------------ +inline uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) { +#if USE_SPI_ARRAY_TRANSFER + memset(buf, 0XFF, count); + m_spi->transfer(buf, count); +#else // USE_SPI_ARRAY_TRANSFER + for (size_t i = 0; i < count; i++) { + buf[i] = m_spi->transfer(0XFF); + } +#endif // USE_SPI_ARRAY_TRANSFER + return 0; +} +//------------------------------------------------------------------------------ +inline void SdSpiArduinoDriver::send(uint8_t data) { + m_spi->transfer(data); +} +//------------------------------------------------------------------------------ +inline void SdSpiArduinoDriver::send(const uint8_t* buf, size_t count) { +#if USE_SPI_ARRAY_TRANSFER + if (count <= 512) { + uint8_t tmp[count]; // NOLINT + memcpy(tmp, buf, count); + m_spi->transfer(tmp, count); + return; + } +#endif // USE_SPI_ARRAY_TRANSFER + for (size_t i = 0; i < count; i++) { + m_spi->transfer(buf[i]); + } +} +#endif // SdSpiLibDriver_h diff --git a/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiParticle.cpp b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiParticle.cpp new file mode 100644 index 0000000..c76253d --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiParticle.cpp @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "SdSpiDriver.h" +#if defined(SD_USE_CUSTOM_SPI) && defined(PLATFORM_ID) +static volatile bool SPI_DMA_TransferCompleted = false; +//----------------------------------------------------------------------------- +static void SD_SPI_DMA_TransferComplete_Callback() { + SPI_DMA_TransferCompleted = true; +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::activate() { + m_spi->beginTransaction(m_spiSettings); +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) { + if (spiConfig.spiPort) { + m_spi = spiConfig.spiPort; + } else { + m_spi = &SPI; + } + m_spi->begin(); +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::deactivate() { + m_spi->endTransaction(); +} +//------------------------------------------------------------------------------ +uint8_t SdSpiArduinoDriver::receive() { + return m_spi->transfer(0XFF); +} +//------------------------------------------------------------------------------ +uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) { + SPI_DMA_TransferCompleted = false; + m_spi->transfer(nullptr, buf, count, SD_SPI_DMA_TransferComplete_Callback); + while (!SPI_DMA_TransferCompleted) {} + return 0; +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::send(uint8_t data) { + m_spi->transfer(data); +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::send(const uint8_t* buf , size_t count) { + SPI_DMA_TransferCompleted = false; + + m_spi->transfer(const_cast(buf), nullptr, count, + SD_SPI_DMA_TransferComplete_Callback); + + while (!SPI_DMA_TransferCompleted) {} +} +#endif // defined(SD_USE_CUSTOM_SPI) && defined(PLATFORM_ID) + diff --git a/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiSTM32.cpp b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiSTM32.cpp new file mode 100644 index 0000000..737bdd2 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiSTM32.cpp @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "SdSpiDriver.h" +#if defined(SD_USE_CUSTOM_SPI)\ + && (defined(__STM32F1__) || defined(__STM32F4__)) +#if defined(__STM32F1__) +#define USE_STM32_DMA 1 +#elif defined(__STM32F4__) +#define USE_STM32_DMA 1 +#else // defined(__STM32F1__) +#error Unknown STM32 type +#endif // defined(__STM32F1__) +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::activate() { + m_spi->beginTransaction(m_spiSettings); +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) { + if (spiConfig.spiPort) { + m_spi = spiConfig.spiPort; + } else { + m_spi = &SPI; + } + m_spi->begin(); +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::deactivate() { + m_spi->endTransaction(); +} +//------------------------------------------------------------------------------ +uint8_t SdSpiArduinoDriver::receive() { + return m_spi->transfer(0XFF); +} +//------------------------------------------------------------------------------ +uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) { +#if USE_STM32_DMA + return m_spi->dmaTransfer(nullptr, buf, count); +#else // USE_STM32_DMA + m_spi->read(buf, count); + return 0; +#endif // USE_STM32_DMA +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::send(uint8_t data) { + m_spi->transfer(data); +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::send(const uint8_t* buf , size_t count) { +#if USE_STM32_DMA + m_spi->dmaTransfer(const_cast(buf), nullptr, count); +#else // USE_STM32_DMA + m_spi->write(const_cast(buf), count); +#endif // USE_STM32_DMA +} +#endif // defined(SD_USE_CUSTOM_SPI) && defined(__STM32F1__) diff --git a/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiSoftDriver.h b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiSoftDriver.h new file mode 100644 index 0000000..f283e2f --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiSoftDriver.h @@ -0,0 +1,119 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief Class for software SPI. + */ +#ifndef SdSpiSoftDriver_h +#define SdSpiSoftDriver_h +#include "../DigitalIO/SoftSPI.h" +/** + * \class SdSpiSoftDriver + * \brief Base class for external soft SPI. + */ +class SdSpiSoftDriver { + public: + /** Activate SPI hardware. */ + void activate() {} + /** Initialize the SPI bus. */ + virtual void begin() = 0; + /** Initialize the SPI bus. + * + * \param[in] spiConfig SD card configuration. + */ + void begin(SdSpiConfig spiConfig) { + (void)spiConfig; + begin(); + } + /** Deactivate SPI hardware. */ + void deactivate() {} + /** Receive a byte. + * + * \return The byte. + */ + virtual uint8_t receive() = 0; + /** Receive multiple bytes. + * + * \param[out] buf Buffer to receive the data. + * \param[in] count Number of bytes to receive. + * + * \return Zero for no error or nonzero error code. + */ + uint8_t receive(uint8_t* buf, size_t count) { + for (size_t i = 0; i < count; i++) { + buf[i] = receive(); + } + return 0; + } + /** Send a byte. + * + * \param[in] data Byte to send + */ + virtual void send(uint8_t data) = 0; + /** Send multiple bytes. + * + * \param[in] buf Buffer for data to be sent. + * \param[in] count Number of bytes to send. + */ + void send(const uint8_t* buf, size_t count) { + for (size_t i = 0; i < count; i++) { + send(buf[i]); + } + } + /** Save high speed SPISettings after SD initialization. + * + * \param[in] maxSck Maximum SCK frequency. + */ + void setSckSpeed(uint32_t maxSck) { + (void)maxSck; + } +}; +//------------------------------------------------------------------------------ +/** + * \class SoftSpiDriver + * \brief Class for external soft SPI. + */ +template +class SoftSpiDriver : public SdSpiSoftDriver { + public: + /** Initialize the SPI bus. */ + void begin() {m_spi.begin();} + /** Receive a byte. + * + * \return The byte. + */ + uint8_t receive() {return m_spi.receive();} + /** Send a byte. + * + * \param[in] data Byte to send + */ + void send(uint8_t data) {m_spi.send(data);} + private: + SoftSPI m_spi; +}; + +/** Typedef for use of SdSoftSpiDriver */ +typedef SdSpiSoftDriver SdSpiDriver; +#endif // SdSpiSoftDriver_h diff --git a/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiTeensy3.cpp b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiTeensy3.cpp new file mode 100644 index 0000000..247c08f --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/SpiDriver/SdSpiTeensy3.cpp @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "SdSpiDriver.h" +#if defined(SD_USE_CUSTOM_SPI) && defined(__arm__) && defined(CORE_TEENSY) +#define USE_BLOCK_TRANSFER 1 +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::activate() { + m_spi->beginTransaction(m_spiSettings); +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) { + if (spiConfig.spiPort) { + m_spi = spiConfig.spiPort; +#if defined(SDCARD_SPI) && defined(SDCARD_SS_PIN) + } else if (spiConfig.csPin == SDCARD_SS_PIN) { + m_spi = &SDCARD_SPI; + m_spi->setMISO(SDCARD_MISO_PIN); + m_spi->setMOSI(SDCARD_MOSI_PIN); + m_spi->setSCK(SDCARD_SCK_PIN); +#endif // defined(SDCARD_SPI) && defined(SDCARD_SS_PIN) + } else { + m_spi = &SPI; + } + m_spi->begin(); +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::deactivate() { + m_spi->endTransaction(); +} +//------------------------------------------------------------------------------ +uint8_t SdSpiArduinoDriver::receive() { + return m_spi->transfer(0XFF); +} +//------------------------------------------------------------------------------ +uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) { +#if USE_BLOCK_TRANSFER + memset(buf, 0XFF, count); + m_spi->transfer(buf, count); +#else // USE_BLOCK_TRANSFER + for (size_t i = 0; i < count; i++) { + buf[i] = m_spi->transfer(0XFF); + } +#endif // USE_BLOCK_TRANSFER + return 0; +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::send(uint8_t data) { + m_spi->transfer(data); +} +//------------------------------------------------------------------------------ +void SdSpiArduinoDriver::send(const uint8_t* buf , size_t count) { +#if USE_BLOCK_TRANSFER + uint32_t tmp[128]; + if (0 < count && count <= 512) { + memcpy(tmp, buf, count); + m_spi->transfer(tmp, count); + return; + } +#endif // USE_BLOCK_TRANSFER + for (size_t i = 0; i < count; i++) { + m_spi->transfer(buf[i]); + } +} +#endif // defined(SD_USE_CUSTOM_SPI) && defined(__arm__) &&defined(CORE_TEENSY) diff --git a/Firmware_V3/lib/SdFat/src/common/ArduinoFiles.h b/Firmware_V3/lib/SdFat/src/common/ArduinoFiles.h new file mode 100644 index 0000000..3e08de9 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/common/ArduinoFiles.h @@ -0,0 +1,157 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef ArduinoFiles_h +#define ArduinoFiles_h +#include "../SdFatConfig.h" +//------------------------------------------------------------------------------ +/** Arduino SD.h style flag for open for read. */ +#ifndef FILE_READ +#define FILE_READ O_RDONLY +#endif // FILE_READ +/** Arduino SD.h style flag for open at EOF for read/write with create. */ +#ifndef FILE_WRITE +#define FILE_WRITE (O_RDWR | O_CREAT | O_AT_END) +#endif // FILE_WRITE +//------------------------------------------------------------------------------ +/** + * \class PrintFile + * \brief PrintFile class. + */ +template +class PrintFile : public print_t, public BaseFile { + public: + using BaseFile::clearWriteError; + using BaseFile::getWriteError; + using BaseFile::read; + using BaseFile::write; + /** Write a single byte. + * \param[in] b byte to write. + * \return one for success. + */ + size_t write(uint8_t b) { + return BaseFile::write(&b, 1); + } +}; +//------------------------------------------------------------------------------ +/** + * \class StreamFile + * \brief StreamFile class. + */ +template +class StreamFile : public stream_t, public BaseFile { + public: + using BaseFile::clearWriteError; + using BaseFile::getWriteError; + using BaseFile::read; + using BaseFile::write; + + StreamFile() {} + + /** \return number of bytes available from the current position to EOF + * or INT_MAX if more than INT_MAX bytes are available. + */ + int available() { + return BaseFile::available(); + } + /** Ensure that any bytes written to the file are saved to the SD card. */ + void flush() { + BaseFile::sync(); + } + /** This function reports if the current file is a directory or not. + * \return true if the file is a directory. + */ + bool isDirectory() { + return BaseFile::isDir(); + } + /** No longer implemented due to Long File Names. + * + * Use getName(char* name, size_t size). + * \return a pointer to replacement suggestion. + */ + const char* name() const {return "use getName()";} + /** Return the next available byte without consuming it. + * + * \return The byte if no error and not at eof else -1; + */ + int peek() { + return BaseFile::peek(); + } + /** \return the current file position. */ + PosType position() { + return BaseFile::curPosition(); + } + /** Read the next byte from a file. + * + * \return For success return the next byte in the file as an int. + * If an error occurs or end of file is reached return -1. + */ + int read() { + return BaseFile::read(); + } + /** Rewind a file if it is a directory */ + void rewindDirectory() { + if (BaseFile::isDir()) { + BaseFile::rewind(); + } + } + /** + * Seek to a new position in the file, which must be between + * 0 and the size of the file (inclusive). + * + * \param[in] pos the new file position. + * \return true for success or false for failure. + */ + bool seek(PosType pos) { + return BaseFile::seekSet(pos); + } + /** \return the file's size. */ + PosType size() { + return BaseFile::fileSize(); + } + /** Write a byte to a file. Required by the Arduino Print class. + * \param[in] b the byte to be written. + * Use getWriteError to check for errors. + * \return 1 for success and 0 for failure. + */ + size_t write(uint8_t b) { + return BaseFile::write(b); + } + /** Write data to an open file. + * + * \note Data is moved to the cache but may not be written to the + * storage device until sync() is called. + * + * \param[in] buffer Pointer to the location of the data to be written. + * + * \param[in] size Number of bytes to write. + * + * \return For success write() returns the number of bytes written, always + * \a size. + */ + size_t write(const uint8_t* buffer, size_t size) { + return BaseFile::write(buffer, size); + } +}; +#endif // ArduinoFiles_h diff --git a/Firmware_V3/lib/SdFat/src/common/BlockDevice.h b/Firmware_V3/lib/SdFat/src/common/BlockDevice.h new file mode 100644 index 0000000..aebfeef --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/common/BlockDevice.h @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef BlockDevice_h +#define BlockDevice_h +#include "SdCard/SdCard.h" +#if HAS_SDIO_CLASS || USE_BLOCK_DEVICE_INTERFACE +typedef BlockDeviceInterface BlockDevice; +#else +typedef SdCard BlockDevice; +#endif +#endif // BlockDevice_h diff --git a/Firmware_V3/lib/SdFat/src/common/BlockDeviceInterface.h b/Firmware_V3/lib/SdFat/src/common/BlockDeviceInterface.h new file mode 100644 index 0000000..748059f --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/common/BlockDeviceInterface.h @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief BlockDeviceInterface include file. + */ +#ifndef BlockDeviceInterface_h +#define BlockDeviceInterface_h +#include +#include +#include "../SdFatConfig.h" +/** + * \class BlockDeviceInterface + * \brief BlockDeviceInterface class. + */ +class BlockDeviceInterface { + public: + virtual ~BlockDeviceInterface() {} + /** + * Check for BlockDevice busy. + * + * \return true if busy else false. + */ + virtual bool isBusy() = 0; + /** + * Read a sector. + * + * \param[in] sector Logical sector to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return true for success or false for failure. + */ + virtual bool readSector(uint32_t sector, uint8_t* dst) = 0; + + /** + * Read multiple sectors. + * + * \param[in] sector Logical sector to be read. + * \param[in] ns Number of sectors to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return true for success or false for failure. + */ + virtual bool readSectors(uint32_t sector, uint8_t* dst, size_t ns) = 0; + + /** \return device size in sectors. */ + virtual uint32_t sectorCount() = 0; + + /** End multi-sector transfer and go to idle state. + * \return true for success or false for failure. + */ + virtual bool syncDevice() = 0; + + /** + * Writes a sector. + * + * \param[in] sector Logical sector to be written. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + virtual bool writeSector(uint32_t sector, const uint8_t* src) = 0; + + /** + * Write multiple sectors. + * + * \param[in] sector Logical sector to be written. + * \param[in] ns Number of sectors to be written. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + virtual bool writeSectors(uint32_t sector, const uint8_t* src, size_t ns) = 0; +}; +#endif // BlockDeviceInterface_h diff --git a/Firmware_V3/lib/SdFat/src/common/CPPLINT.cfg b/Firmware_V3/lib/SdFat/src/common/CPPLINT.cfg new file mode 100644 index 0000000..f274762 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/common/CPPLINT.cfg @@ -0,0 +1,3 @@ +exclude_files=PrintBasic.cpp +exclude_files=PrintBasic.h +exclude_files=PrintTemplates.h \ No newline at end of file diff --git a/Firmware_V3/lib/SdFat/src/common/CompileDateTime.h b/Firmware_V3/lib/SdFat/src/common/CompileDateTime.h new file mode 100644 index 0000000..829dd22 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/common/CompileDateTime.h @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef CompileDateTime_h +#define CompileDateTime_h +// Note - these functions will compile to a few bytes +// since they are evaluated at compile time. + +/** \return year field of the __DATE__ macro. */ +constexpr uint16_t compileYear() { + return 1000*(__DATE__[7] - '0') + + 100*(__DATE__[8] - '0') + + 10*(__DATE__[9] - '0') + + (__DATE__[10] - '0'); +} +/** \return true if str equals the month field of the __DATE__ macro. */ +constexpr bool compileMonthIs(const char* str) { + return __DATE__[0] == str[0] + && __DATE__[1] == str[1] + && __DATE__[2] == str[2]; +} +/** \return month field of the __DATE__ macro. */ +constexpr uint8_t compileMonth() { + return compileMonthIs("Jan") ? 1 : + compileMonthIs("Feb") ? 2 : + compileMonthIs("Mar") ? 3 : + compileMonthIs("Apr") ? 4 : + compileMonthIs("May") ? 5 : + compileMonthIs("Jun") ? 6 : + compileMonthIs("Jul") ? 7 : + compileMonthIs("Aug") ? 8 : + compileMonthIs("Sep") ? 9 : + compileMonthIs("Oct") ? 10 : + compileMonthIs("Nov") ? 11 : + compileMonthIs("Dec") ? 12 : 0; +} +/** \return day field of the __DATE__ macro. */ +constexpr uint8_t compileDay() { + return 10*(__DATE__[4] == ' ' ? 0 : __DATE__[4] - '0') + (__DATE__[5] - '0'); +} +/** \return hour field of the __TIME__ macro. */ +constexpr uint8_t compileHour() { + return 10*(__TIME__[0] - '0') + __TIME__[1] - '0'; +} +/** \return minute field of the __TIME__ macro. */ +constexpr uint8_t compileMinute() { + return 10*(__TIME__[3] - '0') + __TIME__[4] - '0'; +} +/** \return second field of the __TIME__ macro. */ +constexpr uint8_t compileSecond() { + return 10*(__TIME__[6] - '0') + __TIME__[7] - '0'; +} +#endif // CompileDateTime_h diff --git a/Firmware_V3/lib/SdFat/src/common/DebugMacros.h b/Firmware_V3/lib/SdFat/src/common/DebugMacros.h new file mode 100644 index 0000000..e01f609 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/common/DebugMacros.h @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef DebugMacros_h +#define DebugMacros_h +#include "../SdFatConfig.h" +#define USE_DBG_MACROS 0 + +#if USE_DBG_MACROS +#include "Arduino.h" +#ifndef DBG_FILE +#error DBG_FILE not defined +#endif // DBG_FILE +static void dbgPrint(uint16_t line) { + Serial.print(F("DBG_FAIL: ")); + Serial.print(F(DBG_FILE)); + Serial.write('.'); + Serial.println(line); +} + +#define DBG_PRINT_IF(b) if (b) {Serial.print(F(__FILE__));\ + Serial.println(__LINE__);} +#define DBG_HALT_IF(b) if (b) {Serial.print(F("DBG_HALT "));\ + Serial.print(F(__FILE__)); Serial.println(__LINE__);\ + while (true) {}} +#define DBG_FAIL_MACRO dbgPrint(__LINE__); +#else // USE_DBG_MACROS +#define DBG_FAIL_MACRO +#define DBG_PRINT_IF(b) +#define DBG_HALT_IF(b) +#endif // USE_DBG_MACROS +#endif // DebugMacros_h diff --git a/Firmware_V3/lib/SdFat/src/common/FmtNumber.cpp b/Firmware_V3/lib/SdFat/src/common/FmtNumber.cpp new file mode 100644 index 0000000..fcc9e54 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/common/FmtNumber.cpp @@ -0,0 +1,516 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "FmtNumber.h" +// always use fmtBase10() - seems fast even on teensy 3.6. +#define USE_FMT_BASE10 1 + +// Use Stimmer div/mod 10 on avr +#ifdef __AVR__ +#include +#define USE_STIMMER +#endif // __AVR__ +//------------------------------------------------------------------------------ +// Stimmer div/mod 10 for AVR +// this code fragment works out i/10 and i%10 by calculating +// i*(51/256)*(256/255)/2 == i*51/510 == i/10 +// by "j.k" I mean 32.8 fixed point, j is integer part, k is fractional part +// j.k = ((j+1.0)*51.0)/256.0 +// (we add 1 because we will be using the floor of the result later) +// divmod10_asm16 and divmod10_asm32 are public domain code by Stimmer. +// http://forum.arduino.cc/index.php?topic=167414.msg1293679#msg1293679 +#define divmod10_asm16(in32, mod8, tmp8) \ +asm volatile( \ + " ldi %2,51 \n\t" \ + " mul %A0,%2 \n\t" \ + " clr %A0 \n\t" \ + " add r0,%2 \n\t" \ + " adc %A0,r1 \n\t" \ + " mov %1,r0 \n\t" \ + " mul %B0,%2 \n\t" \ + " clr %B0 \n\t" \ + " add %A0,r0 \n\t" \ + " adc %B0,r1 \n\t" \ + " clr r1 \n\t" \ + " add %1,%A0 \n\t" \ + " adc %A0,%B0 \n\t" \ + " adc %B0,r1 \n\t" \ + " add %1,%B0 \n\t" \ + " adc %A0,r1 \n\t" \ + " adc %B0,r1 \n\t" \ + " lsr %B0 \n\t" \ + " ror %A0 \n\t" \ + " ror %1 \n\t" \ + " ldi %2,10 \n\t" \ + " mul %1,%2 \n\t" \ + " mov %1,r1 \n\t" \ + " clr r1 \n\t" \ + :"+r"(in32), "=d"(mod8), "=d"(tmp8) : : "r0") + +#define divmod10_asm32(in32, mod8, tmp8) \ +asm volatile( \ + " ldi %2,51 \n\t" \ + " mul %A0,%2 \n\t" \ + " clr %A0 \n\t" \ + " add r0,%2 \n\t" \ + " adc %A0,r1 \n\t" \ + " mov %1,r0 \n\t" \ + " mul %B0,%2 \n\t" \ + " clr %B0 \n\t" \ + " add %A0,r0 \n\t" \ + " adc %B0,r1 \n\t" \ + " mul %C0,%2 \n\t" \ + " clr %C0 \n\t" \ + " add %B0,r0 \n\t" \ + " adc %C0,r1 \n\t" \ + " mul %D0,%2 \n\t" \ + " clr %D0 \n\t" \ + " add %C0,r0 \n\t" \ + " adc %D0,r1 \n\t" \ + " clr r1 \n\t" \ + " add %1,%A0 \n\t" \ + " adc %A0,%B0 \n\t" \ + " adc %B0,%C0 \n\t" \ + " adc %C0,%D0 \n\t" \ + " adc %D0,r1 \n\t" \ + " add %1,%B0 \n\t" \ + " adc %A0,%C0 \n\t" \ + " adc %B0,%D0 \n\t" \ + " adc %C0,r1 \n\t" \ + " adc %D0,r1 \n\t" \ + " add %1,%D0 \n\t" \ + " adc %A0,r1 \n\t" \ + " adc %B0,r1 \n\t" \ + " adc %C0,r1 \n\t" \ + " adc %D0,r1 \n\t" \ + " lsr %D0 \n\t" \ + " ror %C0 \n\t" \ + " ror %B0 \n\t" \ + " ror %A0 \n\t" \ + " ror %1 \n\t" \ + " ldi %2,10 \n\t" \ + " mul %1,%2 \n\t" \ + " mov %1,r1 \n\t" \ + " clr r1 \n\t" \ + :"+r"(in32), "=d"(mod8), "=d"(tmp8) : : "r0") +//------------------------------------------------------------------------------ +/* +// C++ code is based on this version of divmod10 by robtillaart. +// http://forum.arduino.cc/index.php?topic=167414.msg1246851#msg1246851 +// from robtillaart post: +// The code is based upon the divu10() code from the book Hackers Delight1. +// My insight was that the error formula in divu10() was in fact modulo 10 +// but not always. Sometimes it was 10 more. +void divmod10(uint32_t in, uint32_t &div, uint32_t &mod) +{ + // q = in * 0.8; + uint32_t q = (in >> 1) + (in >> 2); + q = q + (q >> 4); + q = q + (q >> 8); + q = q + (q >> 16); // not needed for 16 bit version + + // q = q / 8; ==> q = in *0.1; + q = q >> 3; + + // determine error + uint32_t r = in - ((q << 3) + (q << 1)); // r = in - q*10; + div = q + (r > 9); + if (r > 9) mod = r - 10; + else mod = r; +} +// Hackers delight function is here: +// http://www.hackersdelight.org/hdcodetxt/divuc.c.txt +// Code below uses 8/10 = 0.1100 1100 1100 1100 1100 1100 1100 1100. +// 15 ops including the multiply, or 17 elementary ops. +unsigned divu10(unsigned n) { + unsigned q, r; + + q = (n >> 1) + (n >> 2); + q = q + (q >> 4); + q = q + (q >> 8); + q = q + (q >> 16); + q = q >> 3; + r = n - q*10; + return q + ((r + 6) >> 4); +// return q + (r > 9); +} +*/ +//------------------------------------------------------------------------------ +// Format 16-bit unsigned +char* fmtBase10(char* str, uint16_t n) { + while (n > 9) { +#ifdef USE_STIMMER + uint8_t tmp8, r; + divmod10_asm16(n, r, tmp8); +#else // USE_STIMMER + uint16_t t = n; + n = (n >> 1) + (n >> 2); + n = n + (n >> 4); + n = n + (n >> 8); + // n = n + (n >> 16); // no code for 16-bit n + n = n >> 3; + uint8_t r = t - (((n << 2) + n) << 1); + if (r > 9) { + n++; + r -= 10; + } +#endif // USE_STIMMER + *--str = r + '0'; + } + *--str = n + '0'; + return str; +} +//------------------------------------------------------------------------------ +// format 32-bit unsigned +char* fmtBase10(char* str, uint32_t n) { + while (n > 0XFFFF) { +#ifdef USE_STIMMER + uint8_t tmp8, r; + divmod10_asm32(n, r, tmp8); +#else // USE_STIMMER + uint32_t t = n; + n = (n >> 1) + (n >> 2); + n = n + (n >> 4); + n = n + (n >> 8); + n = n + (n >> 16); + n = n >> 3; + uint8_t r = t - (((n << 2) + n) << 1); + if (r > 9) { + n++; + r -= 10; + } +#endif // USE_STIMMER + *--str = r + '0'; + } + return fmtBase10(str, (uint16_t)n); +} +//------------------------------------------------------------------------------ +char* fmtHex(char* str, uint32_t n) { + do { + uint8_t h = n & 0XF; + *--str = h + (h < 10 ? '0' : 'A' - 10); + n >>= 4; + } while (n); + return str; +} +//------------------------------------------------------------------------------ +char* fmtSigned(char* str, int32_t num, uint8_t base, bool caps) { + bool neg = base == 10 && num < 0; + if (neg) { + num = -num; + } + str = fmtUnsigned(str, num, base, caps); + if (neg) { + *--str = '-'; + } + return str; +} +//----------------------------------------------------------------------------- +char* fmtUnsigned(char* str, uint32_t num, uint8_t base, bool caps) { +#if USE_FMT_BASE10 + if (base == 10) return fmtBase10(str, (uint32_t)num); +#endif // USE_FMT_BASE10 + do { + int c = num%base; + *--str = c + (c < 10 ? '0' : caps ? 'A' - 10 : 'a' - 10); + } while (num /= base); + return str; +} +//----------------------------------------------------------------------------- + +static const double powTen[] = {1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9}; +static const double rnd[] = + {5e-1, 5e-2, 5e-3, 5e-4, 5e-5, 5e-6, 5e-7, 5e-8, 5e-9, 5e-10}; +static const size_t MAX_PREC = sizeof(powTen)/sizeof(powTen[0]); + +char *fmtDouble(char *str, double num, uint8_t prec, bool altFmt) { + bool neg = num < 0; + if (neg) { + num = -num; + } + if (isnan(num)) { + *--str = 'n'; + *--str = 'a'; + *--str = 'n'; + return str; + } + if (isinf(num)) { + *--str = 'f'; + *--str = 'n'; + *--str = 'i'; + return str; + } + // last float < 2^32 + if (num > 4294967040.0) { + *--str = 'f'; + *--str = 'v'; + *--str = 'o'; + return str; + } + + if (prec > MAX_PREC) { + prec = MAX_PREC; + } + num += rnd[prec]; + uint32_t ul = num; + if (prec) { + char* s = str - prec; + uint32_t f = (num - ul)*powTen[prec - 1]; + str = fmtBase10(str, f); + while (str > s) { + *--str = '0'; + } + } + if (prec || altFmt) { + *--str = '.'; + } + str = fmtBase10(str, ul); + if (neg) { + *--str = '-'; + } + return str; +} +//------------------------------------------------------------------------------ +/** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] ptr Pointer to last char in buffer. + * \param[in] prec Number of digits after decimal point. + * \param[in] expChar Use exp format if non zero. + * \return Pointer to first character of result. + */ +char* fmtDouble(char* str, double value, + uint8_t prec, bool altFmt, char expChar) { + if (expChar != 'e' && expChar != 'E') { + expChar = 0; + } + bool neg = value < 0; + if (neg) { + value = -value; + } + // check for nan inf ovf + if (isnan(value)) { + *--str = 'n'; + *--str = 'a'; + *--str = 'n'; + return str; + } + if (isinf(value)) { + *--str = 'f'; + *--str = 'n'; + *--str = 'i'; + return str; + } + if (!expChar && value > 4294967040.0) { + *--str = 'f'; + *--str = 'v'; + *--str = 'o'; + return str; + } + if (prec > 9) { + prec = 9; + } + if (expChar) { + int8_t exp = 0; + bool expNeg = false; + if (value) { + if (value > 10.0L) { + while (value > 1e16L) { + value *= 1e-16L; + exp += 16; + } + while (value > 1e4L) { + value *= 1e-4L; + exp += 4; + } + while (value > 10.0L) { + value *= 0.1L; + exp++; + } + } else if (value < 1.0L) { + while (value < 1e-16L) { + value *= 1e16L; + exp -= 16; + } + while (value < 1e-4L) { + value *= 1e4L; + exp -= 4; + } + while (value < 1.0L) { + value *= 10.0L; + exp--; + } + } + value += rnd[prec]; + if (value >= 10.0L) { + value *= 0.1L; + exp++; + } + expNeg = exp < 0; + if (expNeg) { + exp = -exp; + } + } + str = fmtBase10(str, (uint16_t)exp); + if (exp < 10) { + *--str = '0'; + } + *--str = expNeg ? '-' : '+'; + *--str = expChar; + } else { + // round value + value += rnd[prec]; + } + + uint32_t whole = value; + if (prec) { + char* tmp = str - prec; + uint32_t fraction = (value - whole)*powTen[prec - 1]; + str = fmtBase10(str, fraction); + while (str > tmp) { + *--str = '0'; + } + } + if (prec || altFmt)*--str = '.'; + str = fmtBase10(str, whole); + if (neg) { + *--str = '-'; + } + return str; +} +//============================================================================== +// functions below not used +//------------------------------------------------------------------------------ +#ifndef DOXYGEN_SHOULD_SKIP_THIS +#ifdef __AVR__ +static const float m[] PROGMEM = {1e-1, 1e-2, 1e-4, 1e-8, 1e-16, 1e-32}; +static const float p[] PROGMEM = {1e+1, 1e+2, 1e+4, 1e+8, 1e+16, 1e+32}; +#else // __AVR__ +static const float m[] = {1e-1, 1e-2, 1e-4, 1e-8, 1e-16, 1e-32}; +static const float p[] = {1e+1, 1e+2, 1e+4, 1e+8, 1e+16, 1e+32}; +#endif // __AVR__ +#endif // DOXYGEN_SHOULD_SKIP_THIS +// scale float v by power of ten. return v*10^n +float scale10(float v, int8_t n) { + const float *s; + if (n < 0) { + n = -n; + s = m; + } else { + s = p; + } + n &= 63; + for (uint8_t i = 0; n; n >>= 1, i++) { +#ifdef __AVR__ + if (n & 1) { + v *= pgm_read_float(&s[i]); + } +#else // __AVR__ + if (n & 1) { + v *= s[i]; + } +#endif // __AVR__ + } + return v; +} +//------------------------------------------------------------------------------ +float scanFloat(const char* str, const char** ptr) { + int16_t const EXP_LIMIT = 100; + bool digit = false; + bool dot = false; + uint32_t fract = 0; + int fracExp = 0; + uint8_t nd = 0; + bool neg; + int c; + float v; + const char* successPtr = str; + + if (ptr) { + *ptr = str; + } + + while (isSpace((c = *str++))) {} + neg = c == '-'; + if (c == '-' || c == '+') { + c = *str++; + } + // Skip leading zeros + while (c == '0') { + c = *str++; + digit = true; + } + for (;;) { + if (isDigit(c)) { + digit = true; + if (nd < 9) { + fract = 10*fract + c - '0'; + nd++; + if (dot) { + fracExp--; + } + } else { + if (!dot) { + fracExp++; + } + } + } else if (c == '.') { + if (dot) { + goto fail; + } + dot = true; + } else { + if (!digit) { + goto fail; + } + break; + } + successPtr = str; + c = *str++; + } + if (c == 'e' || c == 'E') { + int exp = 0; + c = *str++; + bool expNeg = c == '-'; + if (c == '-' || c == '+') { + c = *str++; + } + while (isDigit(c)) { + if (exp > EXP_LIMIT) { + goto fail; + } + exp = 10*exp + c - '0'; + successPtr = str; + c = *str++; + } + fracExp += expNeg ? -exp : exp; + } + if (ptr) { + *ptr = successPtr; + } + v = scale10(static_cast(fract), fracExp); + return neg ? -v : v; + + fail: + return 0; +} diff --git a/Firmware_V3/lib/SdFat/src/common/FmtNumber.h b/Firmware_V3/lib/SdFat/src/common/FmtNumber.h new file mode 100644 index 0000000..73e6ad1 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/common/FmtNumber.h @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef FmtNumber_h +#define FmtNumber_h +#include +#include +#include +inline bool isDigit(char c) { + return '0' <= (c) && (c) <= '9'; +} +inline bool isSpace(char c) { + return (c) == ' ' || (0X9 <= (c) && (c) <= 0XD); +} +char* fmtBase10(char* str, uint16_t n); +char* fmtBase10(char* str, uint32_t n); +char* fmtDouble(char *str, double d, uint8_t prec, bool altFmt); +char* fmtDouble(char* str, double d, uint8_t prec, bool altFmt, char expChar); +char* fmtHex(char* str, uint32_t n); +char* fmtSigned(char* str, int32_t n, uint8_t base, bool caps); +char* fmtUnsigned(char* str, uint32_t n, uint8_t base, bool caps); +#endif // FmtNumber_h diff --git a/Firmware_V3/lib/SdFat/src/common/FsApiConstants.h b/Firmware_V3/lib/SdFat/src/common/FsApiConstants.h new file mode 100644 index 0000000..fadc45f --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/common/FsApiConstants.h @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef FsApiConstants_h +#define FsApiConstants_h +#include "../SdFatConfig.h" + +#if USE_FCNTL_H +#include +/* values for GNU Arm Embedded Toolchain. + * O_RDONLY: 0x0 + * O_WRONLY: 0x1 + * O_RDWR: 0x2 + * O_ACCMODE: 0x3 + * O_APPEND: 0x8 + * O_CREAT: 0x200 + * O_TRUNC: 0x400 + * O_EXCL: 0x800 + * O_SYNC: 0x2000 + * O_NONBLOCK: 0x4000 + */ +/** Use O_NONBLOCK for open at EOF */ +#define O_AT_END O_NONBLOCK ///< Open at EOF. +typedef int oflag_t; +#else // USE_FCNTL_H +#define O_RDONLY 0X00 ///< Open for reading only. +#define O_WRONLY 0X01 ///< Open for writing only. +#define O_RDWR 0X02 ///< Open for reading and writing. +#define O_AT_END 0X04 ///< Open at EOF. +#define O_APPEND 0X08 ///< Set append mode. +#define O_CREAT 0x10 ///< Create file if it does not exist. +#define O_TRUNC 0x20 ///< Truncate file to zero length. +#define O_EXCL 0x40 ///< Fail if the file exists. +#define O_SYNC 0x80 ///< Synchronized write I/O operations. + +#define O_ACCMODE (O_RDONLY|O_WRONLY|O_RDWR) ///< Mask for access mode. +typedef uint8_t oflag_t; +#endif // USE_FCNTL_H + +#define O_READ O_RDONLY +#define O_WRITE O_WRONLY + +inline bool isWriteMode(oflag_t oflag) { + oflag &= O_ACCMODE; + return oflag == O_WRONLY || oflag == O_RDWR; +} + +// flags for ls() +/** ls() flag for list all files including hidden. */ +const uint8_t LS_A = 1; +/** ls() flag to print modify. date */ +const uint8_t LS_DATE = 2; +/** ls() flag to print file size. */ +const uint8_t LS_SIZE = 4; +/** ls() flag for recursive list of subdirectories */ +const uint8_t LS_R = 8; + +// flags for time-stamp +/** set the file's last access date */ +const uint8_t T_ACCESS = 1; +/** set the file's creation date and time */ +const uint8_t T_CREATE = 2; +/** Set the file's write date and time */ +const uint8_t T_WRITE = 4; +#endif // FsApiConstants_h diff --git a/Firmware_V3/lib/SdFat/src/common/FsCache.cpp b/Firmware_V3/lib/SdFat/src/common/FsCache.cpp new file mode 100644 index 0000000..2ee269f --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/common/FsCache.cpp @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#define DBG_FILE "FsCache.cpp" +#include "DebugMacros.h" +#include "FsCache.h" +//------------------------------------------------------------------------------ +uint8_t* FsCache::get(uint32_t sector, uint8_t option) { + if (!m_blockDev) { + DBG_FAIL_MACRO; + goto fail; + } + if (m_sector != sector) { + if (!sync()) { + DBG_FAIL_MACRO; + goto fail; + } + if (!(option & CACHE_OPTION_NO_READ)) { + if (!m_blockDev->readSector(sector, m_buffer)) { + DBG_FAIL_MACRO; + goto fail; + } + } + m_status = 0; + m_sector = sector; + } + m_status |= option & CACHE_STATUS_MASK; + return m_buffer; + + fail: + return nullptr; +} +//------------------------------------------------------------------------------ +bool FsCache::sync() { + if (m_status & CACHE_STATUS_DIRTY) { + if (!m_blockDev->writeSector(m_sector, m_buffer)) { + DBG_FAIL_MACRO; + goto fail; + } + // mirror second FAT + if (m_status & CACHE_STATUS_MIRROR_FAT) { + uint32_t sector = m_sector + m_mirrorOffset; + if (!m_blockDev->writeSector(sector, m_buffer)) { + DBG_FAIL_MACRO; + goto fail; + } + } + m_status &= ~CACHE_STATUS_DIRTY; + } + return true; + + fail: + return false; +} diff --git a/Firmware_V3/lib/SdFat/src/common/FsCache.h b/Firmware_V3/lib/SdFat/src/common/FsCache.h new file mode 100644 index 0000000..a923221 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/common/FsCache.h @@ -0,0 +1,183 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef FsCache_h +#define FsCache_h +/** + * \file + * \brief Common cache code for exFAT and FAT. + */ +#include "SysCall.h" +#include "BlockDevice.h" +/** + * \class FsCache + * \brief Sector cache. + */ +class FsCache { + public: + /** Cached sector is dirty */ + static const uint8_t CACHE_STATUS_DIRTY = 1; + /** Cashed sector is FAT entry and must be mirrored in second FAT. */ + static const uint8_t CACHE_STATUS_MIRROR_FAT = 2; + /** Cache sector status bits */ + static const uint8_t CACHE_STATUS_MASK = + CACHE_STATUS_DIRTY | CACHE_STATUS_MIRROR_FAT; + /** Sync existing sector but do not read new sector. */ + static const uint8_t CACHE_OPTION_NO_READ = 4; + /** Cache sector for read. */ + static const uint8_t CACHE_FOR_READ = 0; + /** Cache sector for write. */ + static const uint8_t CACHE_FOR_WRITE = CACHE_STATUS_DIRTY; + /** Reserve cache sector for write - do not read from sector device. */ + static const uint8_t CACHE_RESERVE_FOR_WRITE = + CACHE_STATUS_DIRTY | CACHE_OPTION_NO_READ; + //---------------------------------------------------------------------------- + /** \return Cache buffer address. */ + uint8_t* cacheBuffer() { + return m_buffer; + } + /** + * Cache safe read of a sector. + * + * \param[in] sector Logical sector to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return true for success or false for failure. + */ + bool cacheSafeRead(uint32_t sector, uint8_t* dst) { + if (isCached(sector)) { + memcpy(dst, m_buffer, 512); + return true; + } + return m_blockDev->readSector(sector, dst); + } + /** + * Cache safe read of multiple sectors. + * + * \param[in] sector Logical sector to be read. + * \param[in] count Number of sectors to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return true for success or false for failure. + */ + bool cacheSafeRead(uint32_t sector, uint8_t* dst, size_t count) { + if (isCached(sector, count) && !sync()) { + return false; + } + return m_blockDev->readSectors(sector, dst, count); + } + /** + * Cache safe write of a sectors. + * + * \param[in] sector Logical sector to be written. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ + bool cacheSafeWrite(uint32_t sector, const uint8_t* src) { + if (isCached(sector)) { + invalidate(); + } + return m_blockDev->writeSector(sector, src); + } + /** + * Cache safe write of multiple sectors. + * + * \param[in] sector Logical sector to be written. + * \param[in] src Pointer to the location of the data to be written. + * \param[in] count Number of sectors to be written. + * \return true for success or false for failure. + */ + bool cacheSafeWrite(uint32_t sector, const uint8_t* src, size_t count) { + if (isCached(sector, count)) { + invalidate(); + } + return m_blockDev->writeSectors(sector, src, count); + } + /** \return Clear the cache and returns a pointer to the cache. */ + uint8_t* clear() { + if (isDirty() && !sync()) { + return nullptr; + } + invalidate(); + return m_buffer; + } + /** Set current sector dirty. */ + void dirty() { + m_status |= CACHE_STATUS_DIRTY; + } + /** Initialize the cache. + * \param[in] blockDev Block device for this cache. + */ + void init(BlockDevice* blockDev) { + m_blockDev = blockDev; + invalidate(); + } + /** Invalidate current cache sector. */ + void invalidate() { + m_status = 0; + m_sector = 0XFFFFFFFF; + } + /** Check if a sector is in the cache. + * \param[in] sector Sector to checked. + * \return true if the sector is cached. + */ + bool isCached(uint32_t sector) const {return sector == m_sector;} + /** Check if the cache contains a sector from a range. + * \param[in] sector Start sector of the range. + * \param[in] count Number of sectors in the range. + * \return true if a sector in the range is cached. + */ + bool isCached(uint32_t sector, size_t count) { + return sector <= m_sector && m_sector < (sector + count); + } + /** \return dirty status */ + bool isDirty() { + return m_status & CACHE_STATUS_DIRTY; + } + /** \return Logical sector number for cached sector. */ + uint32_t sector() { + return m_sector; + } + /** Set the offset to the second FAT for mirroring. + * \param[in] offset Sector offset to second FAT. + */ + void setMirrorOffset(uint32_t offset) { + m_mirrorOffset = offset; + } + /** Fill cache with sector data. + * \param[in] sector Sector to read. + * \param[in] option mode for cached sector. + * \return Address of cached sector. */ + uint8_t* get(uint32_t sector, uint8_t option); + /** Write current sector if dirty. + * \return true for success or false for failure. + */ + bool sync(); + + private: + uint8_t m_status; + BlockDevice* m_blockDev; + uint32_t m_mirrorOffset; + uint32_t m_sector; + uint8_t m_buffer[512]; +}; +#endif // FsCache_h diff --git a/Firmware_V3/lib/SdFat/src/common/FsDateTime.cpp b/Firmware_V3/lib/SdFat/src/common/FsDateTime.cpp new file mode 100644 index 0000000..50e792a --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/common/FsDateTime.cpp @@ -0,0 +1,175 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "SysCall.h" +#include "FsDateTime.h" +#include "FmtNumber.h" + +static void dateTimeMs10(uint16_t* date, uint16_t* time, uint8_t* ms10) { + *ms10 = 0; + FsDateTime::callback2(date, time); +} +//------------------------------------------------------------------------------ +/** Date time callback. */ +namespace FsDateTime { + void (*callback)(uint16_t* date, uint16_t* time, uint8_t* ms10) = nullptr; + void (*callback2)(uint16_t* date, uint16_t* time) = nullptr; + void clearCallback() { + callback = nullptr; + } + void setCallback(void (*dateTime)(uint16_t* date, uint16_t* time)) { + callback = dateTimeMs10; + callback2 = dateTime; + } + void setCallback( + void (*dateTime)(uint16_t* date, uint16_t* time, uint8_t* ms10)) { + callback = dateTime; + } +} // namespace FsDateTime +//------------------------------------------------------------------------------ +static char* fsFmtField(char* str, uint16_t n, char sep) { + if (sep) { + *--str = sep; + } + str = fmtBase10(str, n); + if (n < 10) { + *--str = '0'; + } + return str; +} +//------------------------------------------------------------------------------ +char* fsFmtDate(char* str, uint16_t date) { + str = fsFmtField(str, date & 31, 0); + date >>= 5; + str = fsFmtField(str, date & 15, '-'); + date >>= 4; + return fsFmtField(str, 1980 + date, '-'); +} +//------------------------------------------------------------------------------ +char* fsFmtTime(char* str, uint16_t time) { + time >>= 5; + str = fsFmtField(str, time & 63, 0); + return fsFmtField(str, time >> 6, ':'); +} +//------------------------------------------------------------------------------ +char* fsFmtTime(char* str, uint16_t time, uint8_t sec100) { + str = fsFmtField(str, 2*(time & 31) + (sec100 < 100 ? 0 : 1), 0); + *--str = ':'; + return fsFmtTime(str, time); +} +//------------------------------------------------------------------------------ +char* fsFmtTimeZone(char* str, int8_t tz) { + char sign; + if (tz & 0X80) { + if (tz & 0X40) { + sign = '-'; + tz = -tz; + } else { + sign = '+'; + tz &= 0X7F; + } + if (tz) { + str = fsFmtField(str, 15*(tz%4), 0); + str = fsFmtField(str, tz/4, ':'); + *--str = sign; + } + *--str = 'C'; + *--str = 'T'; + *--str = 'U'; + } + return str; +} +//------------------------------------------------------------------------------ +size_t fsPrintDate(print_t* pr, uint16_t date) { + // Allow YYYY-MM-DD + char buf[sizeof("YYYY-MM-DD") -1]; + char* str = buf + sizeof(buf); + if (date) { + str = fsFmtDate(str, date); + } else { + do { + *--str = ' '; + } while (str > buf); + } + return pr->write(reinterpret_cast(str), buf + sizeof(buf) - str); +} +//------------------------------------------------------------------------------ +size_t fsPrintDateTime(print_t* pr, uint16_t date, uint16_t time) { + // Allow YYYY-MM-DD hh:mm + char buf[sizeof("YYYY-MM-DD hh:mm") -1]; + char* str = buf + sizeof(buf); + if (date) { + str = fsFmtTime(str, time); + *--str = ' '; + str = fsFmtDate(str, date); + } else { + do { + *--str = ' '; + } while (str > buf); + } + return pr->write(reinterpret_cast(str), buf + sizeof(buf) - str); +} +//------------------------------------------------------------------------------ +size_t fsPrintDateTime(print_t* pr, uint32_t dateTime) { + return fsPrintDateTime(pr, dateTime >> 16, dateTime & 0XFFFF); +} +//------------------------------------------------------------------------------ +size_t fsPrintDateTime(print_t* pr, + uint32_t dateTime, uint8_t s100, int8_t tz) { + // Allow YYYY-MM-DD hh:mm:ss UTC+hh:mm + char buf[sizeof("YYYY-MM-DD hh:mm:ss UTC+hh:mm") -1]; + char* str = buf + sizeof(buf); + if (tz) { + str = fsFmtTimeZone(str, tz); + *--str = ' '; + } + str = fsFmtTime(str, (uint16_t)dateTime, s100); + *--str = ' '; + str = fsFmtDate(str, (uint16_t)(dateTime >> 16)); + return pr->write(reinterpret_cast(str), buf + sizeof(buf) - str); +} +//------------------------------------------------------------------------------ +size_t fsPrintTime(print_t* pr, uint16_t time) { + // Allow hh:mm + char buf[sizeof("hh:mm") -1]; + char* str = buf + sizeof(buf); + str = fsFmtTime(str, time); + return pr->write(reinterpret_cast(str), buf + sizeof(buf) - str); +} +//------------------------------------------------------------------------------ +size_t fsPrintTime(print_t* pr, uint16_t time, uint8_t sec100) { + // Allow hh:mm:ss + char buf[sizeof("hh:mm:ss") -1]; + char* str = buf + sizeof(buf); + str = fsFmtTime(str, time, sec100); + return pr->write(reinterpret_cast(str), buf + sizeof(buf) - str); +} +//------------------------------------------------------------------------------ +size_t fsPrintTimeZone(print_t* pr, int8_t tz) { + // Allow UTC+hh:mm + char buf[sizeof("UTC+hh:mm") -1]; + char* str = buf + sizeof(buf); + str = fsFmtTimeZone(str, tz); + return pr->write(reinterpret_cast(str), buf + sizeof(buf) - str); +} diff --git a/Firmware_V3/lib/SdFat/src/common/FsDateTime.h b/Firmware_V3/lib/SdFat/src/common/FsDateTime.h new file mode 100644 index 0000000..b614eac --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/common/FsDateTime.h @@ -0,0 +1,193 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef FsDateTime_h +#define FsDateTime_h +#include +#include "CompileDateTime.h" +#include "SysCall.h" + +/** Backward compatible definition. */ +#define FAT_DATE(y, m, d) FS_DATE(y, m, d) + +/** Backward compatible definition. */ +#define FAT_TIME(h, m, s) FS_TIME(h, m, s) + +/** Date time callback */ +namespace FsDateTime { + /** Date time callback. */ + extern void (*callback)(uint16_t* date, uint16_t* time, uint8_t* ms10); + /** Date time callback. */ + extern void (*callback2)(uint16_t* date, uint16_t* time); + /** Cancel callback. */ + void clearCallback(); + /** Set the date/time callback function. + * + * \param[in] dateTime The user's call back function. The callback. + * function is of the form: + * + * \code + * void dateTime(uint16_t* date, uint16_t* time) { + * uint16_t year; + * uint8_t month, day, hour, minute, second; + * + * // User gets date and time from GPS or real-time clock here. + * + * // Return date using FS_DATE macro to format fields. + * *date = FS_DATE(year, month, day); + * + * // Return time using FS_TIME macro to format fields. + * *time = FS_TIME(hour, minute, second); + * } + * \endcode + * + * Sets the function that is called when a file is created or when + * a file's directory entry is modified by sync(). All timestamps, + * access, creation, and modify, are set when a file is created. + * sync() maintains the last access date and last modify date/time. + * + */ + void setCallback(void (*dateTime)(uint16_t* date, uint16_t* time)); + /** Set the date/time callback function. + * + * \param[in] dateTime The user's call back function. The callback + * function is of the form: + * + * \code + * void dateTime(uint16_t* date, uint16_t* time, uint8_t* ms10) { + * uint16_t year; + * uint8_t month, day, hour, minute, second; + * + * // User gets date and time from GPS or real-time clock here. + * + * // Return date using FS_DATE macro to format fields + * *date = FS_DATE(year, month, day); + * + * // Return time using FS_TIME macro to format fields + * *time = FS_TIME(hour, minute, second); + * + * // Return the time since the last even second in units of 10 ms. + * // The granularity of the seconds part of FS_TIME is 2 seconds so + * // this field is a count of hundredth of a second and its valid + * // range is 0-199 inclusive. + * // For a simple RTC return 100*(seconds & 1). + * *ms10 = + * } + * \endcode + * + * Sets the function that is called when a file is created or when + * a file's directory entry is modified by sync(). All timestamps, + * access, creation, and modify, are set when a file is created. + * sync() maintains the last access date and last modify date/time. + * + */ + void setCallback( + void (*dateTime)(uint16_t* date, uint16_t* time, uint8_t* ms10)); +} // namespace FsDateTime + +/** date field for directory entry + * \param[in] year [1980,2107] + * \param[in] month [1,12] + * \param[in] day [1,31] + * + * \return Packed date for directory entry. + */ +static inline uint16_t FS_DATE(uint16_t year, uint8_t month, uint8_t day) { + year -= 1980; + return year > 127 || month > 12 || day > 31 ? 0 : + year << 9 | month << 5 | day; +} +/** year part of FAT directory date field + * \param[in] fatDate Date in packed dir format. + * + * \return Extracted year [1980,2107] + */ +static inline uint16_t FS_YEAR(uint16_t fatDate) { + return 1980 + (fatDate >> 9); +} +/** month part of FAT directory date field + * \param[in] fatDate Date in packed dir format. + * + * \return Extracted month [1,12] + */ +static inline uint8_t FS_MONTH(uint16_t fatDate) { + return (fatDate >> 5) & 0XF; +} +/** day part of FAT directory date field + * \param[in] fatDate Date in packed dir format. + * + * \return Extracted day [1,31] + */ +static inline uint8_t FS_DAY(uint16_t fatDate) { + return fatDate & 0X1F; +} +/** time field for directory entry + * \param[in] hour [0,23] + * \param[in] minute [0,59] + * \param[in] second [0,59] + * + * \return Packed time for directory entry. + */ +static inline uint16_t FS_TIME(uint8_t hour, uint8_t minute, uint8_t second) { + return hour > 23 || minute > 59 || second > 59 ? 0 : + hour << 11 | minute << 5 | second >> 1; +} +/** hour part of FAT directory time field + * \param[in] fatTime Time in packed dir format. + * + * \return Extracted hour [0,23] + */ +static inline uint8_t FS_HOUR(uint16_t fatTime) { + return fatTime >> 11; +} +/** minute part of FAT directory time field + * \param[in] fatTime Time in packed dir format. + * + * \return Extracted minute [0,59] + */ +static inline uint8_t FS_MINUTE(uint16_t fatTime) { + return (fatTime >> 5) & 0X3F; +} +/** second part of FAT directory time field + * N\note second/2 is stored in packed time. + * + * \param[in] fatTime Time in packed dir format. + * + * \return Extracted second [0,58] + */ +static inline uint8_t FS_SECOND(uint16_t fatTime) { + return 2*(fatTime & 0X1F); +} +char* fsFmtDate(char* str, uint16_t date); +char* fsFmtTime(char* str, uint16_t time); +char* fsFmtTime(char* str, uint16_t time, uint8_t sec100); +char* fsFmtTimeZone(char* str, int8_t tz); +size_t fsPrintDate(print_t* pr, uint16_t date); +size_t fsPrintDateTime(print_t* pr, uint16_t date, uint16_t time); +size_t fsPrintDateTime(print_t* pr, uint32_t dateTime); +size_t fsPrintDateTime(print_t* pr, uint32_t dateTime, uint8_t s100, int8_t tz); +size_t fsPrintTime(print_t* pr, uint16_t time); +size_t fsPrintTime(print_t* pr, uint16_t time, uint8_t sec100); +size_t fsPrintTimeZone(print_t* pr, int8_t tz); +#endif // FsDateTime_h diff --git a/Firmware_V3/lib/SdFat/src/common/FsStructs.cpp b/Firmware_V3/lib/SdFat/src/common/FsStructs.cpp new file mode 100644 index 0000000..5d434ee --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/common/FsStructs.cpp @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "FsStructs.h" +// bgnLba = relSector; +// endLba = relSector + partSize - 1; +void lbaToMbrChs(uint8_t* chs, uint32_t capacityMB, uint32_t lba) { + uint32_t c; + uint8_t h; + uint8_t s; + + uint8_t numberOfHeads; + uint8_t sectorsPerTrack = capacityMB <= 256 ? 32 : 63; + if (capacityMB <= 16) { + numberOfHeads = 2; + } else if (capacityMB <= 32) { + numberOfHeads = 4; + } else if (capacityMB <= 128) { + numberOfHeads = 8; + } else if (capacityMB <= 504) { + numberOfHeads = 16; + } else if (capacityMB <= 1008) { + numberOfHeads = 32; + } else if (capacityMB <= 2016) { + numberOfHeads = 64; + } else if (capacityMB <= 4032) { + numberOfHeads = 128; + } else { + numberOfHeads = 255; + } + c = lba / (numberOfHeads * sectorsPerTrack); + if (c <= 1023) { + h = (lba % (numberOfHeads * sectorsPerTrack)) / sectorsPerTrack; + s = (lba % sectorsPerTrack) + 1; + } else { + c = 1023; + h = 254; + s = 63; + } + chs[0] = h; + chs[1] = ((c >> 2) & 0XC0) | s; + chs[2] = c; +} diff --git a/Firmware_V3/lib/SdFat/src/common/FsStructs.h b/Firmware_V3/lib/SdFat/src/common/FsStructs.h new file mode 100644 index 0000000..0a65bff --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/common/FsStructs.h @@ -0,0 +1,384 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef FsStructs_h +#define FsStructs_h +#include +//----------------------------------------------------------------------------- +void lbaToMbrChs(uint8_t* chs, uint32_t capacityMB, uint32_t lba); +//----------------------------------------------------------------------------- +#if !defined(USE_SIMPLE_LITTLE_ENDIAN) || USE_SIMPLE_LITTLE_ENDIAN +// assumes CPU is little-endian and handles alignment issues. +inline uint16_t getLe16(const uint8_t* src) { + return *reinterpret_cast(src); +} +inline uint32_t getLe32(const uint8_t* src) { + return *reinterpret_cast(src); +} +inline uint64_t getLe64(const uint8_t* src) { + return *reinterpret_cast(src); +} +inline void setLe16(uint8_t* dst, uint16_t src) { + *reinterpret_cast(dst) = src; +} + +inline void setLe32(uint8_t* dst, uint32_t src) { + *reinterpret_cast(dst) = src; +} +inline void setLe64(uint8_t* dst, uint64_t src) { + *reinterpret_cast(dst) = src; +} +#else // USE_SIMPLE_LITTLE_ENDIAN +inline uint16_t getLe16(const uint8_t* src) { + return (uint16_t)src[0] << 0 | + (uint16_t)src[1] << 8; +} +inline uint32_t getLe32(const uint8_t* src) { + return (uint32_t)src[0] << 0 | + (uint32_t)src[1] << 8 | + (uint32_t)src[2] << 16 | + (uint32_t)src[3] << 24; +} +inline uint64_t getLe64(const uint8_t* src) { + return (uint64_t)src[0] << 0 | + (uint64_t)src[1] << 8 | + (uint64_t)src[2] << 16 | + (uint64_t)src[3] << 24 | + (uint64_t)src[4] << 32 | + (uint64_t)src[5] << 40 | + (uint64_t)src[6] << 48 | + (uint64_t)src[7] << 56; +} +inline void setLe16(uint8_t* dst, uint16_t src) { + dst[0] = src >> 0; + dst[1] = src >> 8; +} +inline void setLe32(uint8_t* dst, uint32_t src) { + dst[0] = src >> 0; + dst[1] = src >> 8; + dst[2] = src >> 16; + dst[3] = src >> 24; +} +inline void setLe64(uint8_t* dst, uint64_t src) { + dst[0] = src >> 0; + dst[1] = src >> 8; + dst[2] = src >> 16; + dst[3] = src >> 24; + dst[4] = src >> 32; + dst[5] = src >> 40; + dst[6] = src >> 48; + dst[7] = src >> 56; +} +#endif // USE_SIMPLE_LITTLE_ENDIAN +//----------------------------------------------------------------------------- +const uint16_t MBR_SIGNATURE = 0xAA55; +const uint16_t PBR_SIGNATURE = 0xAA55; + +typedef struct mbrPartition { + uint8_t boot; + uint8_t beginCHS[3]; + uint8_t type; + uint8_t endCHS[3]; + uint8_t relativeSectors[4]; + uint8_t totalSectors[4]; +} MbrPart_t; +//----------------------------------------------------------------------------- +typedef struct masterBootRecordSector { + uint8_t bootCode[446]; + MbrPart_t part[4]; + uint8_t signature[2]; +} MbrSector_t; +//----------------------------------------------------------------------------- +typedef struct partitionBootSector { + uint8_t jmpInstruction[3]; + char oemName[8]; + uint8_t bpb[109]; + uint8_t bootCode[390]; + uint8_t signature[2]; +} pbs_t; +//----------------------------------------------------------------------------- +typedef struct { + uint8_t type; + uint8_t data[31]; +} DirGeneric_t; +//============================================================================= +typedef struct { + uint64_t position; + uint32_t cluster; +} fspos_t; +//============================================================================= +const uint8_t EXTENDED_BOOT_SIGNATURE = 0X29; +typedef struct biosParameterBlockFat16 { + uint8_t bytesPerSector[2]; + uint8_t sectorsPerCluster; + uint8_t reservedSectorCount[2]; + uint8_t fatCount; + uint8_t rootDirEntryCount[2]; + uint8_t totalSectors16[2]; + uint8_t mediaType; + uint8_t sectorsPerFat16[2]; + uint8_t sectorsPerTrtack[2]; + uint8_t headCount[2]; + uint8_t hidddenSectors[4]; + uint8_t totalSectors32[4]; + + uint8_t physicalDriveNumber; + uint8_t extReserved; + uint8_t extSignature; + uint8_t volumeSerialNumber[4]; + uint8_t volumeLabel[11]; + uint8_t volumeType[8]; +} BpbFat16_t; +//----------------------------------------------------------------------------- +typedef struct biosParameterBlockFat32 { + uint8_t bytesPerSector[2]; + uint8_t sectorsPerCluster; + uint8_t reservedSectorCount[2]; + uint8_t fatCount; + uint8_t rootDirEntryCount[2]; + uint8_t totalSectors16[2]; + uint8_t mediaType; + uint8_t sectorsPerFat16[2]; + uint8_t sectorsPerTrtack[2]; + uint8_t headCount[2]; + uint8_t hidddenSectors[4]; + uint8_t totalSectors32[4]; + + uint8_t sectorsPerFat32[4]; + uint8_t fat32Flags[2]; + uint8_t fat32Version[2]; + uint8_t fat32RootCluster[4]; + uint8_t fat32FSInfoSector[2]; + uint8_t fat32BackBootSector[2]; + uint8_t fat32Reserved[12]; + + uint8_t physicalDriveNumber; + uint8_t extReserved; + uint8_t extSignature; + uint8_t volumeSerialNumber[4]; + uint8_t volumeLabel[11]; + uint8_t volumeType[8]; +} BpbFat32_t; +//----------------------------------------------------------------------------- +typedef struct partitionBootSectorFat { + uint8_t jmpInstruction[3]; + char oemName[8]; + union { + uint8_t bpb[109]; + BpbFat16_t bpb16; + BpbFat32_t bpb32; + } bpb; + uint8_t bootCode[390]; + uint8_t signature[2]; +} PbsFat_t; +//----------------------------------------------------------------------------- +const uint32_t FSINFO_LEAD_SIGNATURE = 0X41615252; +const uint32_t FSINFO_STRUCT_SIGNATURE = 0x61417272; +const uint32_t FSINFO_TRAIL_SIGNATURE = 0xAA550000; +typedef struct FsInfoSector { + uint8_t leadSignature[4]; + uint8_t reserved1[480]; + uint8_t structSignature[4]; + uint8_t freeCount[4]; + uint8_t nextFree[4]; + uint8_t reserved2[12]; + uint8_t trailSignature[4]; +} FsInfo_t; +//----------------------------------------------------------------------------- +/** name[0] value for entry that is free after being "deleted" */ +const uint8_t FAT_NAME_DELETED = 0XE5; +/** name[0] value for entry that is free and no allocated entries follow */ +const uint8_t FAT_NAME_FREE = 0X00; +const uint8_t FAT_ATTRIB_READ_ONLY = 0x01; +const uint8_t FAT_ATTRIB_HIDDEN = 0x02; +const uint8_t FAT_ATTRIB_SYSTEM = 0x04; +const uint8_t FAT_ATTRIB_LABEL = 0x08; +const uint8_t FAT_ATTRIB_DIRECTORY = 0x10; +const uint8_t FAT_ATTRIB_ARCHIVE = 0x20; +const uint8_t FAT_ATTRIB_LONG_NAME = 0X0F; +/** Filename base-name is all lower case */ +const uint8_t FAT_CASE_LC_BASE = 0X08; +/** Filename extension is all lower case.*/ +const uint8_t FAT_CASE_LC_EXT = 0X10; + +typedef struct { + uint8_t name[11]; + uint8_t attributes; + uint8_t caseFlags; + uint8_t createTimeMs; + uint8_t createTime[2]; + uint8_t createDate[2]; + uint8_t accessDate[2]; + uint8_t firstClusterHigh[2]; + uint8_t modifyTime[2]; + uint8_t modifyDate[2]; + uint8_t firstClusterLow[2]; + uint8_t fileSize[4]; +} DirFat_t; + +static inline bool isFileDir(const DirFat_t* dir) { + return (dir->attributes & (FAT_ATTRIB_DIRECTORY | FAT_ATTRIB_LABEL)) == 0; +} +static inline bool isFileOrSubdir(const DirFat_t* dir) { + return (dir->attributes & FAT_ATTRIB_LABEL) == 0; +} +static inline uint8_t isLongName(const DirFat_t* dir) { + return dir->attributes == FAT_ATTRIB_LONG_NAME; +} +static inline bool isSubdir(const DirFat_t* dir) { + return (dir->attributes & (FAT_ATTRIB_DIRECTORY | FAT_ATTRIB_LABEL)) + == FAT_ATTRIB_DIRECTORY; +} +//----------------------------------------------------------------------------- +/** + * Order mask that indicates the entry is the last long dir entry in a + * set of long dir entries. All valid sets of long dir entries must + * begin with an entry having this mask. + */ +const uint8_t FAT_ORDER_LAST_LONG_ENTRY = 0X40; + +typedef struct { + uint8_t order; + uint8_t unicode1[10]; + uint8_t attributes; + uint8_t mustBeZero1; + uint8_t checksum; + uint8_t unicode2[12]; + uint8_t mustBeZero2[2]; + uint8_t unicode3[4]; +} DirLfn_t; +//============================================================================= +inline uint32_t exFatChecksum(uint32_t sum, uint8_t data) { + return (sum << 31) + (sum >> 1) + data; +} +//----------------------------------------------------------------------------- +typedef struct biosParameterBlockExFat { + uint8_t mustBeZero[53]; + uint8_t partitionOffset[8]; + uint8_t volumeLength[8]; + uint8_t fatOffset[4]; + uint8_t fatLength[4]; + uint8_t clusterHeapOffset[4]; + uint8_t clusterCount[4]; + uint8_t rootDirectoryCluster[4]; + uint8_t volumeSerialNumber[4]; + uint8_t fileSystemRevision[2]; + uint8_t volumeFlags[2]; + uint8_t bytesPerSectorShift; + uint8_t sectorsPerClusterShift; + uint8_t numberOfFats; + uint8_t driveSelect; + uint8_t percentInUse; + uint8_t reserved[7]; +} BpbExFat_t; +//----------------------------------------------------------------------------- +typedef struct ExFatBootSector { + uint8_t jmpInstruction[3]; + char oemName[8]; + BpbExFat_t bpb; + uint8_t bootCode[390]; + uint8_t signature[2]; +} ExFatPbs_t; +//----------------------------------------------------------------------------- +const uint32_t EXFAT_EOC = 0XFFFFFFFF; + +const uint8_t EXFAT_TYPE_BITMAP = 0X81; +typedef struct { + uint8_t type; + uint8_t flags; + uint8_t reserved[18]; + uint8_t firstCluster[4]; + uint8_t size[8]; +} DirBitmap_t; +//----------------------------------------------------------------------------- +const uint8_t EXFAT_TYPE_UPCASE = 0X82; +typedef struct { + uint8_t type; + uint8_t reserved1[3]; + uint8_t checksum[4]; + uint8_t reserved2[12]; + uint8_t firstCluster[4]; + uint8_t size[8]; +} DirUpcase_t; +//----------------------------------------------------------------------------- +const uint8_t EXFAT_TYPE_LABEL = 0X83; +typedef struct { + uint8_t type; + uint8_t labelLength; + uint8_t unicode[22]; + uint8_t reserved[8]; +} DirLabel_t; +//----------------------------------------------------------------------------- +const uint8_t EXFAT_TYPE_FILE = 0X85; +const uint8_t EXFAT_ATTRIB_READ_ONLY = 0x01; +const uint8_t EXFAT_ATTRIB_HIDDEN = 0x02; +const uint8_t EXFAT_ATTRIB_SYSTEM = 0x04; +const uint8_t EXFAT_ATTRIB_RESERVED = 0x08; +const uint8_t EXFAT_ATTRIB_DIRECTORY = 0x10; +const uint8_t EXFAT_ATTRIB_ARCHIVE = 0x20; + +typedef struct { + uint8_t type; + uint8_t setCount; + uint8_t setChecksum[2]; + uint8_t attributes[2]; + uint8_t reserved1[2]; + uint8_t createTime[2]; + uint8_t createDate[2]; + uint8_t modifyTime[2]; + uint8_t modifyDate[2]; + uint8_t accessTime[2]; + uint8_t accessDate[2]; + uint8_t createTimeMs; + uint8_t modifyTimeMs; + uint8_t createTimezone; + uint8_t modifyTimezone; + uint8_t accessTimezone; + uint8_t reserved2[7]; +} DirFile_t; + +const uint8_t EXFAT_TYPE_STREAM = 0XC0; +const uint8_t EXFAT_FLAG_ALWAYS1 = 0x01; +const uint8_t EXFAT_FLAG_CONTIGUOUS = 0x02; +typedef struct { + uint8_t type; + uint8_t flags; + uint8_t reserved1; + uint8_t nameLength; + uint8_t nameHash[2]; + uint8_t reserved2[2]; + uint8_t validLength[8]; + uint8_t reserved3[4]; + uint8_t firstCluster[4]; + uint8_t dataLength[8]; +} DirStream_t; + +const uint8_t EXFAT_TYPE_NAME = 0XC1; +const uint8_t EXFAT_MAX_NAME_LENGTH = 255; +typedef struct { + uint8_t type; + uint8_t mustBeZero; + uint8_t unicode[30]; +} DirName_t; +#endif // FsStructs_h diff --git a/Firmware_V3/lib/SdFat/src/common/SysCall.h b/Firmware_V3/lib/SdFat/src/common/SysCall.h new file mode 100644 index 0000000..702f6d1 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/common/SysCall.h @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief SysCall class + */ +#ifndef SysCall_h +#define SysCall_h +#include +#include +#include "../SdFatConfig.h" +#if __cplusplus < 201103 +#warning nullptr defined +/** Define nullptr if not C++11 */ +#define nullptr NULL +#endif // __cplusplus < 201103 +//------------------------------------------------------------------------------ +/** Type for millis. */ +typedef uint16_t SdMillis_t; +//------------------------------------------------------------------------------ +/** + * \class SysCall + * \brief SysCall - Class to wrap system calls. + */ +class SysCall { + public: + /** \return the time in milliseconds. */ + static SdMillis_t curTimeMS(); + /** Halt execution of this thread. */ + static void halt() { + while (1) { + yield(); + } + } + /** Yield to other threads. */ + static void yield(); +}; +#if ENABLE_ARDUINO_FEATURES +#if defined(ARDUINO) +/** Use Arduino Print. */ +typedef Print print_t; +/** Use Arduino Stream. */ +typedef Stream stream_t; +#else // defined(ARDUINO) +#error "Unknown system" +#endif // defined(ARDUINO) +//------------------------------------------------------------------------------ +#ifndef F +/** Define macro for strings stored in flash. */ +#define F(str) (str) +#endif // F +//------------------------------------------------------------------------------ +/** \return the time in milliseconds. */ +inline SdMillis_t SysCall::curTimeMS() { + return millis(); +} +//------------------------------------------------------------------------------ +#if defined(PLATFORM_ID) // Only defined if a Particle device +inline void SysCall::yield() { + // Recommended to only call Particle.process() if system threading is disabled + if (system_thread_get_state(NULL) == spark::feature::DISABLED) { + Particle.process(); + } +} +#elif defined(ARDUINO) +inline void SysCall::yield() { + // Use the external Arduino yield() function. + ::yield(); +} +#else // defined(PLATFORM_ID) +inline void SysCall::yield() {} +#endif // defined(PLATFORM_ID) +//------------------------------------------------------------------------------ +#else // ENABLE_ARDUINO_FEATURES +#error Print not defined +#include "PrintBasic.h" +/** If not Arduino */ +typedef PrintBasic print_t; +/** If not Arduino */ +typedef PrintBasic stream_t; +inline void SysCall::yield() {} +#endif // ENABLE_ARDUINO_FEATURES +#endif // SysCall_h diff --git a/Firmware_V3/lib/SdFat/src/iostream/ArduinoStream.h b/Firmware_V3/lib/SdFat/src/iostream/ArduinoStream.h new file mode 100644 index 0000000..4e1ccac --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/iostream/ArduinoStream.h @@ -0,0 +1,151 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef ArduinoStream_h +#define ArduinoStream_h +/** + * \file + * \brief ArduinoInStream and ArduinoOutStream classes + */ +#include "SdFatConfig.h" +#include "bufstream.h" +//============================================================================== +/** + * \class ArduinoInStream + * \brief Input stream for Arduino Stream objects + */ +class ArduinoInStream : public ibufstream { + public: + /** + * Constructor + * \param[in] hws hardware stream + * \param[in] buf buffer for input line + * \param[in] size size of input buffer + */ + ArduinoInStream(Stream &hws, char* buf, size_t size) { + m_hw = &hws; + m_line = buf; + m_size = size; + } + /** read a line. */ + void readline() { + size_t i = 0; + uint32_t t; + m_line[0] = '\0'; + while (!m_hw->available()) { + SysCall::yield(); + } + + while (1) { + t = millis(); + while (!m_hw->available()) { + if ((millis() - t) > 10) { + goto done; + } + } + if (i >= (m_size - 1)) { + setstate(failbit); + return; + } + m_line[i++] = m_hw->read(); + m_line[i] = '\0'; + } +done: + init(m_line); + } + + protected: + /** Internal - do not use. + * \param[in] off + * \param[in] way + * \return true/false. + */ + bool seekoff(off_type off, seekdir way) { + (void)off; + (void)way; + return false; + } + /** Internal - do not use. + * \param[in] pos + * \return true/false. + */ + bool seekpos(pos_type pos) { + (void)pos; + return false; + } + + private: + char *m_line; + size_t m_size; + Stream* m_hw; +}; +//============================================================================== +/** + * \class ArduinoOutStream + * \brief Output stream for Arduino Print objects + */ +class ArduinoOutStream : public ostream { + public: + /** constructor + * + * \param[in] pr Print object for this ArduinoOutStream. + */ + explicit ArduinoOutStream(Print& pr) : m_pr(&pr) {} + + protected: + /// @cond SHOW_PROTECTED + /** + * Internal do not use + * \param[in] c + */ + void putch(char c) { + if (c == '\n') { + m_pr->write('\r'); + } + m_pr->write(c); + } + void putstr(const char* str) { + m_pr->write(str); + } + bool seekoff(off_type off, seekdir way) { + (void)off; + (void)way; + return false; + } + bool seekpos(pos_type pos) { + (void)pos; + return false; + } + bool sync() { + return true; + } + pos_type tellpos() { + return 0; + } + /// @endcond + private: + ArduinoOutStream() {} + Print* m_pr; +}; +#endif // ArduinoStream_h diff --git a/Firmware_V3/lib/SdFat/src/iostream/StdioStream.cpp b/Firmware_V3/lib/SdFat/src/iostream/StdioStream.cpp new file mode 100644 index 0000000..cc36cb6 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/iostream/StdioStream.cpp @@ -0,0 +1,451 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "StdioStream.h" +#include "../common/FmtNumber.h" +//------------------------------------------------------------------------------ +int StdioStream::fclose() { + int rtn = 0; + if (!m_status) { + return EOF; + } + if (m_status & S_SWR) { + if (!flushBuf()) { + rtn = EOF; + } + } + if (!StreamBaseFile::close()) { + rtn = EOF; + } + m_r = 0; + m_w = 0; + m_status = 0; + return rtn; +} +//------------------------------------------------------------------------------ +int StdioStream::fflush() { + if ((m_status & (S_SWR | S_SRW)) && !(m_status & S_SRD)) { + if (flushBuf() && StreamBaseFile::sync()) { + return 0; + } + } + return EOF; +} +//------------------------------------------------------------------------------ +char* StdioStream::fgets(char* str, size_t num, size_t* len) { + char* s = str; + size_t n; + if (num-- == 0) { + return 0; + } + while (num) { + if ((n = m_r) == 0) { + if (!fillBuf()) { + if (s == str) { + return 0; + } + break; + } + n = m_r; + } + if (n > num) { + n = num; + } + uint8_t* end = reinterpret_cast(memchr(m_p, '\n', n)); + if (end != 0) { + n = ++end - m_p; + memcpy(s, m_p, n); + m_r -= n; + m_p = end; + s += n; + break; + } + memcpy(s, m_p, n); + m_r -= n; + m_p += n; + s += n; + num -= n; + } + *s = 0; + if (len) { + *len = s - str; + } + return str; +} +//------------------------------------------------------------------------------ +bool StdioStream::fopen(const char* path, const char* mode) { + oflag_t oflag; + uint8_t m; + switch (*mode++) { + case 'a': + m = O_WRONLY; + oflag = O_CREAT | O_APPEND; + m_status = S_SWR; + break; + + case 'r': + m = O_RDONLY; + oflag = 0; + m_status = S_SRD; + break; + + case 'w': + m = O_WRONLY; + oflag = O_CREAT | O_TRUNC; + m_status = S_SWR; + break; + + default: + goto fail; + } + while (*mode) { + switch (*mode++) { + case '+': + m_status = S_SRW; + m = O_RDWR; + break; + + case 'b': + break; + + case 'x': + oflag |= O_EXCL; + break; + + default: + goto fail; + } + } + oflag |= m; + if (!StreamBaseFile::open(path, oflag)) { + goto fail; + } + m_r = 0; + m_w = 0; + m_p = m_buf; + return true; + + fail: + m_status = 0; + return false; +} +//------------------------------------------------------------------------------ +int StdioStream::fputs(const char* str) { + size_t len = strlen(str); + return fwrite(str, 1, len) == len ? len : EOF; +} +//------------------------------------------------------------------------------ +size_t StdioStream::fread(void* ptr, size_t size, size_t count) { + uint8_t* dst = reinterpret_cast(ptr); + size_t total = size*count; + if (total == 0) { + return 0; + } + size_t need = total; + while (need > m_r) { + memcpy(dst, m_p, m_r); + dst += m_r; + m_p += m_r; + need -= m_r; + if (!fillBuf()) { + return (total - need)/size; + } + } + memcpy(dst, m_p, need); + m_r -= need; + m_p += need; + return count; +} +//------------------------------------------------------------------------------ +int StdioStream::fseek(int32_t offset, int origin) { + int32_t pos; + if (m_status & S_SWR) { + if (!flushBuf()) { + goto fail; + } + } + switch (origin) { + case SEEK_CUR: + pos = ftell(); + if (pos < 0) { + goto fail; + } + pos += offset; + if (!StreamBaseFile::seekCur(pos)) { + goto fail; + } + break; + + case SEEK_SET: + if (!StreamBaseFile::seekSet(offset)) { + goto fail; + } + break; + + case SEEK_END: + if (!StreamBaseFile::seekEnd(offset)) { + goto fail; + } + break; + + default: + goto fail; + } + m_r = 0; + m_p = m_buf; + return 0; + + fail: + return EOF; +} +//------------------------------------------------------------------------------ +int32_t StdioStream::ftell() { + uint32_t pos = StreamBaseFile::curPosition(); + if (m_status & S_SRD) { + if (m_r > pos) { + return -1L; + } + pos -= m_r; + } else if (m_status & S_SWR) { + pos += m_p - m_buf; + } + return pos; +} +//------------------------------------------------------------------------------ +size_t StdioStream::fwrite(const void* ptr, size_t size, size_t count) { + return write(ptr, count*size) < 0 ? EOF : count; +} +//------------------------------------------------------------------------------ +int StdioStream::write(const void* buf, size_t count) { + const uint8_t* src = static_cast(buf); + size_t todo = count; + + while (todo > m_w) { + memcpy(m_p, src, m_w); + m_p += m_w; + src += m_w; + todo -= m_w; + if (!flushBuf()) { + return EOF; + } + } + memcpy(m_p, src, todo); + m_p += todo; + m_w -= todo; + return count; +} +//------------------------------------------------------------------------------ +#if (defined(ARDUINO) && ENABLE_ARDUINO_FEATURES) || defined(DOXYGEN) +size_t StdioStream::print(const __FlashStringHelper *str) { + const char *p = (const char*)str; + uint8_t c; + while ((c = pgm_read_byte(p))) { + if (putc(c) < 0) { + return 0; + } + p++; + } + return p - (const char*)str; +} +#endif // (defined(ARDUINO) && ENABLE_ARDUINO_FEATURES) || defined(DOXYGEN) +//------------------------------------------------------------------------------ +int StdioStream::printDec(float value, uint8_t prec) { + char buf[24]; + char *ptr = fmtDouble(buf + sizeof(buf), value, prec, false); + return write(ptr, buf + sizeof(buf) - ptr); +} +//------------------------------------------------------------------------------ +int StdioStream::printDec(signed char n) { + if (n < 0) { + if (fputc('-') < 0) { + return -1; + } + n = -n; + } + return printDec((unsigned char)n); +} +//------------------------------------------------------------------------------ +int StdioStream::printDec(int16_t n) { + int s; + uint8_t rtn = 0; + if (n < 0) { + if (fputc('-') < 0) { + return -1; + } + n = -n; + rtn++; + } + if ((s = printDec((uint16_t)n)) < 0) { + return s; + } + return rtn; +} +//------------------------------------------------------------------------------ +int StdioStream::printDec(uint16_t n) { + char buf[5]; + char *ptr = fmtBase10(buf + sizeof(buf), n); + uint8_t len = buf + sizeof(buf) - ptr; + return write(ptr, len); +} +//------------------------------------------------------------------------------ +int StdioStream::printDec(int32_t n) { + uint8_t s = 0; + if (n < 0) { + if (fputc('-') < 0) { + return -1; + } + n = -n; + s = 1; + } + int rtn = printDec((uint32_t)n); + return rtn > 0 ? rtn + s : -1; +} +//------------------------------------------------------------------------------ +int StdioStream::printDec(uint32_t n) { + char buf[10]; + char *ptr = fmtBase10(buf + sizeof(buf), n); + uint8_t len = buf + sizeof(buf) - ptr; + return write(ptr, len); +} +//------------------------------------------------------------------------------ +int StdioStream::printHex(uint32_t n) { + char buf[8]; + char *ptr = fmtHex(buf + sizeof(buf), n); + uint8_t len = buf + sizeof(buf) - ptr; + return write(ptr, len); +} +//------------------------------------------------------------------------------ +bool StdioStream::rewind() { + if (m_status & S_SWR) { + if (!flushBuf()) { + return false; + } + } + StreamBaseFile::seekSet(0); + m_r = 0; + return true; +} +//------------------------------------------------------------------------------ +int StdioStream::ungetc(int c) { + // error if EOF. + if (c == EOF) { + return EOF; + } + // error if not reading. + if ((m_status & S_SRD) == 0) { + return EOF; + } + // error if no space. + if (m_p == m_buf) { + return EOF; + } + m_r++; + m_status &= ~S_EOF; + return *--m_p = (uint8_t)c; +} +//============================================================================== +// private +//------------------------------------------------------------------------------ +int StdioStream::fillGet() { + if (!fillBuf()) { + return EOF; + } + m_r--; + return *m_p++; +} +//------------------------------------------------------------------------------ +// private +bool StdioStream::fillBuf() { + if (!(m_status & + S_SRD)) { // check for S_ERR and S_EOF ??///////////////// + if (!(m_status & S_SRW)) { + m_status |= S_ERR; + return false; + } + if (m_status & S_SWR) { + if (!flushBuf()) { + return false; + } + m_status &= ~S_SWR; + m_status |= S_SRD; + m_w = 0; + } + } + m_p = m_buf + UNGETC_BUF_SIZE; + int nr = StreamBaseFile::read(m_p, sizeof(m_buf) - UNGETC_BUF_SIZE); + if (nr <= 0) { + m_status |= nr < 0 ? S_ERR : S_EOF; + m_r = 0; + return false; + } + m_r = nr; + return true; +} +//------------------------------------------------------------------------------ +// private +bool StdioStream::flushBuf() { + if (!(m_status & + S_SWR)) { // check for S_ERR ??//////////////////////// + if (!(m_status & S_SRW)) { + m_status |= S_ERR; + return false; + } + m_status &= ~S_SRD; + m_status |= S_SWR; + m_r = 0; + m_w = sizeof(m_buf); + m_p = m_buf; + return true; + } + uint8_t n = m_p - m_buf; + m_p = m_buf; + m_w = sizeof(m_buf); + if (StreamBaseFile::write(m_buf, n) == n) { + return true; + } + m_status |= S_ERR; + return false; +} +//------------------------------------------------------------------------------ +int StdioStream::flushPut(uint8_t c) { + if (!flushBuf()) { + return EOF; + } + m_w--; + return *m_p++ = c; +} +//------------------------------------------------------------------------------ +char* StdioStream::fmtSpace(uint8_t len) { + if (m_w < len) { + if (!flushBuf() || m_w < len) { + return 0; + } + } + if (len > m_w) { + return 0; + } + m_p += len; + m_w -= len; + return reinterpret_cast(m_p); +} diff --git a/Firmware_V3/lib/SdFat/src/iostream/StdioStream.h b/Firmware_V3/lib/SdFat/src/iostream/StdioStream.h new file mode 100644 index 0000000..e43fe04 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/iostream/StdioStream.h @@ -0,0 +1,663 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef StdioStream_h +#define StdioStream_h +/** + * \file + * \brief StdioStream class + */ +#include +#include "ios.h" +//------------------------------------------------------------------------------ +/** Total size of stream buffer. The entire buffer is used for output. + * During input UNGETC_BUF_SIZE of this space is reserved for ungetc. + */ +const uint8_t STREAM_BUF_SIZE = 64; +/** Amount of buffer allocated for ungetc during input. */ +const uint8_t UNGETC_BUF_SIZE = 2; +//------------------------------------------------------------------------------ +// Get rid of any macros defined in . +#include +#undef clearerr +#undef fclose +#undef feof +#undef ferror +#undef fflush +#undef fgetc +#undef fgetpos +#undef fgets +#undef fopen +#undef fprintf +#undef fputc +#undef fputs +#undef fread +#undef freopen +#undef fscanf +#undef fseek +#undef fsetpos +#undef ftell +#undef fwrite +#undef getc +#undef getchar +#undef gets +#undef perror +//#undef printf // NOLINT +#undef putc +#undef putchar +#undef puts +#undef remove +#undef rename +#undef rewind +#undef scanf +#undef setbuf +#undef setvbuf +//#undef sprintf // NOLINT +#undef sscanf +#undef tmpfile +#undef tmpnam +#undef ungetc +#undef vfprintf +#undef vprintf +#undef vsprintf + +// make sure needed macros are defined +#ifndef EOF +/** End-of-file return value. */ +#define EOF (-1) +#endif // EOF +#ifndef NULL +/** Null pointer */ +#define NULL 0 +#endif // NULL +#ifndef SEEK_CUR +/** Seek relative to current position. */ +#define SEEK_CUR 1 +#endif // SEEK_CUR +#ifndef SEEK_END +/** Seek relative to end-of-file. */ +#define SEEK_END 2 +#endif // SEEK_END +#ifndef SEEK_SET +/** Seek relative to start-of-file. */ +#define SEEK_SET 0 +#endif // SEEK_SET +//------------------------------------------------------------------------------ +/** \class StdioStream + * \brief StdioStream implements a minimal stdio stream. + * + * StdioStream does not support subdirectories or long file names. + */ +class StdioStream : private StreamBaseFile { + public: + /** Constructor + * + */ + StdioStream() {} + //---------------------------------------------------------------------------- + /** Clear the stream's end-of-file and error indicators. */ + void clearerr() { + m_status &= ~(S_ERR | S_EOF); + } + //---------------------------------------------------------------------------- + /** Close a stream. + * + * A successful call to the fclose function causes the stream to be + * flushed and the associated file to be closed. Any unwritten buffered + * data is written to the file; any unread buffered data is discarded. + * Whether or not the call succeeds, the stream is disassociated from + * the file. + * + * \return zero if the stream was successfully closed, or EOF if any any + * errors are detected. + */ + int fclose(); + //---------------------------------------------------------------------------- + /** Test the stream's end-of-file indicator. + * \return non-zero if and only if the end-of-file indicator is set. + */ + int feof() { + return (m_status & S_EOF) != 0; + } + //---------------------------------------------------------------------------- + /** Test the stream's error indicator. + * \return return non-zero if and only if the error indicator is set. + */ + int ferror() { + return (m_status & S_ERR) != 0; + } + //---------------------------------------------------------------------------- + /** Flush the stream. + * + * If stream is an output stream or an update stream in which the most + * recent operation was not input, any unwritten data is written to the + * file; otherwise the call is an error since any buffered input data + * would be lost. + * + * \return sets the error indicator for the stream and returns EOF if an + * error occurs, otherwise it returns zero. + */ + int fflush(); + //---------------------------------------------------------------------------- + /** Get a byte from the stream. + * + * \return If the end-of-file indicator for the stream is set, or if the + * stream is at end-of-file, the end-of-file indicator for the stream is + * set and the fgetc function returns EOF. Otherwise, the fgetc function + * returns the next character from the input stream. + */ + int fgetc() { + return m_r-- == 0 ? fillGet() : *m_p++; + } + //---------------------------------------------------------------------------- + /** Get a string from a stream. + * + * The fgets function reads at most one less than the number of + * characters specified by num from the stream into the array pointed + * to by str. No additional characters are read after a new-line + * character (which is retained) or after end-of-file. A null character + * is written immediately after the last character read into the array. + * + * \param[out] str Pointer to an array of where the string is copied. + * + * \param[in] num Maximum number of characters including the null + * character. + * + * \param[out] len If len is not null and fgets is successful, the + * length of the string is returned. + * + * \return str if successful. If end-of-file is encountered and no + * characters have been read into the array, the contents of the array + * remain unchanged and a null pointer is returned. If a read error + * occurs during the operation, the array contents are indeterminate + * and a null pointer is returned. + */ + char* fgets(char* str, size_t num, size_t* len = 0); + //---------------------------------------------------------------------------- + /** Open a stream. + * + * Open a file and associates the stream with it. + * + * \param[in] path file to be opened. + * + * \param[in] mode a string that indicates the open mode. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
"r" or "rb"Open a file for reading. The file must exist.
"w" or "wb"Truncate an existing to zero length or create an empty file + * for writing.
"wx" or "wbx"Create a file for writing. Fails if the file already exists.
"a" or "ab"Append; open or create file for writing at end-of-file.
"r+" or "rb+" or "r+b"Open a file for update (reading and writing).
"w+" or "w+b" or "wb+"Truncate an existing to zero length or create a file for update.
"w+x" or "w+bx" or "wb+x"Create a file for update. Fails if the file already exists.
"a+" or "a+b" or "ab+"Append; open or create a file for update, writing at end-of-file.
+ * The character 'b' shall have no effect, but is allowed for ISO C + * standard conformance. + * + * Opening a file with append mode causes all subsequent writes to the + * file to be forced to the then current end-of-file, regardless of + * intervening calls to the fseek function. + * + * When a file is opened with update mode, both input and output may be + * performed on the associated stream. However, output shall not be + * directly followed by input without an intervening call to the fflush + * function or to a file positioning function (fseek, or rewind), and + * input shall not be directly followed by output without an intervening + * call to a file positioning function, unless the input operation + * encounters end-of-file. + * + * \return true for success or false for failure. + */ + bool fopen(const char* path, const char* mode); + //---------------------------------------------------------------------------- + /** Write a byte to a stream. + * + * \param[in] c the byte to be written (converted to an unsigned char). + * + * \return Upon successful completion, fputc() returns the value it + * has written. Otherwise, it returns EOF and sets the error indicator for + * the stream. + */ + int fputc(int c) { + return m_w-- == 0 ? flushPut(c) : *m_p++ = c; + } + //---------------------------------------------------------------------------- + /** Write a string to a stream. + * + * \param[in] str a pointer to the string to be written. + * + * \return for success, fputs() returns a non-negative + * number. Otherwise, it returns EOF and sets the error indicator for + * the stream. + */ + int fputs(const char* str); + //---------------------------------------------------------------------------- + /** Binary input. + * + * Reads an array of up to count elements, each one with a size of size + * bytes. + * \param[out] ptr pointer to area of at least (size*count) bytes where + * the data will be stored. + * + * \param[in] size the size, in bytes, of each element to be read. + * + * \param[in] count the number of elements to be read. + * + * \return number of elements successfully read, which may be less than + * count if a read error or end-of-file is encountered. If size or count + * is zero, fread returns zero and the contents of the array and the + * state of the stream remain unchanged. + */ + size_t fread(void* ptr, size_t size, size_t count); + //---------------------------------------------------------------------------- + /** Set the file position for the stream. + * + * \param[in] offset number of offset from the origin. + * + * \param[in] origin position used as reference for the offset. It is + * specified by one of the following constants. + * + * SEEK_SET - Beginning of file. + * + * SEEK_CUR - Current position of the file pointer. + * + * SEEK_END - End of file. + * + * \return zero for success. Otherwise, it returns non-zero and sets the + * error indicator for the stream. + */ + int fseek(int32_t offset, int origin); + //---------------------------------------------------------------------------- + /** Get the current position in a stream. + * + * \return If successful, ftell return the current value of the position + * indicator. On failure, ftell returns −1L. + */ + int32_t ftell(); + //---------------------------------------------------------------------------- + /** Binary output. + * + * Writes an array of up to count elements, each one with a size of size + * bytes. + * \param[in] ptr pointer to (size*count) bytes of data to be written. + * + * \param[in] size the size, in bytes, of each element to be written. + * + * \param[in] count the number of elements to be written. + * + * \return number of elements successfully written. if this number is + * less than count, an error has occurred. If size or count is zero, + * fwrite returns zero. + */ + size_t fwrite(const void * ptr, size_t size, size_t count); + //---------------------------------------------------------------------------- + /** Get a byte from the stream. + * + * getc and fgetc are equivalent but getc is in-line so it is faster but + * require more flash memory. + * + * \return If the end-of-file indicator for the stream is set, or if the + * stream is at end-of-file, the end-of-file indicator for the stream is + * set and the fgetc function returns EOF. Otherwise, the fgetc function + * returns the next character from the input stream. + */ + inline __attribute__((always_inline)) + int getc() { + return m_r-- == 0 ? fillGet() : *m_p++; + } + //---------------------------------------------------------------------------- + /** Write a byte to a stream. + * + * putc and fputc are equivalent but putc is in-line so it is faster but + * require more flash memory. + * + * \param[in] c the byte to be written (converted to an unsigned char). + * + * \return Upon successful completion, fputc() returns the value it + * has written. Otherwise, it returns EOF and sets the error indicator for + * the stream. + */ + inline __attribute__((always_inline)) + int putc(int c) { + return m_w-- == 0 ? flushPut(c) : *m_p++ = c; + } + //---------------------------------------------------------------------------- + /** Write a CR/LF. + * + * \return two, the number of bytes written, for success or -1 for failure. + */ + inline __attribute__((always_inline)) + int putCRLF() { + if (m_w < 2) { + if (!flushBuf()) { + return -1; + } + } + *m_p++ = '\r'; + *m_p++ = '\n'; + m_w -= 2; + return 2; + } + //---------------------------------------------------------------------------- + /** Write a character. + * \param[in] c the character to write. + * \return the number of bytes written. + */ + size_t print(char c) { + return putc(c) < 0 ? 0 : 1; + } + //---------------------------------------------------------------------------- + /** Write a string. + * + * \param[in] str the string to be written. + * + * \return the number of bytes written. + */ + size_t print(const char* str) { + int n = fputs(str); + return n < 0 ? 0 : n; + } + //---------------------------------------------------------------------------- +#if (defined(ARDUINO) && ENABLE_ARDUINO_FEATURES) || defined(DOXYGEN) + /** Print a string stored in flash memory. + * + * \param[in] str the string to print. + * + * \return the number of bytes written. + */ + size_t print(const __FlashStringHelper *str); +#endif // (defined(ARDUINO) && ENABLE_ARDUINO_FEATURES) || defined(DOXYGEN) + //---------------------------------------------------------------------------- + /** Print a floating point number. + * + * \param[in] prec Number of digits after decimal point. + * + * \param[in] val the number to be printed. + * + * \return the number of bytes written. + */ + size_t print(double val, uint8_t prec = 2) { + return print(static_cast(val), prec); + } + //---------------------------------------------------------------------------- + /** Print a floating point number. + * + * \param[in] prec Number of digits after decimal point. + * + * \param[in] val the number to be printed. + * + * \return the number of bytes written. + */ + size_t print(float val, uint8_t prec = 2) { + int n = printDec(val, prec); + return n > 0 ? n : 0; + } + //---------------------------------------------------------------------------- + /** Print a number. + * + * \param[in] val the number to be printed. + * + * \return the number of bytes written. + */ + template + size_t print(T val) { + int n = printDec(val); + return n > 0 ? n : 0; + } + //---------------------------------------------------------------------------- + /** Write a CR/LF. + * + * \return two, the number of bytes written, for success or zero for failure. + */ + size_t println() { + return putCRLF() > 0 ? 2 : 0; + } + //---------------------------------------------------------------------------- + /** Print a floating point number followed by CR/LF. + * + * \param[in] val the number to be printed. + * + * \param[in] prec Number of digits after decimal point. + * + * \return the number of bytes written. + */ + size_t println(double val, uint8_t prec = 2) { + return println(static_cast(val), prec); + } + //---------------------------------------------------------------------------- + /** Print a floating point number followed by CR/LF. + * + * \param[in] val the number to be printed. + * + * \param[in] prec Number of digits after decimal point. + * + * \return the number of bytes written. + */ + size_t println(float val, uint8_t prec = 2) { + int n = printDec(val, prec); + return n > 0 && putCRLF() > 0 ? n + 2 : 0; + } + //---------------------------------------------------------------------------- + /** Print an item followed by CR/LF + * + * \param[in] val the item to be printed. + * + * \return the number of bytes written. + */ + template + size_t println(T val) { + int n = print(val); + return putCRLF() > 0 ? n + 2 : 0; + } + //---------------------------------------------------------------------------- + /** Print a char as a number. + * \param[in] n number to be printed. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(char n) { + if (CHAR_MIN == 0) { + return printDec((unsigned char)n); + } else { + return printDec((signed char)n); + } + } + //---------------------------------------------------------------------------- + /** print a signed 8-bit integer + * \param[in] n number to be printed. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(signed char n); + //---------------------------------------------------------------------------- + /** Print an unsigned 8-bit number. + * \param[in] n number to be print. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(unsigned char n) { + return printDec((uint16_t)n); + } + //---------------------------------------------------------------------------- + /** Print a int16_t + * \param[in] n number to be printed. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(int16_t n); + //---------------------------------------------------------------------------- + /** print a uint16_t. + * \param[in] n number to be printed. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(uint16_t n); + //---------------------------------------------------------------------------- + /** Print a signed 32-bit integer. + * \param[in] n number to be printed. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(int32_t n); + //---------------------------------------------------------------------------- + /** Write an unsigned 32-bit number. + * \param[in] n number to be printed. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(uint32_t n); + //---------------------------------------------------------------------------- + /** Print a double. + * \param[in] value The number to be printed. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(double value, uint8_t prec) { + return printDec(static_cast(value), prec); + } + //---------------------------------------------------------------------------- + /** Print a float. + * \param[in] value The number to be printed. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + int printDec(float value, uint8_t prec); + //---------------------------------------------------------------------------- + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + int printField(double value, char term, uint8_t prec = 2) { + return printField(static_cast(value), term, prec) > 0; + } + //---------------------------------------------------------------------------- + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. + * \param[in] prec Number of digits after decimal point. + * \return The number of bytes written or -1 if an error occurs. + */ + int printField(float value, char term, uint8_t prec = 2) { + int rtn = printDec(value, prec); + return rtn < 0 || putc(term) < 0 ? -1 : rtn + 1; + } + //---------------------------------------------------------------------------- + /** Print a number followed by a field terminator. + * \param[in] value The number to be printed. + * \param[in] term The field terminator. + * \return The number of bytes written or -1 if an error occurs. + */ + template + int printField(T value, char term) { + int rtn = printDec(value); + return rtn < 0 || putc(term) < 0 ? -1 : rtn + 1; + } + //---------------------------------------------------------------------------- + /** Print HEX + * \param[in] n number to be printed as HEX. + * + * \return The number of bytes written or -1 if an error occurs. + */ + int printHex(uint32_t n); + //---------------------------------------------------------------------------- + /** Print HEX with CRLF + * \param[in] n number to be printed as HEX. + * + * \return The number of bytes written or -1 if an error occurs. + */ + int printHexln(uint32_t n) { + int rtn = printHex(n); + return rtn < 0 || putCRLF() != 2 ? -1 : rtn + 2; + } + //---------------------------------------------------------------------------- + /** Set position of a stream to the beginning. + * + * The rewind function sets the file position to the beginning of the + * file. It is equivalent to fseek(0L, SEEK_SET) except that the error + * indicator for the stream is also cleared. + * + * \return true for success or false for failure. + */ + bool rewind(); + //---------------------------------------------------------------------------- + /** Push a byte back into an input stream. + * + * \param[in] c the byte (converted to an unsigned char) to be pushed back. + * + * One character of push-back is guaranteed. If the ungetc function is + * called too many times without an intervening read or file positioning + * operation on that stream, the operation may fail. + * + * A successful intervening call to a file positioning function (fseek, + * fsetpos, or rewind) discards any pushed-back characters for the stream. + * + * \return Upon successful completion, ungetc() returns the byte pushed + * back after conversion. Otherwise it returns EOF. + */ + int ungetc(int c); + //============================================================================ + private: + bool fillBuf(); + int fillGet(); + bool flushBuf(); + int flushPut(uint8_t c); + char* fmtSpace(uint8_t len); + int write(const void* buf, size_t count); + //---------------------------------------------------------------------------- + // S_SRD and S_WR are never simultaneously asserted + static const uint8_t S_SRD = 0x01; // OK to read + static const uint8_t S_SWR = 0x02; // OK to write + static const uint8_t S_SRW = 0x04; // open for reading & writing + static const uint8_t S_EOF = 0x10; // found EOF + static const uint8_t S_ERR = 0x20; // found error + //---------------------------------------------------------------------------- + uint8_t m_buf[STREAM_BUF_SIZE]; + uint8_t m_status = 0; + uint8_t* m_p = m_buf; + uint8_t m_r = 0; + uint8_t m_w; +}; +//------------------------------------------------------------------------------ +#endif // StdioStream_h diff --git a/Firmware_V3/lib/SdFat/src/iostream/StreamBaseClass.cpp b/Firmware_V3/lib/SdFat/src/iostream/StreamBaseClass.cpp new file mode 100644 index 0000000..67cf6bc --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/iostream/StreamBaseClass.cpp @@ -0,0 +1,160 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "fstream.h" +//------------------------------------------------------------------------------ +int16_t StreamBaseClass::getch() { + uint8_t c; + int8_t s = StreamBaseFile::read(&c, 1); + if (s != 1) { + if (s < 0) { + setstate(badbit); + } else { + setstate(eofbit); + } + return -1; + } + if (c != '\r' || (getmode() & ios::binary)) { + return c; + } + s = StreamBaseFile::read(&c, 1); + if (s == 1 && c == '\n') { + return c; + } + if (s == 1) { + StreamBaseFile::seekCur(-1); + } + return '\r'; +} +//------------------------------------------------------------------------------ +void StreamBaseClass::open(const char* path, ios::openmode mode) { + oflag_t oflag; + clearWriteError(); + switch (mode & (app | in | out | trunc)) { + case app | in: + case app | in | out: + oflag = O_RDWR | O_APPEND | O_CREAT; + break; + + case app: + case app | out: + oflag = O_WRONLY | O_APPEND | O_CREAT; + break; + + case in: + oflag = O_RDONLY; + break; + + case in | out: + oflag = O_RDWR | O_CREAT; + break; + + case in | out | trunc: + oflag = O_RDWR | O_TRUNC | O_CREAT; + break; + + case out: + case out | trunc: + oflag = O_WRONLY | O_TRUNC | O_CREAT; + break; + + default: + goto fail; + } + if (mode & ios::ate) { + oflag |= O_AT_END; + } + if (!StreamBaseFile::open(path, oflag)) { + goto fail; + } + setmode(mode); + clear(); + return; + + fail: + StreamBaseFile::close(); + setstate(failbit); + return; +} +//------------------------------------------------------------------------------ +void StreamBaseClass::putch(char c) { + if (c == '\n' && !(getmode() & ios::binary)) { + write('\r'); + } + write(c); + if (getWriteError()) { + setstate(badbit); + } +} +//------------------------------------------------------------------------------ +void StreamBaseClass::putstr(const char* str) { + size_t n = 0; + while (1) { + char c = str[n]; + if (c == '\0' || (c == '\n' && !(getmode() & ios::binary))) { + if (n > 0) { + write(str, n); + } + if (c == '\0') { + break; + } + write('\r'); + str += n; + n = 0; + } + n++; + } + if (getWriteError()) { + setstate(badbit); + } +} +//------------------------------------------------------------------------------ +bool StreamBaseClass::seekoff(off_type off, seekdir way) { + pos_type pos; + switch (way) { + case beg: + pos = off; + break; + + case cur: + pos = StreamBaseFile::curPosition() + off; + break; + + case end: + pos = StreamBaseFile::fileSize() + off; + break; + + default: + return false; + } + return seekpos(pos); +} +//------------------------------------------------------------------------------ +int StreamBaseClass::write(const void* buf, size_t n) { + return StreamBaseFile::write(buf, n); +} +//------------------------------------------------------------------------------ +void StreamBaseClass::write(char c) { + StreamBaseFile::write(&c, 1); +} diff --git a/Firmware_V3/lib/SdFat/src/iostream/bufstream.h b/Firmware_V3/lib/SdFat/src/iostream/bufstream.h new file mode 100644 index 0000000..3291a0d --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/iostream/bufstream.h @@ -0,0 +1,171 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef bufstream_h +#define bufstream_h +/** + * \file + * \brief \ref ibufstream and \ref obufstream classes + */ +#include +#include "iostream.h" +//============================================================================== +/** + * \class ibufstream + * \brief parse a char string + */ +class ibufstream : public istream { + public: + /** Constructor */ + ibufstream() {} + /** Constructor + * \param[in] str pointer to string to be parsed + * Warning: The string will not be copied so must stay in scope. + */ + explicit ibufstream(const char* str) { + init(str); + } + /** Initialize an ibufstream + * \param[in] str pointer to string to be parsed + * Warning: The string will not be copied so must stay in scope. + */ + void init(const char* str) { + m_buf = str; + m_len = strlen(m_buf); + m_pos = 0; + clear(); + } + + protected: + /// @cond SHOW_PROTECTED + int16_t getch() { + if (m_pos < m_len) { + return m_buf[m_pos++]; + } + setstate(eofbit); + return -1; + } + void getpos(pos_t* pos) { + pos->position = m_pos; + } + bool seekoff(off_type off, seekdir way) { + (void)off; + (void)way; + return false; + } + bool seekpos(pos_type pos) { + if (pos < m_len) { + m_pos = pos; + return true; + } + return false; + } + void setpos(pos_t* pos) { + m_pos = pos->position; + } + pos_type tellpos() { + return m_pos; + } + /// @endcond + private: + const char* m_buf = nullptr; + size_t m_len = 0; + size_t m_pos; +}; +//============================================================================== +/** + * \class obufstream + * \brief format a char string + */ +class obufstream : public ostream { + public: + /** constructor */ + obufstream() {} + /** Constructor + * \param[in] buf buffer for formatted string + * \param[in] size buffer size + */ + obufstream(char *buf, size_t size) { + init(buf, size); + } + /** Initialize an obufstream + * \param[in] buf buffer for formatted string + * \param[in] size buffer size + */ + void init(char *buf, size_t size) { + m_buf = buf; + buf[0] = '\0'; + m_size = size; + m_in = 0; + } + /** \return a pointer to the buffer */ + char* buf() { + return m_buf; + } + /** \return the length of the formatted string */ + size_t length() { + return m_in; + } + + protected: + /// @cond SHOW_PROTECTED + void putch(char c) { + if ((m_in + 1) >= m_size) { + setstate(badbit); + return; + } + m_buf[m_in++] = c; + m_buf[m_in] = '\0'; + } + void putstr(const char *str) { + while (*str) { + putch(*str++); + } + } + bool seekoff(off_type off, seekdir way) { + (void)off; + (void)way; + return false; + } + bool seekpos(pos_type pos) { + if (pos > m_in) { + return false; + } + m_in = pos; + m_buf[m_in] = '\0'; + return true; + } + bool sync() { + return true; + } + pos_type tellpos() { + return m_in; + } + /// @endcond + private: + char *m_buf = nullptr; + size_t m_size = 0; + size_t m_in = 0; +}; +#endif // bufstream_h diff --git a/Firmware_V3/lib/SdFat/src/iostream/fstream.h b/Firmware_V3/lib/SdFat/src/iostream/fstream.h new file mode 100644 index 0000000..9686d45 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/iostream/fstream.h @@ -0,0 +1,330 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +/** + * \file + * \brief iostreams for files. + */ +#ifndef fstream_h +#define fstream_h +#include "iostream.h" +//------------------------------------------------------------------------------ +/** + * \class StreamBaseClass + * \brief base type for FAT and exFAT streams + */ +class StreamBaseClass : protected StreamBaseFile, virtual public ios { + protected: + void clearWriteError() { + StreamBaseFile::clearWriteError(); + } + /* Internal do not use + * \return mode + */ + int16_t getch(); + bool getWriteError() { + return StreamBaseFile::getWriteError(); + } + void open(const char* path, ios::openmode mode); + /** Internal do not use + * \return mode + */ + ios::openmode getmode() { + return m_mode; + } + void putch(char c); + void putstr(const char *str); + bool seekoff(off_type off, seekdir way); + /** Internal do not use + * \param[in] pos + */ + bool seekpos(pos_type pos) { + return StreamBaseFile::seekSet(pos); + } + /** Internal do not use + * \param[in] mode + */ + void setmode(ios::openmode mode) { + m_mode = mode; + } + int write(const void* buf, size_t n); + void write(char c); + + private: + ios::openmode m_mode; +}; +//============================================================================== +/** + * \class fstream + * \brief file input/output stream. + */ +class fstream : public iostream, StreamBaseClass { + public: + using iostream::peek; + fstream() {} + /** Constructor with open + * \param[in] path file to open + * \param[in] mode open mode + */ + explicit fstream(const char* path, openmode mode = in | out) { + open(path, mode); + } +#if DESTRUCTOR_CLOSES_FILE + ~fstream() {} +#endif // DESTRUCTOR_CLOSES_FILE + /** Clear state and writeError + * \param[in] state new state for stream + */ + void clear(iostate state = goodbit) { + ios::clear(state); + StreamBaseClass::clearWriteError(); + } + /** Close a file and force cached data and directory information + * to be written to the storage device. + */ + void close() { + StreamBaseClass::close(); + } + /** Open a fstream + * \param[in] path path to open + * \param[in] mode open mode + * + * Valid open modes are (at end, ios::ate, and/or ios::binary may be added): + * + * ios::in - Open file for reading. + * + * ios::out or ios::out | ios::trunc - Truncate to 0 length, if existent, + * or create a file for writing only. + * + * ios::app or ios::out | ios::app - Append; open or create file for + * writing at end-of-file. + * + * ios::in | ios::out - Open file for update (reading and writing). + * + * ios::in | ios::out | ios::trunc - Truncate to zero length, if existent, + * or create file for update. + * + * ios::in | ios::app or ios::in | ios::out | ios::app - Append; open or + * create text file for update, writing at end of file. + */ + void open(const char* path, openmode mode = in | out) { + StreamBaseClass::open(path, mode); + } + /** \return True if stream is open else false. */ + bool is_open() { + return StreamBaseFile::isOpen(); + } + + protected: + /// @cond SHOW_PROTECTED + /** Internal - do not use + * \return + */ + int16_t getch() { + return StreamBaseClass::getch(); + } + /** Internal - do not use + * \param[out] pos + */ + void getpos(pos_t* pos) { + StreamBaseFile::fgetpos(pos); + } + /** Internal - do not use + * \param[in] c + */ + void putch(char c) { + StreamBaseClass::putch(c); + } + /** Internal - do not use + * \param[in] str + */ + void putstr(const char *str) { + StreamBaseClass::putstr(str); + } + /** Internal - do not use + * \param[in] pos + */ + bool seekoff(off_type off, seekdir way) { + return StreamBaseClass::seekoff(off, way); + } + bool seekpos(pos_type pos) { + return StreamBaseClass::seekpos(pos); + } + void setpos(pos_t* pos) { + StreamBaseFile::fsetpos(pos); + } + bool sync() { + return StreamBaseClass::sync(); + } + pos_type tellpos() { + return StreamBaseFile::curPosition(); + } + /// @endcond +}; +//============================================================================== +/** + * \class ifstream + * \brief file input stream. + */ +class ifstream : public istream, StreamBaseClass { + public: + using istream::peek; + ifstream() {} + /** Constructor with open + * \param[in] path file to open + * \param[in] mode open mode + */ + explicit ifstream(const char* path, openmode mode = in) { + open(path, mode); + } +#if DESTRUCTOR_CLOSES_FILE + ~ifstream() {} +#endif // DESTRUCTOR_CLOSES_FILE + /** Close a file and force cached data and directory information + * to be written to the storage device. + */ + void close() { + StreamBaseClass::close(); + } + /** \return True if stream is open else false. */ + bool is_open() { + return StreamBaseFile::isOpen(); + } + /** Open an ifstream + * \param[in] path file to open + * \param[in] mode open mode + * + * \a mode See fstream::open() for valid modes. + */ + void open(const char* path, openmode mode = in) { + StreamBaseClass::open(path, mode | in); + } + + protected: + /// @cond SHOW_PROTECTED + /** Internal - do not use + * \return + */ + int16_t getch() { + return StreamBaseClass::getch(); + } + /** Internal - do not use + * \param[out] pos + */ + void getpos(pos_t* pos) { + StreamBaseFile::fgetpos(pos); + } + /** Internal - do not use + * \param[in] pos + */ + bool seekoff(off_type off, seekdir way) { + return StreamBaseClass::seekoff(off, way); + } + bool seekpos(pos_type pos) { + return StreamBaseClass::seekpos(pos); + } + void setpos(pos_t* pos) { + StreamBaseFile::fsetpos(pos); + } + pos_type tellpos() { + return StreamBaseFile::curPosition(); + } + /// @endcond +}; +//============================================================================== +/** + * \class ofstream + * \brief file output stream. + */ +class ofstream : public ostream, StreamBaseClass { + public: + ofstream() {} + /** Constructor with open + * \param[in] path file to open + * \param[in] mode open mode + */ + explicit ofstream(const char* path, openmode mode = out) { + open(path, mode); + } +#if DESTRUCTOR_CLOSES_FILE + ~ofstream() {} +#endif // DESTRUCTOR_CLOSES_FILE + /** Clear state and writeError + * \param[in] state new state for stream + */ + void clear(iostate state = goodbit) { + ios::clear(state); + StreamBaseClass::clearWriteError(); + } + /** Close a file and force cached data and directory information + * to be written to the storage device. + */ + void close() { + StreamBaseClass::close(); + } + /** Open an ofstream + * \param[in] path file to open + * \param[in] mode open mode + * + * \a mode See fstream::open() for valid modes. + */ + void open(const char* path, openmode mode = out) { + StreamBaseClass::open(path, mode | out); + } + /** \return True if stream is open else false. */ + bool is_open() { + return StreamBaseFile::isOpen(); + } + + protected: + /// @cond SHOW_PROTECTED + /** + * Internal do not use + * \param[in] c + */ + void putch(char c) { + StreamBaseClass::putch(c); + } + void putstr(const char* str) { + StreamBaseClass::putstr(str); + } + bool seekoff(off_type off, seekdir way) { + return StreamBaseClass::seekoff(off, way); + } + bool seekpos(pos_type pos) { + return StreamBaseClass::seekpos(pos); + } + /** + * Internal do not use + * \param[in] b + */ + bool sync() { + return StreamBaseClass::sync(); + } + pos_type tellpos() { + return StreamBaseFile::curPosition(); + } + /// @endcond +}; +#endif // fstream_h diff --git a/Firmware_V3/lib/SdFat/src/iostream/ios.h b/Firmware_V3/lib/SdFat/src/iostream/ios.h new file mode 100644 index 0000000..cac0bdb --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/iostream/ios.h @@ -0,0 +1,448 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef ios_h +#define ios_h +#include "../FsLib/FsLib.h" +/** + * \file + * \brief \ref ios_base and \ref ios classes + */ +//============================================================================== +/** For internal use in c++ streams */ +typedef fspos_t pos_t; +//============================================================================== +#if SDFAT_FILE_TYPE == 1 +/** Set File type for iostreams. */ +typedef FatFile StreamBaseFile; +#elif SDFAT_FILE_TYPE == 2 +typedef ExFatFile StreamBaseFile; +#elif SDFAT_FILE_TYPE == 3 +typedef FsBaseFile StreamBaseFile; +#else // SDFAT_FILE_TYPE +#error Invalid SDFAT_FILE_TYPE +#endif // SDFAT_FILE_TYPE +/** + * \class ios_base + * \brief Base class for all streams + */ +class ios_base { + public: + /** typedef for iostate bitmask */ + typedef unsigned char iostate; + // State flags. + /** iostate for no flags */ + static const iostate goodbit = 0x00; + /** iostate bad bit for a nonrecoverable error. */ + static const iostate badbit = 0X01; + /** iostate bit for end of file reached */ + static const iostate eofbit = 0x02; + /** iostate fail bit for nonfatal error */ + static const iostate failbit = 0X04; +#if SDFAT_FILE_TYPE == 1 + /** + * unsigned size that can represent maximum file size. + * (violates spec - should be signed) + */ + typedef uint32_t streamsize; + /** type for absolute seek position */ + typedef uint32_t pos_type; + /** type for relative seek offset */ + typedef int32_t off_type; +#else // SDFAT_FILE_TYPE + /** + * unsigned size that can represent maximum file size. + * (violates spec - should be signed) + */ + typedef uint64_t streamsize; + /** type for absolute seek position */ + typedef uint64_t pos_type; + /** type for relative seek offset */ + typedef int64_t off_type; +#endif // SDFAT_FILE_TYPE + /** enumerated type for the direction of relative seeks */ + enum seekdir { + /** seek relative to the beginning of the stream */ + beg, + /** seek relative to the current stream position */ + cur, + /** seek relative to the end of the stream */ + end + }; + /** type for format flags */ + typedef unsigned int fmtflags; + /** left adjust fields */ + static const fmtflags left = 0x0001; + /** right adjust fields */ + static const fmtflags right = 0x0002; + /** fill between sign/base prefix and number */ + static const fmtflags internal = 0x0004; + /** base 10 flag*/ + static const fmtflags dec = 0x0008; + /** base 16 flag */ + static const fmtflags hex = 0x0010; + /** base 8 flag */ + static const fmtflags oct = 0x0020; + // static const fmtflags fixed = 0x0040; + // static const fmtflags scientific = 0x0080; + /** use strings true/false for bool */ + static const fmtflags boolalpha = 0x0100; + /** use prefix 0X for hex and 0 for oct */ + static const fmtflags showbase = 0x0200; + /** always show '.' for floating numbers */ + static const fmtflags showpoint = 0x0400; + /** show + sign for nonnegative numbers */ + static const fmtflags showpos = 0x0800; + /** skip initial white space */ + static const fmtflags skipws = 0x1000; + // static const fmtflags unitbuf = 0x2000; + /** use uppercase letters in number representations */ + static const fmtflags uppercase = 0x4000; + /** mask for adjustfield */ + static const fmtflags adjustfield = left | right | internal; + /** mask for basefield */ + static const fmtflags basefield = dec | hex | oct; + // static const fmtflags floatfield = scientific | fixed; + //---------------------------------------------------------------------------- + /** typedef for iostream open mode */ + typedef uint8_t openmode; + + // Openmode flags. + /** seek to end before each write */ + static const openmode app = 0X4; + /** open and seek to end immediately after opening */ + static const openmode ate = 0X8; + /** perform input and output in binary mode (as opposed to text mode) */ + static const openmode binary = 0X10; + /** open for input */ + static const openmode in = 0X20; + /** open for output */ + static const openmode out = 0X40; + /** truncate an existing stream when opening */ + static const openmode trunc = 0X80; + //---------------------------------------------------------------------------- + ios_base() : m_fill(' '), m_fmtflags(dec | right | skipws) + , m_precision(2), m_width(0) {} + /** \return fill character */ + char fill() { + return m_fill; + } + /** Set fill character + * \param[in] c new fill character + * \return old fill character + */ + char fill(char c) { + char r = m_fill; + m_fill = c; + return r; + } + /** \return format flags */ + fmtflags flags() const { + return m_fmtflags; + } + /** set format flags + * \param[in] fl new flag + * \return old flags + */ + fmtflags flags(fmtflags fl) { + fmtflags tmp = m_fmtflags; + m_fmtflags = fl; + return tmp; + } + /** \return precision */ + int precision() const { + return m_precision; + } + /** set precision + * \param[in] n new precision + * \return old precision + */ + int precision(unsigned int n) { + int r = m_precision; + m_precision = n; + return r; + } + /** set format flags + * \param[in] fl new flags to be or'ed in + * \return old flags + */ + fmtflags setf(fmtflags fl) { + fmtflags r = m_fmtflags; + m_fmtflags |= fl; + return r; + } + /** modify format flags + * \param[in] mask flags to be removed + * \param[in] fl flags to be set after mask bits have been cleared + * \return old flags + */ + fmtflags setf(fmtflags fl, fmtflags mask) { + fmtflags r = m_fmtflags; + m_fmtflags &= ~mask; + m_fmtflags |= fl; + return r; + } + /** clear format flags + * \param[in] fl flags to be cleared + */ + void unsetf(fmtflags fl) { + m_fmtflags &= ~fl; + } + /** \return width */ + unsigned width() { + return m_width; + } + /** set width + * \param[in] n new width + * \return old width + */ + unsigned width(unsigned n) { + unsigned r = m_width; + m_width = n; + return r; + } + + protected: + /** \return current number base */ + uint8_t flagsToBase() { + uint8_t f = flags() & basefield; + return f == oct ? 8 : f != hex ? 10 : 16; + } + + private: + char m_fill; + fmtflags m_fmtflags; + unsigned char m_precision; + unsigned int m_width; +}; +//------------------------------------------------------------------------------ +/** function for boolalpha manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& boolalpha(ios_base& str) { + str.setf(ios_base::boolalpha); + return str; +} +/** function for dec manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& dec(ios_base& str) { + str.setf(ios_base::dec, ios_base::basefield); + return str; +} +/** function for hex manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& hex(ios_base& str) { + str.setf(ios_base::hex, ios_base::basefield); + return str; +} +/** function for internal manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& internal(ios_base& str) { + str.setf(ios_base::internal, ios_base::adjustfield); + return str; +} +/** function for left manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& left(ios_base& str) { + str.setf(ios_base::left, ios_base::adjustfield); + return str; +} +/** function for noboolalpha manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& noboolalpha(ios_base& str) { + str.unsetf(ios_base::boolalpha); + return str; +} +/** function for noshowbase manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& noshowbase(ios_base& str) { + str.unsetf(ios_base::showbase); + return str; +} +/** function for noshowpoint manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& noshowpoint(ios_base& str) { + str.unsetf(ios_base::showpoint); + return str; +} +/** function for noshowpos manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& noshowpos(ios_base& str) { + str.unsetf(ios_base::showpos); + return str; +} +/** function for noskipws manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& noskipws(ios_base& str) { + str.unsetf(ios_base::skipws); + return str; +} +/** function for nouppercase manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& nouppercase(ios_base& str) { + str.unsetf(ios_base::uppercase); + return str; +} +/** function for oct manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& oct(ios_base& str) { + str.setf(ios_base::oct, ios_base::basefield); + return str; +} +/** function for right manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& right(ios_base& str) { + str.setf(ios_base::right, ios_base::adjustfield); + return str; +} +/** function for showbase manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& showbase(ios_base& str) { + str.setf(ios_base::showbase); + return str; +} +/** function for showpos manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& showpos(ios_base& str) { + str.setf(ios_base::showpos); + return str; +} +/** function for showpoint manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& showpoint(ios_base& str) { + str.setf(ios_base::showpoint); + return str; +} +/** function for skipws manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& skipws(ios_base& str) { + str.setf(ios_base::skipws); + return str; +} +/** function for uppercase manipulator + * \param[in] str The stream + * \return The stream + */ +inline ios_base& uppercase(ios_base& str) { + str.setf(ios_base::uppercase); + return str; +} +//============================================================================== +/** + * \class ios + * \brief Error and state information for all streams + */ +class ios : public ios_base { + public: + /** Create ios with no error flags set */ + ios() {} + + /** \return null pointer if fail() is true. */ + operator const void*() const { + return !fail() ? reinterpret_cast(this) : nullptr; + } + /** \return true if fail() else false. */ + bool operator!() const { + return fail(); + } + /** \return false if fail() else true. */ + explicit operator bool() const {return !fail();} + /** \return The iostate flags for this file. */ + iostate rdstate() const { + return m_iostate; + } + /** \return True if no iostate flags are set else false. */ + bool good() const { + return m_iostate == goodbit; + } + /** \return true if end of file has been reached else false. + * + * Warning: An empty file returns false before the first read. + * + * Moral: eof() is only useful in combination with fail(), to find out + * whether EOF was the cause for failure + */ + bool eof() const { + return m_iostate & eofbit; + } + /** \return true if any iostate bit other than eof are set else false. */ + bool fail() const { + return m_iostate & (failbit | badbit); + } + /** \return true if bad bit is set else false. */ + bool bad() const { + return m_iostate & badbit; + } + /** Clear iostate bits. + * + * \param[in] state The flags you want to set after clearing all flags. + **/ + void clear(iostate state = goodbit) { + m_iostate = state; + } + /** Set iostate bits. + * + * \param[in] state Bitts to set. + **/ + void setstate(iostate state) { + m_iostate |= state; + } + + private: + iostate m_iostate = 0; +}; +#endif // ios_h diff --git a/Firmware_V3/lib/SdFat/src/iostream/iostream.h b/Firmware_V3/lib/SdFat/src/iostream/iostream.h new file mode 100644 index 0000000..a41eafd --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/iostream/iostream.h @@ -0,0 +1,158 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef iostream_h +#define iostream_h +/** + * \file + * \brief \ref iostream class + */ +#include "istream.h" +#include "ostream.h" +/** Skip white space + * \param[in] is the Stream + * \return The stream + */ +inline istream& ws(istream& is) { + is.skipWhite(); + return is; +} +/** insert endline + * \param[in] os The Stream + * \return The stream + */ +inline ostream& endl(ostream& os) { + os.put('\n'); +#if ENDL_CALLS_FLUSH + os.flush(); +#endif // ENDL_CALLS_FLUSH + return os; +} +/** flush manipulator + * \param[in] os The stream + * \return The stream + */ +inline ostream& flush(ostream& os) { + os.flush(); + return os; +} +/** + * \struct setfill + * \brief type for setfill manipulator + */ +struct setfill { + /** fill character */ + char c; + /** constructor + * + * \param[in] arg new fill character + */ + explicit setfill(char arg) : c(arg) {} +}; +/** setfill manipulator + * \param[in] os the stream + * \param[in] arg set setfill object + * \return the stream + */ +inline ostream &operator<< (ostream &os, const setfill &arg) { + os.fill(arg.c); + return os; +} +/** setfill manipulator + * \param[in] obj the stream + * \param[in] arg set setfill object + * \return the stream + */ +inline istream &operator>>(istream &obj, const setfill &arg) { + obj.fill(arg.c); + return obj; +} +//------------------------------------------------------------------------------ +/** \struct setprecision + * \brief type for setprecision manipulator + */ +struct setprecision { + /** precision */ + unsigned int p; + /** constructor + * \param[in] arg new precision + */ + explicit setprecision(unsigned int arg) : p(arg) {} +}; +/** setprecision manipulator + * \param[in] os the stream + * \param[in] arg set setprecision object + * \return the stream + */ +inline ostream &operator<< (ostream &os, const setprecision &arg) { + os.precision(arg.p); + return os; +} +/** setprecision manipulator + * \param[in] is the stream + * \param[in] arg set setprecision object + * \return the stream + */ +inline istream &operator>>(istream &is, const setprecision &arg) { + is.precision(arg.p); + return is; +} +//------------------------------------------------------------------------------ +/** \struct setw + * \brief type for setw manipulator + */ +struct setw { + /** width */ + unsigned w; + /** constructor + * \param[in] arg new width + */ + explicit setw(unsigned arg) : w(arg) {} +}; +/** setw manipulator + * \param[in] os the stream + * \param[in] arg set setw object + * \return the stream + */ +inline ostream &operator<< (ostream &os, const setw &arg) { + os.width(arg.w); + return os; +} +/** setw manipulator + * \param[in] is the stream + * \param[in] arg set setw object + * \return the stream + */ +inline istream &operator>>(istream &is, const setw &arg) { + is.width(arg.w); + return is; +} +//============================================================================== +/** + * \class iostream + * \brief Input/Output stream + */ +class iostream : public istream, public ostream { +}; +#endif // iostream_h diff --git a/Firmware_V3/lib/SdFat/src/iostream/istream.cpp b/Firmware_V3/lib/SdFat/src/iostream/istream.cpp new file mode 100644 index 0000000..5ffbd41 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/iostream/istream.cpp @@ -0,0 +1,395 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include +#include +#include "istream.h" +//------------------------------------------------------------------------------ +int istream::get() { + int c; + m_gcount = 0; + c = getch(); + if (c < 0) { + setstate(failbit); + } else { + m_gcount = 1; + } + return c; +} +//------------------------------------------------------------------------------ +istream& istream::get(char& c) { + int tmp = get(); + if (tmp >= 0) { + c = tmp; + } + return *this; +} +//------------------------------------------------------------------------------ +istream& istream::get(char *str, streamsize n, char delim) { + int c; + pos_t pos; + m_gcount = 0; + while ((m_gcount + 1) < n) { + c = getch(&pos); + if (c < 0) { + break; + } + if (c == delim) { + setpos(&pos); + break; + } + str[m_gcount++] = c; + } + if (n > 0) { + str[m_gcount] = '\0'; + } + if (m_gcount == 0) { + setstate(failbit); + } + return *this; +} +//------------------------------------------------------------------------------ +void istream::getBool(bool *b) { + if ((flags() & boolalpha) == 0) { + getNumber(b); + return; + } +#ifdef __AVR__ + PGM_P truePtr = PSTR("true"); + PGM_P falsePtr = PSTR("false"); +#else // __AVR__ + const char* truePtr = "true"; + const char* falsePtr = "false"; +#endif // __AVR + const uint8_t true_len = 4; + const uint8_t false_len = 5; + bool trueOk = true; + bool falseOk = true; + uint8_t i = 0; + int c = readSkip(); + while (1) { +#ifdef __AVR__ + falseOk = falseOk && c == pgm_read_byte(falsePtr + i); + trueOk = trueOk && c == pgm_read_byte(truePtr + i); +#else // __AVR__ + falseOk = falseOk && c == falsePtr[i]; + trueOk = trueOk && c == truePtr[i]; +#endif // __AVR__ + if (trueOk == false && falseOk == false) { + break; + } + i++; + if (trueOk && i == true_len) { + *b = true; + return; + } + if (falseOk && i == false_len) { + *b = false; + return; + } + c = getch(); + } + setstate(failbit); +} +//------------------------------------------------------------------------------ +void istream::getChar(char* ch) { + int16_t c = readSkip(); + if (c < 0) { + setstate(failbit); + } else { + *ch = c; + } +} +//------------------------------------------------------------------------------ +// +// http://www.exploringbinary.com/category/numbers-in-computers/ +// +int16_t const EXP_LIMIT = 100; +static const uint32_t uint32_max = (uint32_t)-1; +bool istream::getDouble(double* value) { + bool got_digit = false; + bool got_dot = false; + bool neg; + int16_t c; + bool expNeg = false; + int16_t exp = 0; + int16_t fracExp = 0; + uint32_t frac = 0; + pos_t endPos; + double pow10; + double v; + + getpos(&endPos); + c = readSkip(); + neg = c == '-'; + if (c == '-' || c == '+') { + c = getch(); + } + while (1) { + if (isdigit(c)) { + got_digit = true; + if (frac < uint32_max/10) { + frac = frac * 10 + (c - '0'); + if (got_dot) { + fracExp--; + } + } else { + if (!got_dot) { + fracExp++; + } + } + } else if (!got_dot && c == '.') { + got_dot = true; + } else { + break; + } + if (fracExp < -EXP_LIMIT || fracExp > EXP_LIMIT) { + goto fail; + } + c = getch(&endPos); + } + if (!got_digit) { + goto fail; + } + if (c == 'e' || c == 'E') { + c = getch(); + expNeg = c == '-'; + if (c == '-' || c == '+') { + c = getch(); + } + while (isdigit(c)) { + if (exp > EXP_LIMIT) { + goto fail; + } + exp = exp * 10 + (c - '0'); + c = getch(&endPos); + } + } + v = static_cast(frac); + exp = expNeg ? fracExp - exp : fracExp + exp; + expNeg = exp < 0; + if (expNeg) { + exp = -exp; + } + pow10 = 10.0; + while (exp) { + if (exp & 1) { + if (expNeg) { + // check for underflow + if (v < DBL_MIN * pow10 && frac != 0) { + goto fail; + } + v /= pow10; + } else { + // check for overflow + if (v > DBL_MAX / pow10) { + goto fail; + } + v *= pow10; + } + } + pow10 *= pow10; + exp >>= 1; + } + setpos(&endPos); + *value = neg ? -v : v; + return true; + + fail: + // error restore position to last good place + setpos(&endPos); + setstate(failbit); + return false; +} +//------------------------------------------------------------------------------ + +istream& istream::getline(char *str, streamsize n, char delim) { + pos_t pos; + int c; + m_gcount = 0; + if (n > 0) { + str[0] = '\0'; + } + while (1) { + c = getch(&pos); + if (c < 0) { + break; + } + if (c == delim) { + m_gcount++; + break; + } + if ((m_gcount + 1) >= n) { + setpos(&pos); + setstate(failbit); + break; + } + str[m_gcount++] = c; + str[m_gcount] = '\0'; + } + if (m_gcount == 0) { + setstate(failbit); + } + return *this; +} +//------------------------------------------------------------------------------ +bool istream::getNumber(uint32_t posMax, uint32_t negMax, uint32_t* num) { + int16_t c; + int8_t any = 0; + int8_t have_zero = 0; + uint8_t neg; + uint32_t val = 0; + uint32_t cutoff; + uint8_t cutlim; + pos_t endPos; + uint8_t f = flags() & basefield; + uint8_t base = f == oct ? 8 : f != hex ? 10 : 16; + getpos(&endPos); + c = readSkip(); + + neg = c == '-' ? 1 : 0; + if (c == '-' || c == '+') { + c = getch(); + } + + if (base == 16 && c == '0') { // TESTSUITE + c = getch(&endPos); + if (c == 'X' || c == 'x') { + c = getch(); + // remember zero in case no hex digits follow x/X + have_zero = 1; + } else { + any = 1; + } + } + // set values for overflow test + cutoff = neg ? negMax : posMax; + cutlim = cutoff % base; + cutoff /= base; + + while (1) { + if (isdigit(c)) { + c -= '0'; + } else if (isalpha(c)) { + c -= isupper(c) ? 'A' - 10 : 'a' - 10; + } else { + break; + } + if (c >= base) { + break; + } + if (val > cutoff || (val == cutoff && c > cutlim)) { + // indicate overflow error + any = -1; + break; + } + val = val * base + c; + c = getch(&endPos); + any = 1; + } + setpos(&endPos); + if (any > 0 || (have_zero && any >= 0)) { + *num = neg ? -val : val; + return true; + } + setstate(failbit); + return false; +} +//------------------------------------------------------------------------------ +void istream::getStr(char *str) { + pos_t pos; + uint16_t i = 0; + uint16_t m = width() ? width() - 1 : 0XFFFE; + if (m != 0) { + getpos(&pos); + int c = readSkip(); + + while (i < m) { + if (c < 0) { + break; + } + if (isspace(c)) { + setpos(&pos); + break; + } + str[i++] = c; + c = getch(&pos); + } + } + str[i] = '\0'; + if (i == 0) { + setstate(failbit); + } + width(0); +} +//------------------------------------------------------------------------------ +istream& istream::ignore(streamsize n, int delim) { + int c; + m_gcount = 0; + while (m_gcount < n) { + c = getch(); + if (c < 0) { + break; + } + m_gcount++; + if (c == delim) { + break; + } + } + return *this; +} +//------------------------------------------------------------------------------ +int istream::peek() { + int16_t c; + pos_t pos; + m_gcount = 0; + getpos(&pos); + c = getch(); + if (c < 0) { + if (!bad()) { + setstate(eofbit); + } + } else { + setpos(&pos); + } + return c; +} +//------------------------------------------------------------------------------ +int16_t istream::readSkip() { + int16_t c; + do { + c = getch(); + } while (isspace(c) && (flags() & skipws)); + return c; +} +//------------------------------------------------------------------------------ +/** used to implement ws() */ +void istream::skipWhite() { + int c; + pos_t pos; + do { + c = getch(&pos); + } while (isspace(c)); + setpos(&pos); +} diff --git a/Firmware_V3/lib/SdFat/src/iostream/istream.h b/Firmware_V3/lib/SdFat/src/iostream/istream.h new file mode 100644 index 0000000..5ad2fea --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/iostream/istream.h @@ -0,0 +1,384 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef istream_h +#define istream_h +/** + * \file + * \brief \ref istream class + */ +#include "ios.h" + +/** + * \class istream + * \brief Input Stream + */ +class istream : public virtual ios { + public: + istream() {} + /** call manipulator + * \param[in] pf function to call + * \return the stream + */ + istream& operator>>(istream& (*pf)(istream& str)) { + return pf(*this); + } + /** call manipulator + * \param[in] pf function to call + * \return the stream + */ + istream& operator>>(ios_base& (*pf)(ios_base& str)) { + pf(*this); + return *this; + } + /** call manipulator + * \param[in] pf function to call + * \return the stream + */ + istream& operator>>(ios& (*pf)(ios& str)) { + pf(*this); + return *this; + } + /** + * Extract a character string + * \param[out] str location to store the string. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(char *str) { + getStr(str); + return *this; + } + /** + * Extract a character + * \param[out] ch location to store the character. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(char& ch) { + getChar(&ch); + return *this; + } + /** + * Extract a character string + * \param[out] str location to store the string. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(signed char *str) { + getStr(reinterpret_cast(str)); + return *this; + } + /** + * Extract a character + * \param[out] ch location to store the character. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(signed char& ch) { + getChar(reinterpret_cast(&ch)); + return *this; + } + /** + * Extract a character string + * \param[out] str location to store the string. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(unsigned char *str) { + getStr(reinterpret_cast(str)); + return *this; + } + /** + * Extract a character + * \param[out] ch location to store the character. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(unsigned char& ch) { + getChar(reinterpret_cast(&ch)); + return *this; + } + /** + * Extract a value of type bool. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>>(bool& arg) { + getBool(&arg); + return *this; + } + /** + * Extract a value of type short. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream &operator>>(short& arg) { // NOLINT + getNumber(&arg); + return *this; + } + /** + * Extract a value of type unsigned short. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream &operator>>(unsigned short& arg) { // NOLINT + getNumber(&arg); + return *this; + } + /** + * Extract a value of type int. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream &operator>>(int& arg) { + getNumber(&arg); + return *this; + } + /** + * Extract a value of type unsigned int. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream &operator>>(unsigned int& arg) { + getNumber(&arg); + return *this; + } + /** + * Extract a value of type long. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream &operator>>(long& arg) { // NOLINT + getNumber(&arg); + return *this; + } + /** + * Extract a value of type unsigned long. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream &operator>>(unsigned long& arg) { // NOLINT + getNumber(&arg); + return *this; + } + /** + * Extract a value of type double. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream &operator>> (double& arg) { + getDouble(&arg); + return *this; + } + /** + * Extract a value of type float. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream &operator>> (float& arg) { + double v; + getDouble(&v); + arg = v; + return *this; + } + /** + * Extract a value of type void*. + * \param[out] arg location to store the value. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& operator>> (void*& arg) { + uint32_t val; + getNumber(&val); + arg = reinterpret_cast(val); + return *this; + } + /** + * \return The number of characters extracted by the last unformatted + * input function. + */ + streamsize gcount() const { + return m_gcount; + } + /** + * Extract a character if one is available. + * + * \return The character or -1 if a failure occurs. A failure is indicated + * by the stream state. + */ + int get(); + /** + * Extract a character if one is available. + * + * \param[out] ch location to receive the extracted character. + * + * \return always returns *this. A failure is indicated by the stream state. + */ + istream& get(char& ch); + /** + * Extract characters. + * + * \param[out] str Location to receive extracted characters. + * \param[in] n Size of str. + * \param[in] delim Delimiter + * + * Characters are extracted until extraction fails, n is less than 1, + * n-1 characters are extracted, or the next character equals + * \a delim (delim is not extracted). If no characters are extracted + * failbit is set. If end-of-file occurs the eofbit is set. + * + * \return always returns *this. A failure is indicated by the stream state. + */ + istream& get(char *str, streamsize n, char delim = '\n'); + /** + * Extract characters + * + * \param[out] str Location to receive extracted characters. + * \param[in] n Size of str. + * \param[in] delim Delimiter + * + * Characters are extracted until extraction fails, + * the next character equals \a delim (delim is extracted), or n-1 + * characters are extracted. + * + * The failbit is set if no characters are extracted or n-1 characters + * are extracted. If end-of-file occurs the eofbit is set. + * + * \return always returns *this. A failure is indicated by the stream state. + */ + istream& getline(char *str, streamsize n, char delim = '\n'); + /** + * Extract characters and discard them. + * + * \param[in] n maximum number of characters to ignore. + * \param[in] delim Delimiter. + * + * Characters are extracted until extraction fails, \a n characters + * are extracted, or the next input character equals \a delim + * (the delimiter is extracted). If end-of-file occurs the eofbit is set. + * + * Failures are indicated by the state of the stream. + * + * \return *this + * + */ + istream& ignore(streamsize n = 1, int delim = -1); + /** + * Return the next available character without consuming it. + * + * \return The character if the stream state is good else -1; + * + */ + int peek(); +// istream& read(char *str, streamsize count); +// streamsize readsome(char *str, streamsize count); + /** + * \return the stream position + */ + pos_type tellg() { + return tellpos(); + } + /** + * Set the stream position + * \param[in] pos The absolute position in which to move the read pointer. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& seekg(pos_type pos) { + if (!seekpos(pos)) { + setstate(failbit); + } + return *this; + } + /** + * Set the stream position. + * + * \param[in] off An offset to move the read pointer relative to way. + * \a off is a signed 32-bit int so the offset is limited to +- 2GB. + * \param[in] way One of ios::beg, ios::cur, or ios::end. + * \return Is always *this. Failure is indicated by the state of *this. + */ + istream& seekg(off_type off, seekdir way) { + if (!seekoff(off, way)) { + setstate(failbit); + } + return *this; + } + void skipWhite(); + + protected: + /// @cond SHOW_PROTECTED + /** + * Internal - do not use + * \return + */ + virtual int16_t getch() = 0; + /** + * Internal - do not use + * \param[out] pos + * \return + */ + int16_t getch(pos_t* pos) { + getpos(pos); + return getch(); + } + /** + * Internal - do not use + * \param[out] pos + */ + virtual void getpos(pos_t* pos) = 0; + /** + * Internal - do not use + * \param[in] pos + */ + virtual bool seekoff(off_type off, seekdir way) = 0; + virtual bool seekpos(pos_type pos) = 0; + virtual void setpos(pos_t* pos) = 0; + virtual pos_type tellpos() = 0; + + /// @endcond + private: + void getBool(bool *b); + void getChar(char* ch); + bool getDouble(double* value); + template void getNumber(T* value); + bool getNumber(uint32_t posMax, uint32_t negMax, uint32_t* num); + void getStr(char *str); + int16_t readSkip(); + + size_t m_gcount; +}; +//------------------------------------------------------------------------------ +template +void istream::getNumber(T* value) { + uint32_t tmp; + if ((T)-1 < 0) { + // number is signed, max positive value + uint32_t const m = ((uint32_t)-1) >> (33 - sizeof(T) * 8); + // max absolute value of negative number is m + 1. + if (getNumber(m, m + 1, &tmp)) { + *value = (T)tmp; + } + } else { + // max unsigned value for T + uint32_t const m = (T)-1; + if (getNumber(m, m, &tmp)) { + *value = (T)tmp; + } + } +} +#endif // istream_h diff --git a/Firmware_V3/lib/SdFat/src/iostream/ostream.cpp b/Firmware_V3/lib/SdFat/src/iostream/ostream.cpp new file mode 100644 index 0000000..d7ac154 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/iostream/ostream.cpp @@ -0,0 +1,153 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include +#include "ostream.h" +#ifndef PSTR +#define PSTR(x) x +#endif // PSTR +//------------------------------------------------------------------------------ +void ostream::do_fill(unsigned len) { + for (; len < width(); len++) { + putch(fill()); + } + width(0); +} +//------------------------------------------------------------------------------ +void ostream::fill_not_left(unsigned len) { + if ((flags() & adjustfield) != left) { + do_fill(len); + } +} +//------------------------------------------------------------------------------ +void ostream::putBool(bool b) { + if (flags() & boolalpha) { + if (b) { + putPgm(PSTR("true")); + } else { + putPgm(PSTR("false")); + } + } else { + putChar(b ? '1' : '0'); + } +} +//------------------------------------------------------------------------------ +void ostream::putChar(char c) { + fill_not_left(1); + putch(c); + do_fill(1); +} +//------------------------------------------------------------------------------ +void ostream::putDouble(double n) { + uint8_t nd = precision(); + double round = 0.5; + char sign; + char buf[13]; // room for sign, 10 digits, '.', and zero byte + char *ptr = buf + sizeof(buf) - 1; + char *str = ptr; + // terminate string + *ptr = '\0'; + + // get sign and make nonnegative + if (n < 0.0) { + sign = '-'; + n = -n; + } else { + sign = flags() & showpos ? '+' : '\0'; + } + // check for larger than uint32_t + if (n > 4.0E9) { + putPgm(PSTR("BIG FLT")); + return; + } + // round up and separate int and fraction parts + for (uint8_t i = 0; i < nd; ++i) { + round *= 0.1; + } + n += round; + uint32_t intPart = n; + double fractionPart = n - intPart; + + // format intPart and decimal point + if (nd || (flags() & showpoint)) { + *--str = '.'; + } + str = fmtNum(intPart, str, 10); + + // calculate length for fill + uint8_t len = sign ? 1 : 0; + len += nd + ptr - str; + + // extract adjust field + fmtflags adj = flags() & adjustfield; + if (adj == internal) { + if (sign) { + putch(sign); + } + do_fill(len); + } else { + // do fill for right + fill_not_left(len); + if (sign) { + *--str = sign; + } + } + putstr(str); + // output fraction + while (nd-- > 0) { + fractionPart *= 10.0; + int digit = static_cast(fractionPart); + putch(digit + '0'); + fractionPart -= digit; + } + // do fill if not done above + do_fill(len); +} +//------------------------------------------------------------------------------ +void ostream::putNum(int32_t n) { + bool neg = n < 0 && flagsToBase() == 10; + putNum((uint32_t)(neg ? -n : n), neg); +} +//------------------------------------------------------------------------------ +void ostream::putNum(int64_t n) { + bool neg = n < 0 && flagsToBase() == 10; + putNum((uint64_t)(neg ? -n : n), neg); +} +//------------------------------------------------------------------------------ +void ostream::putPgm(const char* str) { + int n; + for (n = 0; pgm_read_byte(&str[n]); n++) {} + fill_not_left(n); + for (uint8_t c; (c = pgm_read_byte(str)); str++) { + putch(c); + } + do_fill(n); +} +//------------------------------------------------------------------------------ +void ostream::putStr(const char *str) { + unsigned n = strlen(str); + fill_not_left(n); + putstr(str); + do_fill(n); +} diff --git a/Firmware_V3/lib/SdFat/src/iostream/ostream.h b/Firmware_V3/lib/SdFat/src/iostream/ostream.h new file mode 100644 index 0000000..09f501c --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/iostream/ostream.h @@ -0,0 +1,348 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef ostream_h +#define ostream_h +/** + * \file + * \brief \ref ostream class + */ +#include "ios.h" +//============================================================================== +/** + * \class ostream + * \brief Output Stream + */ +class ostream : public virtual ios { + public: + ostream() {} + + /** call manipulator + * \param[in] pf function to call + * \return the stream + */ + ostream& operator<< (ostream& (*pf)(ostream& str)) { + return pf(*this); + } + /** call manipulator + * \param[in] pf function to call + * \return the stream + */ + ostream& operator<< (ios_base& (*pf)(ios_base& str)) { + pf(*this); + return *this; + } + /** Output bool + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<< (bool arg) { + putBool(arg); + return *this; + } + /** Output string + * \param[in] arg string to output + * \return the stream + */ + ostream &operator<< (const char *arg) { + putStr(arg); + return *this; + } + /** Output string + * \param[in] arg string to output + * \return the stream + */ + ostream &operator<< (const signed char *arg) { + putStr((const char*)arg); + return *this; + } + /** Output string + * \param[in] arg string to output + * \return the stream + */ + ostream &operator<< (const unsigned char *arg) { + putStr((const char*)arg); + return *this; + } +#if ENABLE_ARDUINO_STRING + /** Output string + * \param[in] arg string to output + * \return the stream + */ + ostream &operator<< (const String& arg) { + putStr(arg.c_str()); + return *this; + } +#endif // ENABLE_ARDUINO_STRING + /** Output character + * \param[in] arg character to output + * \return the stream + */ + ostream &operator<< (char arg) { + putChar(arg); + return *this; + } + /** Output character + * \param[in] arg character to output + * \return the stream + */ + ostream &operator<< (signed char arg) { + putChar(static_cast(arg)); + return *this; + } + /** Output character + * \param[in] arg character to output + * \return the stream + */ + ostream &operator<< (unsigned char arg) { + putChar(static_cast(arg)); + return *this; + } + /** Output double + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<< (double arg) { + putDouble(arg); + return *this; + } + /** Output float + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<< (float arg) { + putDouble(arg); + return *this; + } + /** Output signed short + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<< (short arg) { // NOLINT + putNum((int32_t)arg); + return *this; + } + /** Output unsigned short + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<< (unsigned short arg) { // NOLINT + putNum((uint32_t)arg); + return *this; + } + /** Output signed int + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<< (int arg) { + putNum((int32_t)arg); + return *this; + } + /** Output unsigned int + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<< (unsigned int arg) { + putNum((uint32_t)arg); + return *this; + } + /** Output signed long + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<< (long arg) { // NOLINT + putNum((int32_t)arg); + return *this; + } + /** Output unsigned long + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<< (unsigned long arg) { // NOLINT + putNum((uint32_t)arg); + return *this; + } + /** Output signed long long + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<< (long long arg) { // NOLINT + putNum((int64_t)arg); + return *this; + } + /** Output unsigned long long + * \param[in] arg value to output + * \return the stream + */ + ostream &operator<< (unsigned long long arg) { // NOLINT + putNum((uint64_t)arg); + return *this; + } + /** Output pointer + * \param[in] arg value to output + * \return the stream + */ + ostream& operator<< (const void* arg) { + putNum(reinterpret_cast(arg)); + return *this; + } + /** Output a string from flash using the Arduino F() macro. + * \param[in] arg pointing to flash string + * \return the stream + */ + ostream &operator<< (const __FlashStringHelper *arg) { + putPgm(reinterpret_cast(arg)); + return *this; + } + /** + * Puts a character in a stream. + * + * The unformatted output function inserts the element \a ch. + * It returns *this. + * + * \param[in] ch The character + * \return A reference to the ostream object. + */ + ostream& put(char ch) { + putch(ch); + return *this; + } +// ostream& write(char *str, streamsize count); + /** + * Flushes the buffer associated with this stream. The flush function + * calls the sync function of the associated file. + * \return A reference to the ostream object. + */ + ostream& flush() { + if (!sync()) { + setstate(badbit); + } + return *this; + } + /** + * \return the stream position + */ + pos_type tellp() { + return tellpos(); + } + /** + * Set the stream position + * \param[in] pos The absolute position in which to move the write pointer. + * \return Is always *this. Failure is indicated by the state of *this. + */ + ostream& seekp(pos_type pos) { + if (!seekpos(pos)) { + setstate(failbit); + } + return *this; + } + /** + * Set the stream position. + * + * \param[in] off An offset to move the write pointer relative to way. + * \a off is a signed 32-bit int so the offset is limited to +- 2GB. + * \param[in] way One of ios::beg, ios::cur, or ios::end. + * \return Is always *this. Failure is indicated by the state of *this. + */ + ostream& seekp(off_type off, seekdir way) { + if (!seekoff(off, way)) { + setstate(failbit); + } + return *this; + } + + protected: + /// @cond SHOW_PROTECTED + /** Put character with binary/text conversion + * \param[in] ch character to write + */ + virtual void putch(char ch) = 0; + virtual void putstr(const char *str) = 0; + virtual bool seekoff(off_type pos, seekdir way) = 0; + virtual bool seekpos(pos_type pos) = 0; + virtual bool sync() = 0; + virtual pos_type tellpos() = 0; + /// @endcond + private: + void do_fill(unsigned len); + void fill_not_left(unsigned len); + void putBool(bool b); + void putChar(char c); + void putDouble(double n); + void putNum(int32_t n); + void putNum(int64_t n); + void putNum(uint32_t n) {putNum(n, false);} + void putNum(uint64_t n) {putNum(n, false);} + void putPgm(const char* str); + void putStr(const char* str); + + template + char* fmtNum(T n, char *ptr, uint8_t base) { + char a = flags() & uppercase ? 'A' - 10 : 'a' - 10; + do { + T m = n; + n /= base; + char c = m - base * n; + *--ptr = c < 10 ? c + '0' : c + a; + } while (n); + return ptr; + } + + template + void putNum(T n, bool neg) { + char buf[(8*sizeof(T) + 2)/3 + 2]; + char* ptr = buf + sizeof(buf) - 1; + char* num; + char* str; + uint8_t base = flagsToBase(); + *ptr = '\0'; + str = num = fmtNum(n, ptr, base); + if (base == 10) { + if (neg) { + *--str = '-'; + } else if (flags() & showpos) { + *--str = '+'; + } + } else if (flags() & showbase) { + if (flags() & hex) { + *--str = flags() & uppercase ? 'X' : 'x'; + } + *--str = '0'; + } + uint8_t len = ptr - str; + fmtflags adj = flags() & adjustfield; + if (adj == internal) { + while (str < num) { + putch(*str++); + } + do_fill(len); + } else { + // do fill for right + fill_not_left(len); + } + putstr(str); + do_fill(len); + } +}; +#endif // ostream_h diff --git a/Firmware_V3/lib/SdFat/src/sdios.h b/Firmware_V3/lib/SdFat/src/sdios.h new file mode 100644 index 0000000..2801704 --- /dev/null +++ b/Firmware_V3/lib/SdFat/src/sdios.h @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef sdios_h +#define sdios_h +/** + * \file + * \brief C++ IO Streams features. + */ +#include "iostream/ArduinoStream.h" +#include "iostream/fstream.h" +#include "iostream/StdioStream.h" +#endif // sdios_h diff --git a/Firmware_V3/lib/Time/DateStrings.cpp b/Firmware_V3/lib/Time/DateStrings.cpp new file mode 100644 index 0000000..2424678 --- /dev/null +++ b/Firmware_V3/lib/Time/DateStrings.cpp @@ -0,0 +1,97 @@ +/* DateStrings.cpp + * Definitions for date strings for use with the Time library + * + * Updated for Arduino 1.5.7 18 July 2014 + * + * No memory is consumed in the sketch if your code does not call any of the string methods + * You can change the text of the strings, make sure the short strings are each exactly 3 characters + * the long strings can be any length up to the constant dt_MAX_STRING_LEN defined in TimeLib.h + * + */ + +#include + +// Arduino.h should properly define PROGMEM, PGM_P, strcpy_P, pgm_read_byte, pgm_read_ptr +// But not all platforms define these as they should. If you find a platform needing these +// defined, or if any this becomes unnecessary as platforms improve, please send a pull req. +#if defined(ESP8266) +#undef PROGMEM +#define PROGMEM +#endif + +#include "TimeLib.h" + + +// the short strings for each day or month must be exactly dt_SHORT_STR_LEN +#define dt_SHORT_STR_LEN 3 // the length of short strings + +static char buffer[dt_MAX_STRING_LEN+1]; // must be big enough for longest string and the terminating null + +const char monthStr0[] PROGMEM = ""; +const char monthStr1[] PROGMEM = "January"; +const char monthStr2[] PROGMEM = "February"; +const char monthStr3[] PROGMEM = "March"; +const char monthStr4[] PROGMEM = "April"; +const char monthStr5[] PROGMEM = "May"; +const char monthStr6[] PROGMEM = "June"; +const char monthStr7[] PROGMEM = "July"; +const char monthStr8[] PROGMEM = "August"; +const char monthStr9[] PROGMEM = "September"; +const char monthStr10[] PROGMEM = "October"; +const char monthStr11[] PROGMEM = "November"; +const char monthStr12[] PROGMEM = "December"; + +const PROGMEM char * const PROGMEM monthNames_P[] = +{ + monthStr0,monthStr1,monthStr2,monthStr3,monthStr4,monthStr5,monthStr6, + monthStr7,monthStr8,monthStr9,monthStr10,monthStr11,monthStr12 +}; + +const char monthShortNames_P[] PROGMEM = "ErrJanFebMarAprMayJunJulAugSepOctNovDec"; + +const char dayStr0[] PROGMEM = "Err"; +const char dayStr1[] PROGMEM = "Sunday"; +const char dayStr2[] PROGMEM = "Monday"; +const char dayStr3[] PROGMEM = "Tuesday"; +const char dayStr4[] PROGMEM = "Wednesday"; +const char dayStr5[] PROGMEM = "Thursday"; +const char dayStr6[] PROGMEM = "Friday"; +const char dayStr7[] PROGMEM = "Saturday"; + +const PROGMEM char * const PROGMEM dayNames_P[] = +{ + dayStr0,dayStr1,dayStr2,dayStr3,dayStr4,dayStr5,dayStr6,dayStr7 +}; + +const char dayShortNames_P[] PROGMEM = "ErrSunMonTueWedThuFriSat"; + +/* functions to return date strings */ + +char* monthStr(uint8_t month) +{ + strcpy_P(buffer, (PGM_P)pgm_read_ptr(&(monthNames_P[month]))); + return buffer; +} + +char* monthShortStr(uint8_t month) +{ + for (int i=0; i < dt_SHORT_STR_LEN; i++) + buffer[i] = pgm_read_byte(&(monthShortNames_P[i+ (month*dt_SHORT_STR_LEN)])); + buffer[dt_SHORT_STR_LEN] = 0; + return buffer; +} + +char* dayStr(uint8_t day) +{ + strcpy_P(buffer, (PGM_P)pgm_read_ptr(&(dayNames_P[day]))); + return buffer; +} + +char* dayShortStr(uint8_t day) +{ + uint8_t index = day*dt_SHORT_STR_LEN; + for (int i=0; i < dt_SHORT_STR_LEN; i++) + buffer[i] = pgm_read_byte(&(dayShortNames_P[index + i])); + buffer[dt_SHORT_STR_LEN] = 0; + return buffer; +} diff --git a/Firmware_V3/lib/Time/Readme.md b/Firmware_V3/lib/Time/Readme.md new file mode 100644 index 0000000..ba47aeb --- /dev/null +++ b/Firmware_V3/lib/Time/Readme.md @@ -0,0 +1,165 @@ +# Arduino Time Library + +Time is a library that provides timekeeping functionality for Arduino. + +Using the Arduino Library Manager, install "*Time* by *Michael Margolis*". + +The code is derived from the Playground DateTime library but is updated +to provide an API that is more flexible and easier to use. + +A primary goal was to enable date and time functionality that can be used with +a variety of external time sources with minimum differences required in sketch logic. + +Example sketches illustrate how similar sketch code can be used with: a Real Time Clock, +internet NTP time service, GPS time data, and Serial time messages from a computer +for time synchronization. + +## Functionality + +To use the Time library in an Arduino sketch, include TimeLib.h. + +```c +#include +``` + +The functions available in the library include + +```c +hour(); // the hour now (0-23) +minute(); // the minute now (0-59) +second(); // the second now (0-59) +day(); // the day now (1-31) +weekday(); // day of the week (1-7), Sunday is day 1 +month(); // the month now (1-12) +year(); // the full four digit year: (2009, 2010 etc) +``` + +there are also functions to return the hour in 12-hour format + +```c +hourFormat12(); // the hour now in 12 hour format +isAM(); // returns true if time now is AM +isPM(); // returns true if time now is PM + +now(); // returns the current time as seconds since Jan 1 1970 +``` + +The time and date functions can take an optional parameter for the time. This prevents +errors if the time rolls over between elements. For example, if a new minute begins +between getting the minute and second, the values will be inconsistent. Using the +following functions eliminates this problem + +```c +time_t t = now(); // store the current time in time variable t +hour(t); // returns the hour for the given time t +minute(t); // returns the minute for the given time t +second(t); // returns the second for the given time t +day(t); // the day for the given time t +weekday(t); // day of the week for the given time t +month(t); // the month for the given time t +year(t); // the year for the given time t +``` + +Functions for managing the timer services are: + +```c +setTime(t); // set the system time to the give time t +setTime(hr,min,sec,day,mnth,yr); // alternative to above, yr is 2 or 4 digit yr + // (2010 or 10 sets year to 2010) +adjustTime(adjustment); // adjust system time by adding the adjustment value +timeStatus(); // indicates if time has been set and recently synchronized + // returns one of the following enumerations: +timeNotSet // the time has never been set, the clock started on Jan 1, 1970 +timeNeedsSync // the time had been set but a sync attempt did not succeed +timeSet // the time is set and is synced +``` + +Time and Date values are not valid if the status is `timeNotSet`. Otherwise, values can be used but +the returned time may have drifted if the status is `timeNeedsSync`. + +```c +setSyncProvider(getTimeFunction); // set the external time provider +setSyncInterval(interval); // set the number of seconds between re-sync +``` + +There are many convenience macros in the `time.h` file for time constants and conversion +of time units. + +To use the library, copy the download to the Library directory. + +## Examples + +The Time directory contains the Time library and some example sketches +illustrating how the library can be used with various time sources: + +- `TimeSerial.pde` shows Arduino as a clock without external hardware. + It is synchronized by time messages sent over the serial port. + A companion Processing sketch will automatically provide these messages + if it is running and connected to the Arduino serial port. + +- `TimeSerialDateStrings.pde` adds day and month name strings to the sketch above. + Short (3 characters) and long strings are available to print the days of + the week and names of the months. + +- `TimeRTC` uses a DS1307 real-time clock to provide time synchronization. + The basic [DS1307RTC library][1] must be downloaded and installed, + in order to run this sketch. + +- `TimeRTCSet` is similar to the above and adds the ability to set the Real Time Clock. + +- `TimeRTCLog` demonstrates how to calculate the difference between times. + It is a very simple logger application that monitors events on digital pins + and prints (to the serial port) the time of an event and the time period since + the previous event. + +- `TimeNTP` uses the Arduino Ethernet shield to access time using the internet NTP time service. + The NTP protocol uses UDP and the UdpBytewise library is required, see: + + +- `TimeGPS` gets time from a GPS. + This requires the TinyGPS library from Mikal Hart: + + +## Differences + +Differences between this code and the playground DateTime library +although the Time library is based on the DateTime codebase, the API has changed. +Changes in the Time library API: + +- time elements are functions returning `int` (they are variables in DateTime) +- Years start from 1970 +- days of the week and months start from 1 (they start from 0 in DateTime) +- DateStrings do not require a separate library +- time elements can be accessed non-atomically (in DateTime they are always atomic) +- function added to automatically sync time with external source +- `localTime` and `maketime` parameters changed, `localTime` renamed to `breakTime` + +## Technical Notes + +Internal system time is based on the standard Unix `time_t`. +The value is the number of seconds since Jan 1, 1970. +System time begins at zero when the sketch starts. + +The internal time can be automatically synchronized at regular intervals to an external time source. +This is enabled by calling the `setSyncProvider(provider)` function - the provider argument is +the address of a function that returns the current time as a `time_t`. +See the sketches in the examples directory for usage. + +The default interval for re-syncing the time is 5 minutes but can be changed by calling the +`setSyncInterval(interval)` method to set the number of seconds between re-sync attempts. + +The Time library defines a structure for holding time elements that is a compact version of the C `tm` structure. +All the members of the Arduino `tm` structure are bytes and the year is offset from 1970. +Convenience macros provide conversion to and from the Arduino format. + +Low-level functions to convert between system time and individual time elements are provided: + +```c +breakTime(time, &tm); // break time_t into elements stored in tm struct +makeTime(&tm); // return time_t from elements stored in tm struct +``` + +This [DS1307RTC library][1] provides an example of how a time provider +can use the low-level functions to interface with the Time library. + +[1]: diff --git a/Firmware_V3/lib/Time/Time.cpp b/Firmware_V3/lib/Time/Time.cpp new file mode 100644 index 0000000..0dcb29f --- /dev/null +++ b/Firmware_V3/lib/Time/Time.cpp @@ -0,0 +1,321 @@ +/* + time.c - low level time and date functions + Copyright (c) Michael Margolis 2009-2014 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + 1.0 6 Jan 2010 - initial release + 1.1 12 Feb 2010 - fixed leap year calculation error + 1.2 1 Nov 2010 - fixed setTime bug (thanks to Korman for this) + 1.3 24 Mar 2012 - many edits by Paul Stoffregen: fixed timeStatus() to update + status, updated examples for Arduino 1.0, fixed ARM + compatibility issues, added TimeArduinoDue and TimeTeensy3 + examples, add error checking and messages to RTC examples, + add examples to DS1307RTC library. + 1.4 5 Sep 2014 - compatibility with Arduino 1.5.7 +*/ + +#if ARDUINO >= 100 +#include +#else +#include +#endif + +#include "TimeLib.h" + +static tmElements_t tm; // a cache of time elements +static time_t cacheTime; // the time the cache was updated +static uint32_t syncInterval = 300; // time sync will be attempted after this many seconds + +void refreshCache(time_t t) { + if (t != cacheTime) { + breakTime(t, tm); + cacheTime = t; + } +} + +int hour() { // the hour now + return hour(now()); +} + +int hour(time_t t) { // the hour for the given time + refreshCache(t); + return tm.Hour; +} + +int hourFormat12() { // the hour now in 12 hour format + return hourFormat12(now()); +} + +int hourFormat12(time_t t) { // the hour for the given time in 12 hour format + refreshCache(t); + if( tm.Hour == 0 ) + return 12; // 12 midnight + else if( tm.Hour > 12) + return tm.Hour - 12 ; + else + return tm.Hour ; +} + +uint8_t isAM() { // returns true if time now is AM + return !isPM(now()); +} + +uint8_t isAM(time_t t) { // returns true if given time is AM + return !isPM(t); +} + +uint8_t isPM() { // returns true if PM + return isPM(now()); +} + +uint8_t isPM(time_t t) { // returns true if PM + return (hour(t) >= 12); +} + +int minute() { + return minute(now()); +} + +int minute(time_t t) { // the minute for the given time + refreshCache(t); + return tm.Minute; +} + +int second() { + return second(now()); +} + +int second(time_t t) { // the second for the given time + refreshCache(t); + return tm.Second; +} + +int day(){ + return(day(now())); +} + +int day(time_t t) { // the day for the given time (0-6) + refreshCache(t); + return tm.Day; +} + +int weekday() { // Sunday is day 1 + return weekday(now()); +} + +int weekday(time_t t) { + refreshCache(t); + return tm.Wday; +} + +int month(){ + return month(now()); +} + +int month(time_t t) { // the month for the given time + refreshCache(t); + return tm.Month; +} + +int year() { // as in Processing, the full four digit year: (2009, 2010 etc) + return year(now()); +} + +int year(time_t t) { // the year for the given time + refreshCache(t); + return tmYearToCalendar(tm.Year); +} + +/*============================================================================*/ +/* functions to convert to and from system time */ +/* These are for interfacing with time services and are not normally needed in a sketch */ + +// leap year calculator expects year argument as years offset from 1970 +#define LEAP_YEAR(Y) ( ((1970+(Y))>0) && !((1970+(Y))%4) && ( ((1970+(Y))%100) || !((1970+(Y))%400) ) ) + +static const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0 + +void breakTime(time_t timeInput, tmElements_t &tm){ +// break the given time_t into time components +// this is a more compact version of the C library localtime function +// note that year is offset from 1970 !!! + + uint8_t year; + uint8_t month, monthLength; + uint32_t time; + unsigned long days; + + time = (uint32_t)timeInput; + tm.Second = time % 60; + time /= 60; // now it is minutes + tm.Minute = time % 60; + time /= 60; // now it is hours + tm.Hour = time % 24; + time /= 24; // now it is days + tm.Wday = ((time + 4) % 7) + 1; // Sunday is day 1 + + year = 0; + days = 0; + while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) { + year++; + } + tm.Year = year; // year is offset from 1970 + + days -= LEAP_YEAR(year) ? 366 : 365; + time -= days; // now it is days in this year, starting at 0 + + days=0; + month=0; + monthLength=0; + for (month=0; month<12; month++) { + if (month==1) { // february + if (LEAP_YEAR(year)) { + monthLength=29; + } else { + monthLength=28; + } + } else { + monthLength = monthDays[month]; + } + + if (time >= monthLength) { + time -= monthLength; + } else { + break; + } + } + tm.Month = month + 1; // jan is month 1 + tm.Day = time + 1; // day of month +} + +time_t makeTime(const tmElements_t &tm){ +// assemble time elements into time_t +// note year argument is offset from 1970 (see macros in time.h to convert to other formats) +// previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9 + + int i; + uint32_t seconds; + + // seconds from 1970 till 1 jan 00:00:00 of the given year + seconds= tm.Year*(SECS_PER_DAY * 365); + for (i = 0; i < tm.Year; i++) { + if (LEAP_YEAR(i)) { + seconds += SECS_PER_DAY; // add extra days for leap years + } + } + + // add days for this year, months start from 1 + for (i = 1; i < tm.Month; i++) { + if ( (i == 2) && LEAP_YEAR(tm.Year)) { + seconds += SECS_PER_DAY * 29; + } else { + seconds += SECS_PER_DAY * monthDays[i-1]; //monthDay array starts from 0 + } + } + seconds+= (tm.Day-1) * SECS_PER_DAY; + seconds+= tm.Hour * SECS_PER_HOUR; + seconds+= tm.Minute * SECS_PER_MIN; + seconds+= tm.Second; + return (time_t)seconds; +} +/*=====================================================*/ +/* Low level system time functions */ + +static uint32_t sysTime = 0; +static uint32_t prevMillis = 0; +static uint32_t nextSyncTime = 0; +static timeStatus_t Status = timeNotSet; + +getExternalTime getTimePtr; // pointer to external sync function +//setExternalTime setTimePtr; // not used in this version + +#ifdef TIME_DRIFT_INFO // define this to get drift data +time_t sysUnsyncedTime = 0; // the time sysTime unadjusted by sync +#endif + + +time_t now() { + // calculate number of seconds passed since last call to now() + while (millis() - prevMillis >= 1000) { + // millis() and prevMillis are both unsigned ints thus the subtraction will always be the absolute value of the difference + sysTime++; + prevMillis += 1000; +#ifdef TIME_DRIFT_INFO + sysUnsyncedTime++; // this can be compared to the synced time to measure long term drift +#endif + } + if (nextSyncTime <= sysTime) { + if (getTimePtr != 0) { + time_t t = getTimePtr(); + if (t != 0) { + setTime(t); + } else { + nextSyncTime = sysTime + syncInterval; + Status = (Status == timeNotSet) ? timeNotSet : timeNeedsSync; + } + } + } + return (time_t)sysTime; +} + +void setTime(time_t t) { +#ifdef TIME_DRIFT_INFO + if(sysUnsyncedTime == 0) + sysUnsyncedTime = t; // store the time of the first call to set a valid Time +#endif + + sysTime = (uint32_t)t; + nextSyncTime = (uint32_t)t + syncInterval; + Status = timeSet; + prevMillis = millis(); // restart counting from now (thanks to Korman for this fix) +} + +void setTime(int hr,int min,int sec,int dy, int mnth, int yr){ + // year can be given as full four digit year or two digts (2010 or 10 for 2010); + //it is converted to years since 1970 + if( yr > 99) + yr = yr - 1970; + else + yr += 30; + tm.Year = yr; + tm.Month = mnth; + tm.Day = dy; + tm.Hour = hr; + tm.Minute = min; + tm.Second = sec; + setTime(makeTime(tm)); +} + +void adjustTime(long adjustment) { + sysTime += adjustment; +} + +// indicates if time has been set and recently synchronized +timeStatus_t timeStatus() { + now(); // required to actually update the status + return Status; +} + +void setSyncProvider( getExternalTime getTimeFunction){ + getTimePtr = getTimeFunction; + nextSyncTime = sysTime; + now(); // this will sync the clock +} + +void setSyncInterval(time_t interval){ // set the number of seconds between re-sync + syncInterval = (uint32_t)interval; + nextSyncTime = sysTime + syncInterval; +} diff --git a/Firmware_V3/lib/Time/Time.h b/Firmware_V3/lib/Time/Time.h new file mode 100644 index 0000000..a79b080 --- /dev/null +++ b/Firmware_V3/lib/Time/Time.h @@ -0,0 +1 @@ +#include "TimeLib.h" diff --git a/Firmware_V3/lib/Time/TimeLib.h b/Firmware_V3/lib/Time/TimeLib.h new file mode 100644 index 0000000..b587046 --- /dev/null +++ b/Firmware_V3/lib/Time/TimeLib.h @@ -0,0 +1,144 @@ +/* + time.h - low level time and date functions +*/ + +/* + July 3 2011 - fixed elapsedSecsThisWeek macro (thanks Vincent Valdy for this) + - fixed daysToTime_t macro (thanks maniacbug) +*/ + +#ifndef _Time_h +#ifdef __cplusplus +#define _Time_h + +#include +#ifndef __AVR__ +#include // for __time_t_defined, but avr libc lacks sys/types.h +#endif + + +#if !defined(__time_t_defined) // avoid conflict with newlib or other posix libc +typedef unsigned long time_t; +#endif + + +// This ugly hack allows us to define C++ overloaded functions, when included +// from within an extern "C", as newlib's sys/stat.h does. Actually it is +// intended to include "time.h" from the C library (on ARM, but AVR does not +// have that file at all). On Mac and Windows, the compiler will find this +// "Time.h" instead of the C library "time.h", so we may cause other weird +// and unpredictable effects by conflicting with the C library header "time.h", +// but at least this hack lets us define C++ functions as intended. Hopefully +// nothing too terrible will result from overriding the C library header?! +extern "C++" { +typedef enum {timeNotSet, timeNeedsSync, timeSet +} timeStatus_t ; + +typedef enum { + dowInvalid, dowSunday, dowMonday, dowTuesday, dowWednesday, dowThursday, dowFriday, dowSaturday +} timeDayOfWeek_t; + +typedef enum { + tmSecond, tmMinute, tmHour, tmWday, tmDay,tmMonth, tmYear, tmNbrFields +} tmByteFields; + +typedef struct { + uint8_t Second; + uint8_t Minute; + uint8_t Hour; + uint8_t Wday; // day of week, sunday is day 1 + uint8_t Day; + uint8_t Month; + uint8_t Year; // offset from 1970; +} tmElements_t, TimeElements, *tmElementsPtr_t; + +//convenience macros to convert to and from tm years +#define tmYearToCalendar(Y) ((Y) + 1970) // full four digit year +#define CalendarYrToTm(Y) ((Y) - 1970) +#define tmYearToY2k(Y) ((Y) - 30) // offset is from 2000 +#define y2kYearToTm(Y) ((Y) + 30) + +typedef time_t(*getExternalTime)(); +//typedef void (*setExternalTime)(const time_t); // not used in this version + + +/*==============================================================================*/ +/* Useful Constants */ +#define SECS_PER_MIN ((time_t)(60UL)) +#define SECS_PER_HOUR ((time_t)(3600UL)) +#define SECS_PER_DAY ((time_t)(SECS_PER_HOUR * 24UL)) +#define DAYS_PER_WEEK ((time_t)(7UL)) +#define SECS_PER_WEEK ((time_t)(SECS_PER_DAY * DAYS_PER_WEEK)) +#define SECS_PER_YEAR ((time_t)(SECS_PER_DAY * 365UL)) // TODO: ought to handle leap years +#define SECS_YR_2000 ((time_t)(946684800UL)) // the time at the start of y2k + +/* Useful Macros for getting elapsed time */ +#define numberOfSeconds(_time_) ((_time_) % SECS_PER_MIN) +#define numberOfMinutes(_time_) (((_time_) / SECS_PER_MIN) % SECS_PER_MIN) +#define numberOfHours(_time_) (((_time_) % SECS_PER_DAY) / SECS_PER_HOUR) +#define dayOfWeek(_time_) ((((_time_) / SECS_PER_DAY + 4) % DAYS_PER_WEEK)+1) // 1 = Sunday +#define elapsedDays(_time_) ((_time_) / SECS_PER_DAY) // this is number of days since Jan 1 1970 +#define elapsedSecsToday(_time_) ((_time_) % SECS_PER_DAY) // the number of seconds since last midnight +// The following macros are used in calculating alarms and assume the clock is set to a date later than Jan 1 1971 +// Always set the correct time before setting alarms +#define previousMidnight(_time_) (((_time_) / SECS_PER_DAY) * SECS_PER_DAY) // time at the start of the given day +#define nextMidnight(_time_) (previousMidnight(_time_) + SECS_PER_DAY) // time at the end of the given day +#define elapsedSecsThisWeek(_time_) (elapsedSecsToday(_time_) + ((dayOfWeek(_time_)-1) * SECS_PER_DAY)) // note that week starts on day 1 +#define previousSunday(_time_) ((_time_) - elapsedSecsThisWeek(_time_)) // time at the start of the week for the given time +#define nextSunday(_time_) (previousSunday(_time_)+SECS_PER_WEEK) // time at the end of the week for the given time + + +/* Useful Macros for converting elapsed time to a time_t */ +#define minutesToTime_t ((M)) ( (M) * SECS_PER_MIN) +#define hoursToTime_t ((H)) ( (H) * SECS_PER_HOUR) +#define daysToTime_t ((D)) ( (D) * SECS_PER_DAY) // fixed on Jul 22 2011 +#define weeksToTime_t ((W)) ( (W) * SECS_PER_WEEK) + +/*============================================================================*/ +/* time and date functions */ +int hour(); // the hour now +int hour(time_t t); // the hour for the given time +int hourFormat12(); // the hour now in 12 hour format +int hourFormat12(time_t t); // the hour for the given time in 12 hour format +uint8_t isAM(); // returns true if time now is AM +uint8_t isAM(time_t t); // returns true the given time is AM +uint8_t isPM(); // returns true if time now is PM +uint8_t isPM(time_t t); // returns true the given time is PM +int minute(); // the minute now +int minute(time_t t); // the minute for the given time +int second(); // the second now +int second(time_t t); // the second for the given time +int day(); // the day now +int day(time_t t); // the day for the given time +int weekday(); // the weekday now (Sunday is day 1) +int weekday(time_t t); // the weekday for the given time +int month(); // the month now (Jan is month 1) +int month(time_t t); // the month for the given time +int year(); // the full four digit year: (2009, 2010 etc) +int year(time_t t); // the year for the given time + +time_t now(); // return the current time as seconds since Jan 1 1970 +void setTime(time_t t); +void setTime(int hr,int min,int sec,int day, int month, int yr); +void adjustTime(long adjustment); + +/* date strings */ +#define dt_MAX_STRING_LEN 9 // length of longest date string (excluding terminating null) +char* monthStr(uint8_t month); +char* dayStr(uint8_t day); +char* monthShortStr(uint8_t month); +char* dayShortStr(uint8_t day); + +/* time sync functions */ +timeStatus_t timeStatus(); // indicates if time has been set and recently synchronized +void setSyncProvider( getExternalTime getTimeFunction); // identify the external time provider +void setSyncInterval(time_t interval); // set the number of seconds between re-sync + +/* low level functions to convert to and from system time */ +void breakTime(time_t time, tmElements_t &tm); // break time_t into elements +time_t makeTime(const tmElements_t &tm); // convert time elements into time_t + +} // extern "C++" +#endif // __cplusplus +#endif /* _Time_h */ + diff --git a/Firmware_V3/lib/Time/docs/issue_template.md b/Firmware_V3/lib/Time/docs/issue_template.md new file mode 100644 index 0000000..0610992 --- /dev/null +++ b/Firmware_V3/lib/Time/docs/issue_template.md @@ -0,0 +1,64 @@ +Please use this form only to report code defects or bugs. + +For any question, even questions directly pertaining to this code, post your question on the forums related to the board you are using. + +Arduino: forum.arduino.cc +Teensy: forum.pjrc.com +ESP8266: www.esp8266.com +ESP32: www.esp32.com +Adafruit Feather/Metro/Trinket: forums.adafruit.com +Particle Photon: community.particle.io + +If you are experiencing trouble but not certain of the cause, or need help using this code, ask on the appropriate forum. This is not the place to ask for support or help, even directly related to this code. Only use this form you are certain you have discovered a defect in this code! + +Please verify the problem occurs when using the very latest version, using the newest version of Arduino and any other related software. + + +----------------------------- Remove above ----------------------------- + + + +### Description + +Describe your problem. + + + +### Steps To Reproduce Problem + +Please give detailed instructions needed for anyone to attempt to reproduce the problem. + + + +### Hardware & Software + +Board +Shields / modules used +Arduino IDE version +Teensyduino version (if using Teensy) +Version info & package name (from Tools > Boards > Board Manager) +Operating system & version +Any other software or hardware? + + +### Arduino Sketch + +```cpp +// Change the code below by your sketch (please try to give the smallest code which demonstrates the problem) +#include + +// libraries: give links/details so anyone can compile your code for the same result + +void setup() { +} + +void loop() { +} +``` + + +### Errors or Incorrect Output + +If you see any errors or incorrect output, please show it here. Please use copy & paste to give an exact copy of the message. Details matter, so please show (not merely describe) the actual message or error exactly as it appears. + + diff --git a/Firmware_V3/lib/Time/examples/Processing/SyncArduinoClock/SyncArduinoClock.pde b/Firmware_V3/lib/Time/examples/Processing/SyncArduinoClock/SyncArduinoClock.pde new file mode 100644 index 0000000..62ee57b --- /dev/null +++ b/Firmware_V3/lib/Time/examples/Processing/SyncArduinoClock/SyncArduinoClock.pde @@ -0,0 +1,87 @@ +/** + * SyncArduinoClock. + * + * SyncArduinoClock is a Processing sketch that responds to Arduino + * requests for time synchronization messages. Run this in the + * Processing environment (not in Arduino) on your PC or Mac. + * + * Download TimeSerial onto Arduino and you should see the time + * message displayed when you run SyncArduinoClock in Processing. + * The Arduino time is set from the time on your computer through the + * Processing sketch. + * + * portIndex must be set to the port connected to the Arduino + * + * The current time is sent in response to request message from Arduino + * or by clicking the display window + * + * The time message is 11 ASCII text characters; a header (the letter 'T') + * followed by the ten digit system time (unix time) + */ + + +import processing.serial.*; +import java.util.Date; +import java.util.Calendar; +import java.util.GregorianCalendar; + +public static final short portIndex = 0; // select the com port, 0 is the first port +public static final String TIME_HEADER = "T"; //header for arduino serial time message +public static final char TIME_REQUEST = 7; // ASCII bell character +public static final char LF = 10; // ASCII linefeed +public static final char CR = 13; // ASCII linefeed +Serial myPort; // Create object from Serial class + +void setup() { + size(200, 200); + println(Serial.list()); + println(" Connecting to -> " + Serial.list()[portIndex]); + myPort = new Serial(this,Serial.list()[portIndex], 9600); + println(getTimeNow()); +} + +void draw() +{ + textSize(20); + textAlign(CENTER); + fill(0); + text("Click to send\nTime Sync", 0, 75, 200, 175); + if ( myPort.available() > 0) { // If data is available, + char val = char(myPort.read()); // read it and store it in val + if(val == TIME_REQUEST){ + long t = getTimeNow(); + sendTimeMessage(TIME_HEADER, t); + } + else + { + if(val == LF) + ; //igonore + else if(val == CR) + println(); + else + print(val); // echo everying but time request + } + } +} + +void mousePressed() { + sendTimeMessage( TIME_HEADER, getTimeNow()); +} + + +void sendTimeMessage(String header, long time) { + String timeStr = String.valueOf(time); + myPort.write(header); // send header and time to arduino + myPort.write(timeStr); + myPort.write('\n'); +} + +long getTimeNow(){ + // java time is in ms, we want secs + Date d = new Date(); + Calendar cal = new GregorianCalendar(); + long current = d.getTime()/1000; + long timezone = cal.get(cal.ZONE_OFFSET)/1000; + long daylight = cal.get(cal.DST_OFFSET)/1000; + return current + timezone + daylight; +} diff --git a/Firmware_V3/lib/Time/examples/Processing/SyncArduinoClock/readme.txt b/Firmware_V3/lib/Time/examples/Processing/SyncArduinoClock/readme.txt new file mode 100644 index 0000000..da9721d --- /dev/null +++ b/Firmware_V3/lib/Time/examples/Processing/SyncArduinoClock/readme.txt @@ -0,0 +1,9 @@ +SyncArduinoClock is a Processing sketch that responds to Arduino requests for +time synchronization messages. + +The portIndex must be set the Serial port connected to Arduino. + +Download TimeSerial.pde onto Arduino and you should see the time +message displayed when you run SyncArduinoClock in Processing. +The Arduino time is set from the time on your computer through the +Processing sketch. diff --git a/Firmware_V3/lib/Time/examples/TimeArduinoDue/TimeArduinoDue.ino b/Firmware_V3/lib/Time/examples/TimeArduinoDue/TimeArduinoDue.ino new file mode 100644 index 0000000..f0a9a95 --- /dev/null +++ b/Firmware_V3/lib/Time/examples/TimeArduinoDue/TimeArduinoDue.ino @@ -0,0 +1,71 @@ +/* + * TimeRTC.pde + * example code illustrating Time library with Real Time Clock. + * + * This example requires Markus Lange's Arduino Due RTC Library + * https://github.com/MarkusLange/Arduino-Due-RTC-Library + */ + +#include +#include + +// Select the Slowclock source +//RTC_clock rtc_clock(RC); +RTC_clock rtc_clock(XTAL); + +void setup() { + Serial.begin(9600); + rtc_clock.init(); + if (rtc_clock.date_already_set() == 0) { + // Unfortunately, the Arduino Due hardware does not seem to + // be designed to maintain the RTC clock state when the + // board resets. Markus described it thusly: "Uhh the Due + // does reset with the NRSTB pin. This resets the full chip + // with all backup regions including RTC, RTT and SC. Only + // if the reset is done with the NRST pin will these regions + // stay with their old values." + rtc_clock.set_time(__TIME__); + rtc_clock.set_date(__DATE__); + // However, this might work on other unofficial SAM3X boards + // with different reset circuitry than Arduino Due? + } + setSyncProvider(getArduinoDueTime); + if(timeStatus()!= timeSet) + Serial.println("Unable to sync with the RTC"); + else + Serial.println("RTC has set the system time"); +} + +time_t getArduinoDueTime() +{ + return rtc_clock.unixtime(); +} + +void loop() +{ + digitalClockDisplay(); + delay(1000); +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + diff --git a/Firmware_V3/lib/Time/examples/TimeGPS/TimeGPS.ino b/Firmware_V3/lib/Time/examples/TimeGPS/TimeGPS.ino new file mode 100644 index 0000000..fea9698 --- /dev/null +++ b/Firmware_V3/lib/Time/examples/TimeGPS/TimeGPS.ino @@ -0,0 +1,87 @@ +/* + * TimeGPS.pde + * example code illustrating time synced from a GPS + * + */ + +#include +#include // http://arduiniana.org/libraries/TinyGPS/ +#include +// TinyGPS and SoftwareSerial libraries are the work of Mikal Hart + +SoftwareSerial SerialGPS = SoftwareSerial(10, 11); // receive on pin 10 +TinyGPS gps; + +// To use a hardware serial port, which is far more efficient than +// SoftwareSerial, uncomment this line and remove SoftwareSerial +//#define SerialGPS Serial1 + +// Offset hours from gps time (UTC) +const int offset = 1; // Central European Time +//const int offset = -5; // Eastern Standard Time (USA) +//const int offset = -4; // Eastern Daylight Time (USA) +//const int offset = -8; // Pacific Standard Time (USA) +//const int offset = -7; // Pacific Daylight Time (USA) + +// Ideally, it should be possible to learn the time zone +// based on the GPS position data. However, that would +// require a complex library, probably incorporating some +// sort of database using Eric Muller's time zone shape +// maps, at http://efele.net/maps/tz/ + +time_t prevDisplay = 0; // when the digital clock was displayed + +void setup() +{ + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + SerialGPS.begin(4800); + Serial.println("Waiting for GPS time ... "); +} + +void loop() +{ + while (SerialGPS.available()) { + if (gps.encode(SerialGPS.read())) { // process gps messages + // when TinyGPS reports new data... + unsigned long age; + int Year; + byte Month, Day, Hour, Minute, Second; + gps.crack_datetime(&Year, &Month, &Day, &Hour, &Minute, &Second, NULL, &age); + if (age < 500) { + // set the Time to the latest GPS reading + setTime(Hour, Minute, Second, Day, Month, Year); + adjustTime(offset * SECS_PER_HOUR); + } + } + } + if (timeStatus()!= timeNotSet) { + if (now() != prevDisplay) { //update the display only if the time has changed + prevDisplay = now(); + digitalClockDisplay(); + } + } +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits) { + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + diff --git a/Firmware_V3/lib/Time/examples/TimeNTP/TimeNTP.ino b/Firmware_V3/lib/Time/examples/TimeNTP/TimeNTP.ino new file mode 100644 index 0000000..17a908f --- /dev/null +++ b/Firmware_V3/lib/Time/examples/TimeNTP/TimeNTP.ino @@ -0,0 +1,135 @@ +/* + * Time_NTP.pde + * Example showing time sync to NTP time source + * + * This sketch uses the Ethernet library + */ + +#include +#include +#include +#include + +byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; +// NTP Servers: +IPAddress timeServer(132, 163, 4, 101); // time-a.timefreq.bldrdoc.gov +// IPAddress timeServer(132, 163, 4, 102); // time-b.timefreq.bldrdoc.gov +// IPAddress timeServer(132, 163, 4, 103); // time-c.timefreq.bldrdoc.gov + + +const int timeZone = 1; // Central European Time +//const int timeZone = -5; // Eastern Standard Time (USA) +//const int timeZone = -4; // Eastern Daylight Time (USA) +//const int timeZone = -8; // Pacific Standard Time (USA) +//const int timeZone = -7; // Pacific Daylight Time (USA) + + +EthernetUDP Udp; +unsigned int localPort = 8888; // local port to listen for UDP packets + +void setup() +{ + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + delay(250); + Serial.println("TimeNTP Example"); + if (Ethernet.begin(mac) == 0) { + // no point in carrying on, so do nothing forevermore: + while (1) { + Serial.println("Failed to configure Ethernet using DHCP"); + delay(10000); + } + } + Serial.print("IP number assigned by DHCP is "); + Serial.println(Ethernet.localIP()); + Udp.begin(localPort); + Serial.println("waiting for sync"); + setSyncProvider(getNtpTime); +} + +time_t prevDisplay = 0; // when the digital clock was displayed + +void loop() +{ + if (timeStatus() != timeNotSet) { + if (now() != prevDisplay) { //update the display only if time has changed + prevDisplay = now(); + digitalClockDisplay(); + } + } +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits){ + // utility for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + +/*-------- NTP code ----------*/ + +const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message +byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets + +time_t getNtpTime() +{ + while (Udp.parsePacket() > 0) ; // discard any previously received packets + Serial.println("Transmit NTP Request"); + sendNTPpacket(timeServer); + uint32_t beginWait = millis(); + while (millis() - beginWait < 1500) { + int size = Udp.parsePacket(); + if (size >= NTP_PACKET_SIZE) { + Serial.println("Receive NTP Response"); + Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer + unsigned long secsSince1900; + // convert four bytes starting at location 40 to a long integer + secsSince1900 = (unsigned long)packetBuffer[40] << 24; + secsSince1900 |= (unsigned long)packetBuffer[41] << 16; + secsSince1900 |= (unsigned long)packetBuffer[42] << 8; + secsSince1900 |= (unsigned long)packetBuffer[43]; + return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR; + } + } + Serial.println("No NTP Response :-("); + return 0; // return 0 if unable to get the time +} + +// send an NTP request to the time server at the given address +void sendNTPpacket(IPAddress &address) +{ + // set all bytes in the buffer to 0 + memset(packetBuffer, 0, NTP_PACKET_SIZE); + // Initialize values needed to form NTP request + // (see URL above for details on the packets) + packetBuffer[0] = 0b11100011; // LI, Version, Mode + packetBuffer[1] = 0; // Stratum, or type of clock + packetBuffer[2] = 6; // Polling Interval + packetBuffer[3] = 0xEC; // Peer Clock Precision + // 8 bytes of zero for Root Delay & Root Dispersion + packetBuffer[12] = 49; + packetBuffer[13] = 0x4E; + packetBuffer[14] = 49; + packetBuffer[15] = 52; + // all NTP fields have been given values, now + // you can send a packet requesting a timestamp: + Udp.beginPacket(address, 123); //NTP requests are to port 123 + Udp.write(packetBuffer, NTP_PACKET_SIZE); + Udp.endPacket(); +} + diff --git a/Firmware_V3/lib/Time/examples/TimeNTP_ENC28J60/TimeNTP_ENC28J60.ino b/Firmware_V3/lib/Time/examples/TimeNTP_ENC28J60/TimeNTP_ENC28J60.ino new file mode 100644 index 0000000..b6ad796 --- /dev/null +++ b/Firmware_V3/lib/Time/examples/TimeNTP_ENC28J60/TimeNTP_ENC28J60.ino @@ -0,0 +1,168 @@ +/* + * Time_NTP.pde + * Example showing time sync to NTP time source + * + * Also shows how to handle DST automatically. + * + * This sketch uses the EtherCard library: + * http://jeelabs.org/pub/docs/ethercard/ + */ + +#include +#include + +byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; + +// NTP Server +const char timeServer[] PROGMEM = "pool.ntp.org"; + +const int utcOffset = 1; // Central European Time +//const int utcOffset = -5; // Eastern Standard Time (USA) +//const int utcOffset = -4; // Eastern Daylight Time (USA) +//const int utcOffset = -8; // Pacific Standard Time (USA) +//const int utcOffset = -7; // Pacific Daylight Time (USA) + +// Packet buffer, must be big enough to packet and payload +#define BUFFER_SIZE 550 +byte Ethernet::buffer[BUFFER_SIZE]; + +const unsigned int remotePort = 123; + +void setup() +{ + Serial.begin(9600); + + while (!Serial) // Needed for Leonardo only + ; + delay(250); + + Serial.println("TimeNTP_ENC28J60 Example"); + + if (ether.begin(BUFFER_SIZE, mac) == 0) { + // no point in carrying on, so do nothing forevermore: + while (1) { + Serial.println("Failed to access Ethernet controller"); + delay(10000); + } + } + + if (!ether.dhcpSetup()) { + // no point in carrying on, so do nothing forevermore: + while (1) { + Serial.println("Failed to configure Ethernet using DHCP"); + delay(10000); + } + } + + ether.printIp("IP number assigned by DHCP is ", ether.myip); + + Serial.println("waiting for sync"); + //setSyncProvider(getNtpTime); // Use this for GMT time + setSyncProvider(getDstCorrectedTime); // Use this for local, DST-corrected time +} + +time_t prevDisplay = 0; // when the digital clock was displayed + +void loop() +{ + if (timeStatus() != timeNotSet) { + if (now() != prevDisplay) { //update the display only if time has changed + prevDisplay = now(); + digitalClockDisplay(); + } + } +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits){ + // utility for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + +/*-------- NTP code ----------*/ + +// SyncProvider that returns UTC time +time_t getNtpTime() +{ + // Send request + Serial.println("Transmit NTP Request"); + if (!ether.dnsLookup(timeServer)) { + Serial.println("DNS failed"); + return 0; // return 0 if unable to get the time + } else { + //ether.printIp("SRV: ", ether.hisip); + ether.ntpRequest(ether.hisip, remotePort); + + // Wait for reply + uint32_t beginWait = millis(); + while (millis() - beginWait < 1500) { + word len = ether.packetReceive(); + ether.packetLoop(len); + + unsigned long secsSince1900 = 0L; + if (len > 0 && ether.ntpProcessAnswer(&secsSince1900, remotePort)) { + Serial.println("Receive NTP Response"); + return secsSince1900 - 2208988800UL; + } + } + + Serial.println("No NTP Response :-("); + return 0; + } +} + +/* Alternative SyncProvider that automatically handles Daylight Saving Time (DST) periods, + * at least in Europe, see below. + */ +time_t getDstCorrectedTime (void) { + time_t t = getNtpTime (); + + if (t > 0) { + TimeElements tm; + breakTime (t, tm); + t += (utcOffset + dstOffset (tm.Day, tm.Month, tm.Year + 1970, tm.Hour)) * SECS_PER_HOUR; + } + + return t; +} + +/* This function returns the DST offset for the current UTC time. + * This is valid for the EU, for other places see + * http://www.webexhibits.org/daylightsaving/i.html + * + * Results have been checked for 2012-2030 (but should work since + * 1996 to 2099) against the following references: + * - http://www.uniquevisitor.it/magazine/ora-legale-italia.php + * - http://www.calendario-365.it/ora-legale-orario-invernale.html + */ +byte dstOffset (byte d, byte m, unsigned int y, byte h) { + // Day in March that DST starts on, at 1 am + byte dstOn = (31 - (5 * y / 4 + 4) % 7); + + // Day in October that DST ends on, at 2 am + byte dstOff = (31 - (5 * y / 4 + 1) % 7); + + if ((m > 3 && m < 10) || + (m == 3 && (d > dstOn || (d == dstOn && h >= 1))) || + (m == 10 && (d < dstOff || (d == dstOff && h <= 1)))) + return 1; + else + return 0; +} + diff --git a/Firmware_V3/lib/Time/examples/TimeNTP_ESP8266WiFi/TimeNTP_ESP8266WiFi.ino b/Firmware_V3/lib/Time/examples/TimeNTP_ESP8266WiFi/TimeNTP_ESP8266WiFi.ino new file mode 100644 index 0000000..1aeb2d7 --- /dev/null +++ b/Firmware_V3/lib/Time/examples/TimeNTP_ESP8266WiFi/TimeNTP_ESP8266WiFi.ino @@ -0,0 +1,156 @@ +/* + * TimeNTP_ESP8266WiFi.ino + * Example showing time sync to NTP time source + * + * This sketch uses the ESP8266WiFi library + */ + +#include +#include +#include + +const char ssid[] = "*************"; // your network SSID (name) +const char pass[] = "********"; // your network password + +// NTP Servers: +static const char ntpServerName[] = "us.pool.ntp.org"; +//static const char ntpServerName[] = "time.nist.gov"; +//static const char ntpServerName[] = "time-a.timefreq.bldrdoc.gov"; +//static const char ntpServerName[] = "time-b.timefreq.bldrdoc.gov"; +//static const char ntpServerName[] = "time-c.timefreq.bldrdoc.gov"; + +const int timeZone = 1; // Central European Time +//const int timeZone = -5; // Eastern Standard Time (USA) +//const int timeZone = -4; // Eastern Daylight Time (USA) +//const int timeZone = -8; // Pacific Standard Time (USA) +//const int timeZone = -7; // Pacific Daylight Time (USA) + + +WiFiUDP Udp; +unsigned int localPort = 8888; // local port to listen for UDP packets + +time_t getNtpTime(); +void digitalClockDisplay(); +void printDigits(int digits); +void sendNTPpacket(IPAddress &address); + +void setup() +{ + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + delay(250); + Serial.println("TimeNTP Example"); + Serial.print("Connecting to "); + Serial.println(ssid); + WiFi.begin(ssid, pass); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.print("IP number assigned by DHCP is "); + Serial.println(WiFi.localIP()); + Serial.println("Starting UDP"); + Udp.begin(localPort); + Serial.print("Local port: "); + Serial.println(Udp.localPort()); + Serial.println("waiting for sync"); + setSyncProvider(getNtpTime); + setSyncInterval(300); +} + +time_t prevDisplay = 0; // when the digital clock was displayed + +void loop() +{ + if (timeStatus() != timeNotSet) { + if (now() != prevDisplay) { //update the display only if time has changed + prevDisplay = now(); + digitalClockDisplay(); + } + } +} + +void digitalClockDisplay() +{ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print("."); + Serial.print(month()); + Serial.print("."); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits) +{ + // utility for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if (digits < 10) + Serial.print('0'); + Serial.print(digits); +} + +/*-------- NTP code ----------*/ + +const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message +byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets + +time_t getNtpTime() +{ + IPAddress ntpServerIP; // NTP server's ip address + + while (Udp.parsePacket() > 0) ; // discard any previously received packets + Serial.println("Transmit NTP Request"); + // get a random server from the pool + WiFi.hostByName(ntpServerName, ntpServerIP); + Serial.print(ntpServerName); + Serial.print(": "); + Serial.println(ntpServerIP); + sendNTPpacket(ntpServerIP); + uint32_t beginWait = millis(); + while (millis() - beginWait < 1500) { + int size = Udp.parsePacket(); + if (size >= NTP_PACKET_SIZE) { + Serial.println("Receive NTP Response"); + Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer + unsigned long secsSince1900; + // convert four bytes starting at location 40 to a long integer + secsSince1900 = (unsigned long)packetBuffer[40] << 24; + secsSince1900 |= (unsigned long)packetBuffer[41] << 16; + secsSince1900 |= (unsigned long)packetBuffer[42] << 8; + secsSince1900 |= (unsigned long)packetBuffer[43]; + return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR; + } + } + Serial.println("No NTP Response :-("); + return 0; // return 0 if unable to get the time +} + +// send an NTP request to the time server at the given address +void sendNTPpacket(IPAddress &address) +{ + // set all bytes in the buffer to 0 + memset(packetBuffer, 0, NTP_PACKET_SIZE); + // Initialize values needed to form NTP request + // (see URL above for details on the packets) + packetBuffer[0] = 0b11100011; // LI, Version, Mode + packetBuffer[1] = 0; // Stratum, or type of clock + packetBuffer[2] = 6; // Polling Interval + packetBuffer[3] = 0xEC; // Peer Clock Precision + // 8 bytes of zero for Root Delay & Root Dispersion + packetBuffer[12] = 49; + packetBuffer[13] = 0x4E; + packetBuffer[14] = 49; + packetBuffer[15] = 52; + // all NTP fields have been given values, now + // you can send a packet requesting a timestamp: + Udp.beginPacket(address, 123); //NTP requests are to port 123 + Udp.write(packetBuffer, NTP_PACKET_SIZE); + Udp.endPacket(); +} diff --git a/Firmware_V3/lib/Time/examples/TimeRTC/TimeRTC.ino b/Firmware_V3/lib/Time/examples/TimeRTC/TimeRTC.ino new file mode 100644 index 0000000..fa10ff6 --- /dev/null +++ b/Firmware_V3/lib/Time/examples/TimeRTC/TimeRTC.ino @@ -0,0 +1,55 @@ +/* + * TimeRTC.pde + * example code illustrating Time library with Real Time Clock. + * + */ + +#include +#include +#include // a basic DS1307 library that returns time as a time_t + +void setup() { + Serial.begin(9600); + while (!Serial) ; // wait until Arduino Serial Monitor opens + setSyncProvider(RTC.get); // the function to get the time from the RTC + if(timeStatus()!= timeSet) + Serial.println("Unable to sync with the RTC"); + else + Serial.println("RTC has set the system time"); +} + +void loop() +{ + if (timeStatus() == timeSet) { + digitalClockDisplay(); + } else { + Serial.println("The time has not been set. Please run the Time"); + Serial.println("TimeRTCSet example, or DS1307RTC SetTime example."); + Serial.println(); + delay(4000); + } + delay(1000); +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + diff --git a/Firmware_V3/lib/Time/examples/TimeRTCLog/TimeRTCLog.ino b/Firmware_V3/lib/Time/examples/TimeRTCLog/TimeRTCLog.ino new file mode 100644 index 0000000..42fc3e4 --- /dev/null +++ b/Firmware_V3/lib/Time/examples/TimeRTCLog/TimeRTCLog.ino @@ -0,0 +1,109 @@ +/* + * TimeRTCLogger.ino + * example code illustrating adding and subtracting Time. + * + * this sketch logs pin state change events + * the time of the event and time since the previous event is calculated and sent to the serial port. + */ + +#include +#include +#include // a basic DS1307 library that returns time as a time_t + +const int nbrInputPins = 6; // monitor 6 digital pins +const int inputPins[nbrInputPins] = {2,3,4,5,6,7}; // pins to monitor +boolean state[nbrInputPins] ; // the state of the monitored pins +time_t prevEventTime[nbrInputPins] ; // the time of the previous event + +void setup() { + Serial.begin(9600); + setSyncProvider(RTC.get); // the function to sync the time from the RTC + for (int i=0; i < nbrInputPins; i++) { + pinMode( inputPins[i], INPUT); + // uncomment these lines if pull-up resistors are wanted + // pinMode( inputPins[i], INPUT_PULLUP); + // state[i] = HIGH; + } +} + +void loop() +{ + for (int i=0; i < nbrInputPins; i++) { + boolean val = digitalRead(inputPins[i]); + if (val != state[i]) { + time_t duration = 0; // the time since the previous event + state[i] = val; + time_t timeNow = now(); + if (prevEventTime[i] > 0) { + // if this was not the first state change, calculate the time from the previous change + duration = timeNow - prevEventTime[i]; + } + logEvent(inputPins[i], val, timeNow, duration ); // log the event + prevEventTime[i] = timeNow; // store the time for this event + } + } +} + +void logEvent( int pin, boolean state, time_t timeNow, time_t duration) +{ + Serial.print("Pin "); + Serial.print(pin); + if (state == HIGH) { + Serial.print(" went High at "); + } else { + Serial.print(" went Low at "); + } + showTime(timeNow); + if (duration > 0) { + // only display duration if greater than 0 + Serial.print(", Duration was "); + showDuration(duration); + } + Serial.println(); +} + + +void showTime(time_t t) +{ + // display the given time + Serial.print(hour(t)); + printDigits(minute(t)); + printDigits(second(t)); + Serial.print(" "); + Serial.print(day(t)); + Serial.print(" "); + Serial.print(month(t)); + Serial.print(" "); + Serial.print(year(t)); +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + +void showDuration(time_t duration) +{ + // prints the duration in days, hours, minutes and seconds + if (duration >= SECS_PER_DAY) { + Serial.print(duration / SECS_PER_DAY); + Serial.print(" day(s) "); + duration = duration % SECS_PER_DAY; + } + if (duration >= SECS_PER_HOUR) { + Serial.print(duration / SECS_PER_HOUR); + Serial.print(" hour(s) "); + duration = duration % SECS_PER_HOUR; + } + if (duration >= SECS_PER_MIN) { + Serial.print(duration / SECS_PER_MIN); + Serial.print(" minute(s) "); + duration = duration % SECS_PER_MIN; + } + Serial.print(duration); + Serial.print(" second(s) "); +} + diff --git a/Firmware_V3/lib/Time/examples/TimeRTCSet/TimeRTCSet.ino b/Firmware_V3/lib/Time/examples/TimeRTCSet/TimeRTCSet.ino new file mode 100644 index 0000000..49c49f3 --- /dev/null +++ b/Firmware_V3/lib/Time/examples/TimeRTCSet/TimeRTCSet.ino @@ -0,0 +1,80 @@ +/* + * TimeRTCSet.pde + * example code illustrating Time library with Real Time Clock. + * + * RTC clock is set in response to serial port time message + * A Processing example sketch to set the time is included in the download + * On Linux, you can use "date +T%s > /dev/ttyACM0" (UTC time zone) + */ + +#include +#include +#include // a basic DS1307 library that returns time as a time_t + + +void setup() { + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + setSyncProvider(RTC.get); // the function to get the time from the RTC + if (timeStatus() != timeSet) + Serial.println("Unable to sync with the RTC"); + else + Serial.println("RTC has set the system time"); +} + +void loop() +{ + if (Serial.available()) { + time_t t = processSyncMessage(); + if (t != 0) { + RTC.set(t); // set the RTC and the system time to the received value + setTime(t); + } + } + digitalClockDisplay(); + delay(1000); +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + +/* code to process time sync messages from the serial port */ +#define TIME_HEADER "T" // Header tag for serial time sync message + +unsigned long processSyncMessage() { + unsigned long pctime = 0L; + const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013 + + if(Serial.find(TIME_HEADER)) { + pctime = Serial.parseInt(); + return pctime; + if( pctime < DEFAULT_TIME) { // check the value is a valid time (greater than Jan 1 2013) + pctime = 0L; // return 0 to indicate that the time is not valid + } + } + return pctime; +} + + + + + diff --git a/Firmware_V3/lib/Time/examples/TimeSerial/TimeSerial.ino b/Firmware_V3/lib/Time/examples/TimeSerial/TimeSerial.ino new file mode 100644 index 0000000..07e609f --- /dev/null +++ b/Firmware_V3/lib/Time/examples/TimeSerial/TimeSerial.ino @@ -0,0 +1,81 @@ +/* + * TimeSerial.pde + * example code illustrating Time library set through serial port messages. + * + * Messages consist of the letter T followed by ten digit time (as seconds since Jan 1 1970) + * you can send the text on the next line using Serial Monitor to set the clock to noon Jan 1 2013 + T1357041600 + * + * A Processing example sketch to automatically send the messages is included in the download + * On Linux, you can use "date +T%s\n > /dev/ttyACM0" (UTC time zone) + */ + +#include + +#define TIME_HEADER "T" // Header tag for serial time sync message +#define TIME_REQUEST 7 // ASCII bell character requests a time sync message + +void setup() { + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + pinMode(13, OUTPUT); + setSyncProvider( requestSync); //set function to call when sync required + Serial.println("Waiting for sync message"); +} + +void loop(){ + if (Serial.available()) { + processSyncMessage(); + } + if (timeStatus()!= timeNotSet) { + digitalClockDisplay(); + } + if (timeStatus() == timeSet) { + digitalWrite(13, HIGH); // LED on if synced + } else { + digitalWrite(13, LOW); // LED off if needs refresh + } + delay(1000); +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + + +void processSyncMessage() { + unsigned long pctime; + const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013 + + if(Serial.find(TIME_HEADER)) { + pctime = Serial.parseInt(); + if( pctime >= DEFAULT_TIME) { // check the integer is a valid time (greater than Jan 1 2013) + setTime(pctime); // Sync Arduino clock to the time received on the serial port + } + } +} + +time_t requestSync() +{ + Serial.write(TIME_REQUEST); + return 0; // the time will be sent later in response to serial mesg +} + diff --git a/Firmware_V3/lib/Time/examples/TimeSerialDateStrings/TimeSerialDateStrings.ino b/Firmware_V3/lib/Time/examples/TimeSerialDateStrings/TimeSerialDateStrings.ino new file mode 100644 index 0000000..95d2568 --- /dev/null +++ b/Firmware_V3/lib/Time/examples/TimeSerialDateStrings/TimeSerialDateStrings.ino @@ -0,0 +1,108 @@ +/* + * TimeSerialDateStrings.pde + * example code illustrating Time library date strings + * + * This sketch adds date string functionality to TimeSerial sketch + * Also shows how to handle different messages + * + * A message starting with a time header sets the time + * A Processing example sketch to automatically send the messages is inclided in the download + * On Linux, you can use "date +T%s\n > /dev/ttyACM0" (UTC time zone) + * + * A message starting with a format header sets the date format + + * send: Fs\n for short date format + * send: Fl\n for long date format + */ + +#include + +// single character message tags +#define TIME_HEADER 'T' // Header tag for serial time sync message +#define FORMAT_HEADER 'F' // Header tag indicating a date format message +#define FORMAT_SHORT 's' // short month and day strings +#define FORMAT_LONG 'l' // (lower case l) long month and day strings + +#define TIME_REQUEST 7 // ASCII bell character requests a time sync message + +static boolean isLongFormat = true; + +void setup() { + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + setSyncProvider( requestSync); //set function to call when sync required + Serial.println("Waiting for sync message"); +} + +void loop(){ + if (Serial.available() > 1) { // wait for at least two characters + char c = Serial.read(); + if( c == TIME_HEADER) { + processSyncMessage(); + } + else if( c== FORMAT_HEADER) { + processFormatMessage(); + } + } + if (timeStatus()!= timeNotSet) { + digitalClockDisplay(); + } + delay(1000); +} + +void digitalClockDisplay() { + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + if(isLongFormat) + Serial.print(dayStr(weekday())); + else + Serial.print(dayShortStr(weekday())); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + if(isLongFormat) + Serial.print(monthStr(month())); + else + Serial.print(monthShortStr(month())); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits) { + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + +void processFormatMessage() { + char c = Serial.read(); + if( c == FORMAT_LONG){ + isLongFormat = true; + Serial.println(F("Setting long format")); + } + else if( c == FORMAT_SHORT) { + isLongFormat = false; + Serial.println(F("Setting short format")); + } +} + +void processSyncMessage() { + unsigned long pctime; + const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013 - paul, perhaps we define in time.h? + + pctime = Serial.parseInt(); + if( pctime >= DEFAULT_TIME) { // check the integer is a valid time (greater than Jan 1 2013) + setTime(pctime); // Sync Arduino clock to the time received on the serial port + } +} + +time_t requestSync() { + Serial.write(TIME_REQUEST); + return 0; // the time will be sent later in response to serial mesg +} diff --git a/Firmware_V3/lib/Time/examples/TimeTeensy3/TimeTeensy3.ino b/Firmware_V3/lib/Time/examples/TimeTeensy3/TimeTeensy3.ino new file mode 100644 index 0000000..f68dd8c --- /dev/null +++ b/Firmware_V3/lib/Time/examples/TimeTeensy3/TimeTeensy3.ino @@ -0,0 +1,78 @@ +/* + * TimeRTC.pde + * example code illustrating Time library with Real Time Clock. + * + */ + +#include + +void setup() { + // set the Time library to use Teensy 3.0's RTC to keep time + setSyncProvider(getTeensy3Time); + + Serial.begin(115200); + while (!Serial); // Wait for Arduino Serial Monitor to open + delay(100); + if (timeStatus()!= timeSet) { + Serial.println("Unable to sync with the RTC"); + } else { + Serial.println("RTC has set the system time"); + } +} + +void loop() { + if (Serial.available()) { + time_t t = processSyncMessage(); + if (t != 0) { + Teensy3Clock.set(t); // set the RTC + setTime(t); + } + } + digitalClockDisplay(); + delay(1000); +} + +void digitalClockDisplay() { + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +time_t getTeensy3Time() +{ + return Teensy3Clock.get(); +} + +/* code to process time sync messages from the serial port */ +#define TIME_HEADER "T" // Header tag for serial time sync message + +unsigned long processSyncMessage() { + unsigned long pctime = 0L; + const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013 + + if(Serial.find(TIME_HEADER)) { + pctime = Serial.parseInt(); + return pctime; + if( pctime < DEFAULT_TIME) { // check the value is a valid time (greater than Jan 1 2013) + pctime = 0L; // return 0 to indicate that the time is not valid + } + } + return pctime; +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + diff --git a/Firmware_V3/lib/Time/keywords.txt b/Firmware_V3/lib/Time/keywords.txt new file mode 100644 index 0000000..073f8f8 --- /dev/null +++ b/Firmware_V3/lib/Time/keywords.txt @@ -0,0 +1,34 @@ +####################################### +# Syntax Coloring Map For Time +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### +time_t KEYWORD1 +####################################### +# Methods and Functions (KEYWORD2) +####################################### +now KEYWORD2 +second KEYWORD2 +minute KEYWORD2 +hour KEYWORD2 +day KEYWORD2 +month KEYWORD2 +year KEYWORD2 +isAM KEYWORD2 +isPM KEYWORD2 +weekday KEYWORD2 +setTime KEYWORD2 +adjustTime KEYWORD2 +setSyncProvider KEYWORD2 +setSyncInterval KEYWORD2 +timeStatus KEYWORD2 +TimeLib KEYWORD2 +####################################### +# Instances (KEYWORD2) +####################################### + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/Firmware_V3/lib/Time/library.json b/Firmware_V3/lib/Time/library.json new file mode 100644 index 0000000..1ea5293 --- /dev/null +++ b/Firmware_V3/lib/Time/library.json @@ -0,0 +1,26 @@ +{ + "name": "Time", + "description": "Time keeping library", + "keywords": "Time, date, hour, minute, second, day, week, month, year, RTC", + "authors": [ + { + "name": "Michael Margolis" + }, + { + "name": "Paul Stoffregen", + "email": "paul@pjrc.com", + "url": "http://www.pjrc.com", + "maintainer": true + } + ], + "repository": { + "type": "git", + "url": "https://github.com/PaulStoffregen/Time" + }, + "version": "1.6", + "homepage": "http://playground.arduino.cc/Code/Time", + "frameworks": "Arduino", + "examples": [ + "examples/*/*.ino" + ] +} diff --git a/Firmware_V3/lib/Time/library.properties b/Firmware_V3/lib/Time/library.properties new file mode 100644 index 0000000..2c5d58c --- /dev/null +++ b/Firmware_V3/lib/Time/library.properties @@ -0,0 +1,11 @@ +name=Time +version=1.6 +author=Michael Margolis +maintainer=Paul Stoffregen +sentence=Timekeeping functionality for Arduino +paragraph=Date and Time functions, with provisions to synchronize to external time sources like GPS and NTP (Internet). This library is often used together with TimeAlarms and DS1307RTC. +category=Timing +url=http://playground.arduino.cc/Code/Time/ +includes=TimeLib.h +architectures=* + diff --git a/Firmware_V3/lib/Wire/Wire.cpp b/Firmware_V3/lib/Wire/Wire.cpp new file mode 100644 index 0000000..d62393e --- /dev/null +++ b/Firmware_V3/lib/Wire/Wire.cpp @@ -0,0 +1,341 @@ +/* + TwoWire.cpp - TWI/I2C library for Wiring & Arduino + Copyright (c) 2006 Nicholas Zambetti. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Modified 2012 by Todd Krein (todd@krein.org) to implement repeated starts +*/ + +#include + +#if defined(__AVR__) + +#include "Wire.h" + +extern "C" { + #include + #include + #include + #include "twi.h" +} + + +// Initialize Class Variables ////////////////////////////////////////////////// + +uint8_t TwoWire::rxBuffer[BUFFER_LENGTH]; +uint8_t TwoWire::rxBufferIndex = 0; +uint8_t TwoWire::rxBufferLength = 0; + +uint8_t TwoWire::txAddress = 0; +uint8_t TwoWire::txBuffer[BUFFER_LENGTH]; +uint8_t TwoWire::txBufferIndex = 0; +uint8_t TwoWire::txBufferLength = 0; + +uint8_t TwoWire::transmitting = 0; +void (*TwoWire::user_onRequest)(void); +void (*TwoWire::user_onReceive)(int); + +// Constructors //////////////////////////////////////////////////////////////// + +TwoWire::TwoWire() +{ +} + +// Public Methods ////////////////////////////////////////////////////////////// + +void TwoWire::begin(void) +{ + rxBufferIndex = 0; + rxBufferLength = 0; + + txBufferIndex = 0; + txBufferLength = 0; + + twi_init(); +} + +void TwoWire::begin(uint8_t address) +{ + twi_setAddress(address); + twi_attachSlaveTxEvent(onRequestService); + twi_attachSlaveRxEvent(onReceiveService); + begin(); +} + +void TwoWire::begin(int address) +{ + begin((uint8_t)address); +} + +void TwoWire::end() +{ + TWCR &= ~(_BV(TWEN) | _BV(TWIE) | _BV(TWEA)); + digitalWrite(SDA, 0); + digitalWrite(SCL, 0); +} + +void TwoWire::setClock(uint32_t frequency) +{ + TWBR = ((F_CPU / frequency) - 16) / 2; +} + +void TwoWire::setSDA(uint8_t pin) +{ +} + +void TwoWire::setSCL(uint8_t pin) +{ +} + +uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity, uint8_t sendStop) +{ + // clamp to buffer length + if(quantity > BUFFER_LENGTH){ + quantity = BUFFER_LENGTH; + } + // perform blocking read into buffer + uint8_t read = twi_readFrom(address, rxBuffer, quantity, sendStop); + // set rx buffer iterator vars + rxBufferIndex = 0; + rxBufferLength = read; + + return read; +} + +uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity) +{ + return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)true); +} + +uint8_t TwoWire::requestFrom(int address, int quantity) +{ + return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)true); +} + +uint8_t TwoWire::requestFrom(int address, int quantity, int sendStop) +{ + return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)sendStop); +} + +uint8_t TwoWire::requestFrom(uint8_t addr, uint8_t qty, uint32_t iaddr, uint8_t n, uint8_t stop) +{ + if (n > 0) { + union { uint32_t ul; uint8_t b[4]; } iaddress; + iaddress.ul = iaddr; + beginTransmission(addr); + if (n > 3) n = 3; + do { + n = n - 1; + write(iaddress.b[n]); + } while (n > 0); + endTransmission(false); + } + if (qty > BUFFER_LENGTH) qty = BUFFER_LENGTH; + return requestFrom(addr, qty, stop); +} + +void TwoWire::beginTransmission(uint8_t address) +{ + // indicate that we are transmitting + transmitting = 1; + // set address of targeted slave + txAddress = address; + // reset tx buffer iterator vars + txBufferIndex = 0; + txBufferLength = 0; +} + +void TwoWire::beginTransmission(int address) +{ + beginTransmission((uint8_t)address); +} + +// +// Originally, 'endTransmission' was an f(void) function. +// It has been modified to take one parameter indicating +// whether or not a STOP should be performed on the bus. +// Calling endTransmission(false) allows a sketch to +// perform a repeated start. +// +// WARNING: Nothing in the library keeps track of whether +// the bus tenure has been properly ended with a STOP. It +// is very possible to leave the bus in a hung state if +// no call to endTransmission(true) is made. Some I2C +// devices will behave oddly if they do not see a STOP. +// +uint8_t TwoWire::endTransmission(uint8_t sendStop) +{ + // transmit buffer (blocking) + int8_t ret = twi_writeTo(txAddress, txBuffer, txBufferLength, 1, sendStop); + // reset tx buffer iterator vars + txBufferIndex = 0; + txBufferLength = 0; + // indicate that we are done transmitting + transmitting = 0; + return ret; +} + +// This provides backwards compatibility with the original +// definition, and expected behaviour, of endTransmission +// +uint8_t TwoWire::endTransmission(void) +{ + return endTransmission(true); +} + +// must be called in: +// slave tx event callback +// or after beginTransmission(address) +size_t TwoWire::write(uint8_t data) +{ + if(transmitting){ + // in master transmitter mode + // don't bother if buffer is full + if(txBufferLength >= BUFFER_LENGTH){ + setWriteError(); + return 0; + } + // put byte in tx buffer + txBuffer[txBufferIndex] = data; + ++txBufferIndex; + // update amount in buffer + txBufferLength = txBufferIndex; + }else{ + // in slave send mode + // reply to master + twi_transmit(&data, 1); + } + return 1; +} + +// must be called in: +// slave tx event callback +// or after beginTransmission(address) +size_t TwoWire::write(const uint8_t *data, size_t quantity) +{ + if(transmitting){ + // in master transmitter mode + for(size_t i = 0; i < quantity; ++i){ + write(data[i]); + } + }else{ + // in slave send mode + // reply to master + twi_transmit(data, quantity); + } + return quantity; +} + +// must be called in: +// slave rx event callback +// or after requestFrom(address, numBytes) +int TwoWire::available(void) +{ + return rxBufferLength - rxBufferIndex; +} + +// must be called in: +// slave rx event callback +// or after requestFrom(address, numBytes) +int TwoWire::read(void) +{ + int value = -1; + + // get each successive byte on each call + if(rxBufferIndex < rxBufferLength){ + value = rxBuffer[rxBufferIndex]; + ++rxBufferIndex; + } + + return value; +} + +// must be called in: +// slave rx event callback +// or after requestFrom(address, numBytes) +int TwoWire::peek(void) +{ + int value = -1; + + if(rxBufferIndex < rxBufferLength){ + value = rxBuffer[rxBufferIndex]; + } + + return value; +} + +void TwoWire::flush(void) +{ + // XXX: to be implemented. +} + +// behind the scenes function that is called when data is received +void TwoWire::onReceiveService(uint8_t* inBytes, int numBytes) +{ + // don't bother if user hasn't registered a callback + if(!user_onReceive){ + return; + } + // don't bother if rx buffer is in use by a master requestFrom() op + // i know this drops data, but it allows for slight stupidity + // meaning, they may not have read all the master requestFrom() data yet + if(rxBufferIndex < rxBufferLength){ + return; + } + // copy twi rx buffer into local read buffer + // this enables new reads to happen in parallel + for(uint8_t i = 0; i < numBytes; ++i){ + rxBuffer[i] = inBytes[i]; + } + // set rx iterator vars + rxBufferIndex = 0; + rxBufferLength = numBytes; + // alert user program + user_onReceive(numBytes); +} + +// behind the scenes function that is called when data is requested +void TwoWire::onRequestService(void) +{ + // don't bother if user hasn't registered a callback + if(!user_onRequest){ + return; + } + // reset tx buffer iterator vars + // !!! this will kill any pending pre-master sendTo() activity + txBufferIndex = 0; + txBufferLength = 0; + // alert user program + user_onRequest(); +} + +// sets function called on slave write +void TwoWire::onReceive( void (*function)(int) ) +{ + user_onReceive = function; +} + +// sets function called on slave read +void TwoWire::onRequest( void (*function)(void) ) +{ + user_onRequest = function; +} + +// Preinstantiate Objects ////////////////////////////////////////////////////// + +TwoWire Wire = TwoWire(); + +#endif // __AVR__ diff --git a/Firmware_V3/lib/Wire/Wire.h b/Firmware_V3/lib/Wire/Wire.h new file mode 100644 index 0000000..cffb667 --- /dev/null +++ b/Firmware_V3/lib/Wire/Wire.h @@ -0,0 +1,107 @@ +/* + TwoWire.h - TWI/I2C library for Arduino & Wiring + Copyright (c) 2006 Nicholas Zambetti. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Modified 2012 by Todd Krein (todd@krein.org) to implement repeated starts +*/ + +#ifndef TwoWire_h +#define TwoWire_h + +#if defined(__IMXRT1052__) || defined(__IMXRT1062__) +#include "WireIMXRT.h" + +#elif defined(__arm__) && defined(TEENSYDUINO) +#include "WireKinetis.h" + +#elif defined(__AVR__) + +#include +#include + +#define BUFFER_LENGTH 32 +#define WIRE_HAS_END 1 + +class TwoWire : public Stream +{ + private: + static uint8_t rxBuffer[]; + static uint8_t rxBufferIndex; + static uint8_t rxBufferLength; + + static uint8_t txAddress; + static uint8_t txBuffer[]; + static uint8_t txBufferIndex; + static uint8_t txBufferLength; + + static uint8_t transmitting; + static void onRequestService(void); + static void onReceiveService(uint8_t*, int); + static void (*user_onRequest)(void); + static void (*user_onReceive)(int); + static void sda_rising_isr(void); + public: + TwoWire(); + void begin(); + void begin(uint8_t); + void begin(int); + void end(); + void setClock(uint32_t); + void setSDA(uint8_t); + void setSCL(uint8_t); + void beginTransmission(uint8_t); + void beginTransmission(int); + uint8_t endTransmission(void); + uint8_t endTransmission(uint8_t); + uint8_t requestFrom(uint8_t, uint8_t); + uint8_t requestFrom(uint8_t, uint8_t, uint8_t); + uint8_t requestFrom(int, int); + uint8_t requestFrom(int, int, int); + uint8_t requestFrom(uint8_t, uint8_t, uint32_t, uint8_t, uint8_t); + virtual size_t write(uint8_t); + virtual size_t write(const uint8_t *, size_t); + virtual int available(void); + virtual int read(void); + virtual int peek(void); + virtual void flush(void); + void onReceive( void (*)(int) ); + void onRequest( void (*)(void) ); + +#ifdef CORE_TEENSY + // added by Teensyduino installer, for compatibility + // with pre-1.0 sketches and libraries + void send(uint8_t b) { write(b); } + void send(uint8_t *s, uint8_t n) { write(s, n); } + void send(int n) { write((uint8_t)n); } + void send(char *s) { write(s); } + uint8_t receive(void) { + int c = read(); + if (c < 0) return 0; + return c; + } +#endif + inline size_t write(unsigned long n) { return write((uint8_t)n); } + inline size_t write(long n) { return write((uint8_t)n); } + inline size_t write(unsigned int n) { return write((uint8_t)n); } + inline size_t write(int n) { return write((uint8_t)n); } + using Print::write; +}; + +extern TwoWire Wire; + +#endif +#endif diff --git a/Firmware_V3/lib/Wire/WireIMXRT.cpp b/Firmware_V3/lib/Wire/WireIMXRT.cpp new file mode 100644 index 0000000..96c5721 --- /dev/null +++ b/Firmware_V3/lib/Wire/WireIMXRT.cpp @@ -0,0 +1,380 @@ + +#include "Wire.h" + + +#if defined(__IMXRT1062__) + +//#include "debug/printf.h" + +#define PINCONFIG (IOMUXC_PAD_ODE | IOMUXC_PAD_SRE | IOMUXC_PAD_DSE(4) | IOMUXC_PAD_SPEED(1) | IOMUXC_PAD_PKE | IOMUXC_PAD_PUE | IOMUXC_PAD_PUS(3)) + +void TwoWire::begin(void) +{ + // use 24 MHz clock + CCM_CSCDR2 = (CCM_CSCDR2 & ~CCM_CSCDR2_LPI2C_CLK_PODF(63)) | CCM_CSCDR2_LPI2C_CLK_SEL; + hardware.clock_gate_register |= hardware.clock_gate_mask; + port->MCR = LPI2C_MCR_RST; + setClock(100000); + + // Setup SDA register + *(portControlRegister(hardware.sda_pins[sda_pin_index_].pin)) = PINCONFIG; + *(portConfigRegister(hardware.sda_pins[sda_pin_index_].pin)) = hardware.sda_pins[sda_pin_index_].mux_val; + if (hardware.sda_pins[sda_pin_index_].select_input_register) { + *(hardware.sda_pins[sda_pin_index_].select_input_register) = hardware.sda_pins[sda_pin_index_].select_val; + } + + // setup SCL register + *(portControlRegister(hardware.scl_pins[scl_pin_index_].pin)) = PINCONFIG; + *(portConfigRegister(hardware.scl_pins[scl_pin_index_].pin)) = hardware.scl_pins[scl_pin_index_].mux_val; + if (hardware.scl_pins[scl_pin_index_].select_input_register) { + *(hardware.scl_pins[scl_pin_index_].select_input_register) = hardware.scl_pins[scl_pin_index_].select_val; + } +} + +void TwoWire::begin(uint8_t address) +{ + // TODO: slave mode +} + +void TwoWire::end() +{ +} + +void TwoWire::setSDA(uint8_t pin) { + if (pin == hardware.sda_pins[sda_pin_index_].pin) return; + uint32_t newindex=0; + while (1) { + uint32_t sda_pin = hardware.sda_pins[newindex].pin; + if (sda_pin == 255) return; + if (sda_pin == pin) break; + if (++newindex >= sizeof(hardware.sda_pins)) return; + } + if ((hardware.clock_gate_register & hardware.clock_gate_mask)) { + *(portConfigRegister(hardware.sda_pins[sda_pin_index_].pin)) = 5; // hard to know what to go back to? + + // setup new one... + *(portControlRegister(hardware.sda_pins[newindex].pin)) |= IOMUXC_PAD_PKE | IOMUXC_PAD_PUE | IOMUXC_PAD_PUS(3); + *(portConfigRegister(hardware.sda_pins[newindex].pin)) = hardware.sda_pins[newindex].mux_val; + if (hardware.sda_pins[newindex].select_input_register) { + *(hardware.sda_pins[newindex].select_input_register) = hardware.sda_pins[newindex].select_val; + } + } + sda_pin_index_ = newindex; +} + +void TwoWire::setSCL(uint8_t pin) { + if (pin == hardware.scl_pins[scl_pin_index_].pin) return; + uint32_t newindex=0; + while (1) { + uint32_t scl_pin = hardware.scl_pins[newindex].pin; + if (scl_pin == 255) return; + if (scl_pin == pin) break; + if (++newindex >= sizeof(hardware.scl_pins)) return; + } + if ((hardware.clock_gate_register & hardware.clock_gate_mask)) { + *(portConfigRegister(hardware.scl_pins[scl_pin_index_].pin)) = 5; // hard to know what to go back to? + + // setup new one... + *(portControlRegister(hardware.scl_pins[newindex].pin)) |= IOMUXC_PAD_PKE | IOMUXC_PAD_PUE | IOMUXC_PAD_PUS(3); + *(portConfigRegister(hardware.scl_pins[newindex].pin)) = hardware.scl_pins[newindex].mux_val; + if (hardware.scl_pins[newindex].select_input_register) { + *(hardware.scl_pins[newindex].select_input_register) = hardware.scl_pins[newindex].select_val; + } + } + scl_pin_index_ = newindex; +} + +bool TwoWire::force_clock() +{ + bool ret = false; + uint32_t sda_pin = hardware.sda_pins[sda_pin_index_].pin; + uint32_t scl_pin = hardware.scl_pins[scl_pin_index_].pin; + uint32_t sda_mask = digitalPinToBitMask(sda_pin); + uint32_t scl_mask = digitalPinToBitMask(scl_pin); + // take control of pins with GPIO + *portConfigRegister(sda_pin) = 5 | 0x10; + *portSetRegister(sda_pin) = sda_mask; + *portModeRegister(sda_pin) |= sda_mask; + *portConfigRegister(scl_pin) = 5 | 0x10; + *portSetRegister(scl_pin) = scl_mask; + *portModeRegister(scl_pin) |= scl_mask; + delayMicroseconds(10); + for (int i=0; i < 9; i++) { + if ((*portInputRegister(sda_pin) & sda_mask) + && (*portInputRegister(scl_pin) & scl_mask)) { + // success, both pins are high + ret = true; + break; + } + *portClearRegister(scl_pin) = scl_mask; + delayMicroseconds(5); + *portSetRegister(scl_pin) = scl_mask; + delayMicroseconds(5); + } + // return control of pins to I2C + *(portConfigRegister(sda_pin)) = hardware.sda_pins[sda_pin_index_].mux_val; + *(portConfigRegister(scl_pin)) = hardware.scl_pins[scl_pin_index_].mux_val; + return ret; +} + +size_t TwoWire::write(uint8_t data) +{ + if (transmitting || slave_mode) { + if (txBufferLength >= BUFFER_LENGTH+1) { + setWriteError(); + return 0; + } + txBuffer[txBufferLength++] = data; + return 1; + } + return 0; +} + +size_t TwoWire::write(const uint8_t *data, size_t quantity) +{ + if (transmitting || slave_mode) { + size_t avail = BUFFER_LENGTH+1 - txBufferLength; + if (quantity > avail) { + quantity = avail; + setWriteError(); + } + memcpy(txBuffer + txBufferLength, data, quantity); + txBufferLength += quantity; + return quantity; + } + return 0; +} + + + + +// 2 BBF = Bus Busy Flag +// 1 MBF = Master Busy Flag +// 40 DMF = Data Match Flag +// 20 PLTF = Pin Low Timeout Flag +// 10 FEF = FIFO Error Flag +// 08 ALF = Arbitration Lost Flag +// 04 NDF = NACK Detect Flag +// 02 SDF = STOP Detect Flag +// 01 EPF = End Packet Flag +// 2 RDF = Receive Data Flag +// 1 TDF = Transmit Data Flag + +bool TwoWire::wait_idle() +{ + elapsedMillis timeout = 0; + while (1) { + uint32_t status = port->MSR; // pg 2899 & 2892 + if (!(status & LPI2C_MSR_BBF)) break; // bus is available + if (status & LPI2C_MSR_MBF) break; // we already have bus control + if (timeout > 16) { + //Serial.printf("timeout waiting for idle, MSR = %x\n", status); + if (force_clock()) break; + //Serial.printf("unable to get control of I2C bus\n"); + return false; + } + } + port->MSR = 0x00007F00; // clear all prior flags + return true; +} + + +uint8_t TwoWire::endTransmission(uint8_t sendStop) +{ + uint32_t tx_len = txBufferLength; + if (!tx_len) return 4; // no address for transmit + if (!wait_idle()) return 4; + uint32_t tx_index = 0; // 0=start, 1=addr, 2-(N-1)=data, N=stop + elapsedMillis timeout = 0; + while (1) { + // transmit stuff, if we haven't already + if (tx_index <= tx_len) { + uint32_t fifo_used = port->MFSR & 0x07; // pg 2914 + while (fifo_used < 4) { + if (tx_index == 0) { + port->MTDR = LPI2C_MTDR_CMD_START | txBuffer[0]; + tx_index = 1; + } else if (tx_index < tx_len) { + port->MTDR = LPI2C_MTDR_CMD_TRANSMIT | txBuffer[tx_index++]; + } else { + if (sendStop) port->MTDR = LPI2C_MTDR_CMD_STOP; + tx_index++; + break; + } + fifo_used++; + } + } + // monitor status + uint32_t status = port->MSR; // pg 2884 & 2891 + if (status & LPI2C_MSR_ALF) { + port->MCR |= LPI2C_MCR_RTF | LPI2C_MCR_RRF; // clear FIFOs + return 4; // we lost bus arbitration to another master + } + if (status & LPI2C_MSR_NDF) { + port->MCR |= LPI2C_MCR_RTF | LPI2C_MCR_RRF; // clear FIFOs + port->MTDR = LPI2C_MTDR_CMD_STOP; + return 2; // NACK (assume address, TODO: how to tell address from data) + } + if ((status & LPI2C_MSR_PLTF) || timeout > 50) { + port->MCR |= LPI2C_MCR_RTF | LPI2C_MCR_RRF; // clear FIFOs + port->MTDR = LPI2C_MTDR_CMD_STOP; // try to send a stop + return 4; // clock stretched too long or generic timeout + } + // are we done yet? + if (tx_index > tx_len) { + uint32_t tx_fifo = port->MFSR & 0x07; + if (tx_fifo == 0 && ((status & LPI2C_MSR_SDF) || !sendStop)) { + return 0; + } + } + yield(); + } +} + +uint8_t TwoWire::requestFrom(uint8_t address, uint8_t length, uint8_t sendStop) +{ + if (!wait_idle()) return 4; + address = (address & 0x7F) << 1; + if (length < 1) length = 1; + if (length > 255) length = 255; + rxBufferIndex = 0; + rxBufferLength = 0; + uint32_t tx_state = 0; // 0=begin, 1=start, 2=data, 3=stop + elapsedMillis timeout = 0; + while (1) { + // transmit stuff, if we haven't already + if (tx_state < 3) { + uint32_t tx_fifo = port->MFSR & 0x07; // pg 2914 + while (tx_fifo < 4 && tx_state < 3) { + if (tx_state == 0) { + port->MTDR = LPI2C_MTDR_CMD_START | 1 | address; + } else if (tx_state == 1) { + port->MTDR = LPI2C_MTDR_CMD_RECEIVE | (length - 1); + } else { + if (sendStop) port->MTDR = LPI2C_MTDR_CMD_STOP; + } + tx_state++; + tx_fifo--; + } + } + // receive stuff + if (rxBufferLength < sizeof(rxBuffer)) { + uint32_t rx_fifo = (port->MFSR >> 16) & 0x07; + while (rx_fifo > 0 && rxBufferLength < sizeof(rxBuffer)) { + rxBuffer[rxBufferLength++] = port->MRDR; + rx_fifo--; + } + } + // monitor status, check for error conditions + uint32_t status = port->MSR; // pg 2884 & 2891 + if (status & LPI2C_MSR_ALF) { + port->MCR |= LPI2C_MCR_RTF | LPI2C_MCR_RRF; // clear FIFOs + break; + } + if ((status & LPI2C_MSR_NDF) || (status & LPI2C_MSR_PLTF) || timeout > 50) { + port->MCR |= LPI2C_MCR_RTF | LPI2C_MCR_RRF; // clear FIFOs + port->MTDR = LPI2C_MTDR_CMD_STOP; // try to send a stop + break; + } + // are we done yet? + if (rxBufferLength >= length && tx_state >= 3) { + uint32_t tx_fifo = port->MFSR & 0x07; + if (tx_fifo == 0 && ((status & LPI2C_MSR_SDF) || !sendStop)) { + break; + } + } + yield(); + } + uint32_t rx_fifo = (port->MFSR >> 16) & 0x07; + if (rx_fifo > 0) port->MCR |= LPI2C_MCR_RRF; + return rxBufferLength; +} + +uint8_t TwoWire::requestFrom(uint8_t addr, uint8_t qty, uint32_t iaddr, uint8_t n, uint8_t stop) +{ + if (n > 0) { + union { uint32_t ul; uint8_t b[4]; } iaddress; + iaddress.ul = iaddr; + beginTransmission(addr); + if (n > 3) n = 3; + do { + n = n - 1; + write(iaddress.b[n]); + } while (n > 0); + endTransmission(false); + } + if (qty > BUFFER_LENGTH) qty = BUFFER_LENGTH; + return requestFrom(addr, qty, stop); +} + + + +PROGMEM +constexpr TwoWire::I2C_Hardware_t TwoWire::i2c1_hardware = { + CCM_CCGR2, CCM_CCGR2_LPI2C1(CCM_CCGR_ON), + {{18, 3 | 0x10, &IOMUXC_LPI2C1_SDA_SELECT_INPUT, 1}, {0xff, 0xff, nullptr, 0}}, + {{19, 3 | 0x10, &IOMUXC_LPI2C1_SCL_SELECT_INPUT, 1}, {0xff, 0xff, nullptr, 0}}, + IRQ_LPI2C1 +}; +TwoWire Wire(&IMXRT_LPI2C1, TwoWire::i2c1_hardware); + +PROGMEM +constexpr TwoWire::I2C_Hardware_t TwoWire::i2c3_hardware = { + CCM_CCGR2, CCM_CCGR2_LPI2C3(CCM_CCGR_ON), + {{17, 1 | 0x10, &IOMUXC_LPI2C3_SDA_SELECT_INPUT, 2}, {36, 2 | 0x10, &IOMUXC_LPI2C3_SDA_SELECT_INPUT, 1}}, + {{16, 1 | 0x10, &IOMUXC_LPI2C3_SCL_SELECT_INPUT, 2}, {37, 2 | 0x10, &IOMUXC_LPI2C3_SCL_SELECT_INPUT, 1}}, + IRQ_LPI2C3 +}; +TwoWire Wire1(&IMXRT_LPI2C3, TwoWire::i2c3_hardware); + +PROGMEM +constexpr TwoWire::I2C_Hardware_t TwoWire::i2c4_hardware = { + CCM_CCGR6, CCM_CCGR6_LPI2C4_SERIAL(CCM_CCGR_ON), + {{25, 0 | 0x10, &IOMUXC_LPI2C4_SDA_SELECT_INPUT, 1}, {0xff, 0xff, nullptr, 0}}, + {{24, 0 | 0x10, &IOMUXC_LPI2C4_SCL_SELECT_INPUT, 1}, {0xff, 0xff, nullptr, 0}}, + IRQ_LPI2C4 +}; +TwoWire Wire2(&IMXRT_LPI2C4, TwoWire::i2c4_hardware); + + + + +// Timeout if a device stretches SCL this long, in microseconds +#define CLOCK_STRETCH_TIMEOUT 15000 + + +void TwoWire::setClock(uint32_t frequency) +{ + port->MCR = 0; + if (frequency < 400000) { + // 100 kHz + port->MCCR0 = LPI2C_MCCR0_CLKHI(55) | LPI2C_MCCR0_CLKLO(59) | + LPI2C_MCCR0_DATAVD(25) | LPI2C_MCCR0_SETHOLD(40); + port->MCFGR1 = LPI2C_MCFGR1_PRESCALE(1); + port->MCFGR2 = LPI2C_MCFGR2_FILTSDA(5) | LPI2C_MCFGR2_FILTSCL(5) | + LPI2C_MCFGR2_BUSIDLE(3000); // idle timeout 250 us + port->MCFGR3 = LPI2C_MCFGR3_PINLOW(CLOCK_STRETCH_TIMEOUT * 12 / 256 + 1); + } else if (frequency < 1000000) { + // 400 kHz + port->MCCR0 = LPI2C_MCCR0_CLKHI(26) | LPI2C_MCCR0_CLKLO(28) | + LPI2C_MCCR0_DATAVD(12) | LPI2C_MCCR0_SETHOLD(18); + port->MCFGR1 = LPI2C_MCFGR1_PRESCALE(0); + port->MCFGR2 = LPI2C_MCFGR2_FILTSDA(2) | LPI2C_MCFGR2_FILTSCL(2) | + LPI2C_MCFGR2_BUSIDLE(3600); // idle timeout 150 us + port->MCFGR3 = LPI2C_MCFGR3_PINLOW(CLOCK_STRETCH_TIMEOUT * 24 / 256 + 1); + } else { + // 1 MHz + port->MCCR0 = LPI2C_MCCR0_CLKHI(9) | LPI2C_MCCR0_CLKLO(10) | + LPI2C_MCCR0_DATAVD(4) | LPI2C_MCCR0_SETHOLD(7); + port->MCFGR1 = LPI2C_MCFGR1_PRESCALE(0); + port->MCFGR2 = LPI2C_MCFGR2_FILTSDA(1) | LPI2C_MCFGR2_FILTSCL(1) | + LPI2C_MCFGR2_BUSIDLE(2400); // idle timeout 100 us + port->MCFGR3 = LPI2C_MCFGR3_PINLOW(CLOCK_STRETCH_TIMEOUT * 24 / 256 + 1); + } + port->MCCR1 = port->MCCR0; + port->MCFGR0 = 0; + port->MFCR = LPI2C_MFCR_RXWATER(1) | LPI2C_MFCR_TXWATER(1); + port->MCR = LPI2C_MCR_MEN; +} + +#endif diff --git a/Firmware_V3/lib/Wire/WireIMXRT.h b/Firmware_V3/lib/Wire/WireIMXRT.h new file mode 100644 index 0000000..595a084 --- /dev/null +++ b/Firmware_V3/lib/Wire/WireIMXRT.h @@ -0,0 +1,329 @@ +/* Wire Library for Teensy LC & 3.X + * Copyright (c) 2014-2017, Paul Stoffregen, paul@pjrc.com + * + * Development of this I2C library was funded by PJRC.COM, LLC by sales of + * Teensy and related products. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice, development funding notice, and this permission + * notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef TwoWireIMXRT_h +#define TwoWireIMXRT_h + +#if defined(__IMXRT1052__) || defined(__IMXRT1062__) + +#include +#include + +#define BUFFER_LENGTH 32 +//#define WIRE_HAS_END 1 +#define WIRE_IMPLEMENT_WIRE +#define WIRE_IMPLEMENT_WIRE1 +#define WIRE_IMPLEMENT_WIRE2 + +class TwoWire : public Stream +{ +public: + // Hardware description struct + static const uint8_t cnt_sda_pins = 2; + static const uint8_t cnt_scl_pins = 2; + typedef struct { + const uint8_t pin; // The pin number + const uint32_t mux_val; // Value to set for mux; + volatile uint32_t *select_input_register; // Which register controls the selection + const uint32_t select_val; // Value for that selection + } pin_info_t; + + typedef struct { + volatile uint32_t &clock_gate_register; + uint32_t clock_gate_mask; + pin_info_t sda_pins[cnt_sda_pins]; + pin_info_t scl_pins[cnt_scl_pins]; + IRQ_NUMBER_t irq; + } I2C_Hardware_t; + static const I2C_Hardware_t i2c1_hardware; + static const I2C_Hardware_t i2c2_hardware; + static const I2C_Hardware_t i2c3_hardware; + static const I2C_Hardware_t i2c4_hardware; +public: + constexpr TwoWire(IMXRT_LPI2C_t *myport, const I2C_Hardware_t &myhardware) + : port(myport), hardware(myhardware) { + } + void begin(); + void begin(uint8_t address); + void begin(int address) { + begin((uint8_t)address); + } + void end(); + void setClock(uint32_t frequency); + void setSDA(uint8_t pin); + void setSCL(uint8_t pin); + void beginTransmission(uint8_t address) { + txBuffer[0] = (address << 1); + transmitting = 1; + txBufferLength = 1; + } + void beginTransmission(int address) { + beginTransmission((uint8_t)address); + } + uint8_t endTransmission(uint8_t sendStop); + uint8_t endTransmission(void) { + return endTransmission(1); + } + uint8_t requestFrom(uint8_t address, uint8_t quantity, uint8_t sendStop); + uint8_t requestFrom(uint8_t address, uint8_t quantity) { + return requestFrom(address, quantity, (uint8_t)1); + } + uint8_t requestFrom(int address, int quantity, int sendStop) { + return requestFrom((uint8_t)address, (uint8_t)quantity, + (uint8_t)(sendStop ? 1 : 0)); + } + uint8_t requestFrom(int address, int quantity) { + return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)1); + } + uint8_t requestFrom(uint8_t addr, uint8_t qty, uint32_t iaddr, uint8_t n, uint8_t stop); + virtual size_t write(uint8_t data); + virtual size_t write(const uint8_t *data, size_t quantity); + virtual int available(void) { + return rxBufferLength - rxBufferIndex; + } + virtual int read(void) { + if (rxBufferIndex >= rxBufferLength) return -1; + return rxBuffer[rxBufferIndex++]; + } + virtual int peek(void) { + if (rxBufferIndex >= rxBufferLength) return -1; + return rxBuffer[rxBufferIndex]; + } + virtual void flush(void) { + } + void onReceive(void (*function)(int numBytes)) { + user_onReceive = function; + } + void onRequest(void (*function)(void)) { + user_onRequest = function; + } + // send() for compatibility with very old sketches and libraries + void send(uint8_t b) { + write(b); + } + void send(uint8_t *s, uint8_t n) { + write(s, n); + } + void send(int n) { + write((uint8_t)n); + } + void send(char *s) { + write(s); + } + uint8_t receive(void) { + int c = read(); + if (c < 0) return 0; + return c; + } + size_t write(unsigned long n) { + return write((uint8_t)n); + } + size_t write(long n) { + return write((uint8_t)n); + } + size_t write(unsigned int n) { + return write((uint8_t)n); + } + size_t write(int n) { + return write((uint8_t)n); + } + using Print::write; +private: + //void isr(void); + //bool wait_idle(void); + bool wait_idle(); + bool force_clock(); + IMXRT_LPI2C_t * const port; + const I2C_Hardware_t &hardware; + uint8_t sda_pin_index_ = 0x0; // default is always first item + uint8_t scl_pin_index_ = 0x0; + uint8_t rxBuffer[BUFFER_LENGTH] = {}; + uint8_t rxBufferIndex = 0; + uint8_t rxBufferLength = 0; + uint8_t txAddress = 0; + uint8_t txBuffer[BUFFER_LENGTH+1] = {}; + uint8_t txBufferIndex = 0; + uint8_t txBufferLength = 0; + uint8_t transmitting = 0; + uint8_t slave_mode = 0; + uint8_t irqcount = 0; + uint8_t sda_pin_index = 0; + uint8_t scl_pin_index = 0; + void onRequestService(void); + void onReceiveService(uint8_t*, int); + void (*user_onRequest)(void) = nullptr; + void (*user_onReceive)(int) = nullptr; + void sda_rising_isr(void); + friend void i2c0_isr(void); + friend void i2c1_isr(void); + friend void i2c2_isr(void); + friend void i2c3_isr(void); + friend void sda_rising_isr0(void); + friend void sda_rising_isr1(void); +}; + +extern TwoWire Wire; +extern TwoWire Wire1; +extern TwoWire Wire2; +extern TwoWire Wire3; + + +class TWBRemulation +{ +public: + inline TWBRemulation & operator = (int val) __attribute__((always_inline)) { + /*if (val == 12 || val == ((F_CPU / 400000) - 16) / 2) { // 22, 52, 112 + I2C0_C1 = 0; + #if F_BUS == 128000000 + I2C0_F = I2C_F_DIV320; // 400 kHz + #elif F_BUS == 120000000 + I2C0_F = I2C_F_DIV288; // 416 kHz + #elif F_BUS == 108000000 + I2C0_F = I2C_F_DIV256; // 422 kHz + #elif F_BUS == 96000000 + I2C0_F = I2C_F_DIV240; // 400 kHz + #elif F_BUS == 90000000 + I2C0_F = I2C_F_DIV224; // 402 kHz + #elif F_BUS == 80000000 + I2C0_F = I2C_F_DIV192; // 416 kHz + #elif F_BUS == 72000000 + I2C0_F = I2C_F_DIV192; // 375 kHz + #elif F_BUS == 64000000 + I2C0_F = I2C_F_DIV160; // 400 kHz + #elif F_BUS == 60000000 + I2C0_F = I2C_F_DIV144; // 416 kHz + #elif F_BUS == 56000000 + I2C0_F = I2C_F_DIV144; // 389 kHz + #elif F_BUS == 54000000 + I2C0_F = I2C_F_DIV128; // 422 kHz + #elif F_BUS == 48000000 + I2C0_F = I2C_F_DIV112; // 400 kHz + #elif F_BUS == 40000000 + I2C0_F = I2C_F_DIV96; // 416 kHz + #elif F_BUS == 36000000 + I2C0_F = I2C_F_DIV96; // 375 kHz + #elif F_BUS == 24000000 + I2C0_F = I2C_F_DIV64; // 375 kHz + #elif F_BUS == 16000000 + I2C0_F = I2C_F_DIV40; // 400 kHz + #elif F_BUS == 8000000 + I2C0_F = I2C_F_DIV20; // 400 kHz + #elif F_BUS == 4000000 + I2C0_F = I2C_F_DIV20; // 200 kHz + #elif F_BUS == 2000000 + I2C0_F = I2C_F_DIV20; // 100 kHz + #endif + I2C0_C1 = I2C_C1_IICEN; + } else if (val == 72 || val == ((F_CPU / 100000) - 16) / 2) { // 112, 232, 472 + I2C0_C1 = 0; + #if F_BUS == 128000000 + I2C0_F = I2C_F_DIV1280; // 100 kHz + #elif F_BUS == 120000000 + I2C0_F = I2C_F_DIV1152; // 104 kHz + #elif F_BUS == 108000000 + I2C0_F = I2C_F_DIV1024; // 105 kHz + #elif F_BUS == 96000000 + I2C0_F = I2C_F_DIV960; // 100 kHz + #elif F_BUS == 90000000 + I2C0_F = I2C_F_DIV896; // 100 kHz + #elif F_BUS == 80000000 + I2C0_F = I2C_F_DIV768; // 104 kHz + #elif F_BUS == 72000000 + I2C0_F = I2C_F_DIV640; // 112 kHz + #elif F_BUS == 64000000 + I2C0_F = I2C_F_DIV640; // 100 kHz + #elif F_BUS == 60000000 + I2C0_F = I2C_F_DIV576; // 104 kHz + #elif F_BUS == 56000000 + I2C0_F = I2C_F_DIV512; // 109 kHz + #elif F_BUS == 54000000 + I2C0_F = I2C_F_DIV512; // 105 kHz + #elif F_BUS == 48000000 + I2C0_F = I2C_F_DIV480; // 100 kHz + #elif F_BUS == 40000000 + I2C0_F = I2C_F_DIV384; // 104 kHz + #elif F_BUS == 36000000 + I2C0_F = I2C_F_DIV320; // 113 kHz + #elif F_BUS == 24000000 + I2C0_F = I2C_F_DIV240; // 100 kHz + #elif F_BUS == 16000000 + I2C0_F = I2C_F_DIV160; // 100 kHz + #elif F_BUS == 8000000 + I2C0_F = I2C_F_DIV80; // 100 kHz + #elif F_BUS == 4000000 + I2C0_F = I2C_F_DIV40; // 100 kHz + #elif F_BUS == 2000000 + I2C0_F = I2C_F_DIV20; // 100 kHz + #endif + I2C0_C1 = I2C_C1_IICEN; + } */ + return *this; + } + inline operator int () const __attribute__((always_inline)) { + /* #if F_BUS == 128000000 + if (I2C0_F == I2C_F_DIV320) return 12; + #elif F_BUS == 120000000 + if (I2C0_F == I2C_F_DIV288) return 12; + #elif F_BUS == 108000000 + if (I2C0_F == I2C_F_DIV256) return 12; + #elif F_BUS == 96000000 + if (I2C0_F == I2C_F_DIV240) return 12; + #elif F_BUS == 90000000 + if (I2C0_F == I2C_F_DIV224) return 12; + #elif F_BUS == 80000000 + if (I2C0_F == I2C_F_DIV192) return 12; + #elif F_BUS == 72000000 + if (I2C0_F == I2C_F_DIV192) return 12; + #elif F_BUS == 64000000 + if (I2C0_F == I2C_F_DIV160) return 12; + #elif F_BUS == 60000000 + if (I2C0_F == I2C_F_DIV144) return 12; + #elif F_BUS == 56000000 + if (I2C0_F == I2C_F_DIV144) return 12; + #elif F_BUS == 54000000 + if (I2C0_F == I2C_F_DIV128) return 12; + #elif F_BUS == 48000000 + if (I2C0_F == I2C_F_DIV112) return 12; + #elif F_BUS == 40000000 + if (I2C0_F == I2C_F_DIV96) return 12; + #elif F_BUS == 36000000 + if (I2C0_F == I2C_F_DIV96) return 12; + #elif F_BUS == 24000000 + if (I2C0_F == I2C_F_DIV64) return 12; + #elif F_BUS == 16000000 + if (I2C0_F == I2C_F_DIV40) return 12; + #elif F_BUS == 8000000 + if (I2C0_F == I2C_F_DIV20) return 12; + #elif F_BUS == 4000000 + if (I2C0_F == I2C_F_DIV20) return 12; + #endif */ + return 72; + } +}; +extern TWBRemulation TWBR; + +#endif +#endif diff --git a/Firmware_V3/lib/Wire/WireKinetis.cpp b/Firmware_V3/lib/Wire/WireKinetis.cpp new file mode 100644 index 0000000..dcc12c4 --- /dev/null +++ b/Firmware_V3/lib/Wire/WireKinetis.cpp @@ -0,0 +1,931 @@ +/* Wire Library for Teensy LC & 3.X + * Copyright (c) 2014-2017, Paul Stoffregen, paul@pjrc.com + * + * Development of this I2C library was funded by PJRC.COM, LLC by sales of + * Teensy and related products. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice, development funding notice, and this permission + * notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include "Wire.h" + +#if defined(__arm__) && defined(TEENSYDUINO) && (defined(__MKL26Z64__) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__)) + +#include "kinetis.h" +#include // for memcpy +#include "core_pins.h" +#include "Wire.h" + +// undefine these, so we can't accidentally access the hardware directly. +#undef I2C0_A1 +#undef I2C0_F +#undef I2C0_C1 +#undef I2C0_S +#undef I2C0_D +#undef I2C0_C2 +#undef I2C0_FLT +#undef I2C0_RA +#undef I2C0_SMB +#undef I2C0_A2 +#undef I2C0_SLTH +#undef I2C0_SLTL + +void sda_rising_isr0(void); +void sda_rising_isr1(void); + +void TwoWire::begin(void) +{ + //serial_begin(BAUD2DIV(115200)); + //serial_print("\nWire Begin\n"); + + rxBufferIndex = 0; + rxBufferLength = 0; + txBufferIndex = 0; + txBufferLength = 0; + transmitting = 0; + user_onRequest = NULL; + user_onReceive = NULL; + slave_mode = 0; + hardware.clock_gate_register |= hardware.clock_gate_mask; + port().C1 = 0; + // On Teensy 3.0 external pullup resistors *MUST* be used + // the PORT_PCR_PE bit is ignored when in I2C mode + // I2C will not work at all without pullup resistors + // It might seem like setting PORT_PCR_PE & PORT_PCR_PS + // would enable pullup resistors. However, there seems + // to be a bug in chip while I2C is enabled, where setting + // those causes the port to be driven strongly high. + uint32_t mux; + volatile uint32_t *reg; + reg = portConfigRegister(hardware.sda_pin[sda_pin_index]); + mux = PORT_PCR_MUX(hardware.sda_mux[sda_pin_index]); + *reg = mux|PORT_PCR_ODE|PORT_PCR_SRE|PORT_PCR_DSE; + reg = portConfigRegister(hardware.scl_pin[scl_pin_index]); + mux = PORT_PCR_MUX(hardware.scl_mux[scl_pin_index]); + *reg = mux|PORT_PCR_ODE|PORT_PCR_SRE|PORT_PCR_DSE; + setClock(100000); + port().C2 = I2C_C2_HDRS; + port().C1 = I2C_C1_IICEN; + //pinMode(3, OUTPUT); + //pinMode(4, OUTPUT); +} + +void TwoWire::setClock(uint32_t frequency) +{ + if (!(hardware.clock_gate_register & hardware.clock_gate_mask)) return; + +#if F_BUS == 128000000 + if (frequency < 400000) { + port().F = I2C_F_DIV1280; // 100 kHz + } else if (frequency < 1000000) { + port().F = I2C_F_DIV320; // 400 kHz + } else { + port().F = I2C_F_DIV128; // 1 MHz + } + port().FLT = 4; +#elif F_BUS == 120000000 + if (frequency < 400000) { + port().F = I2C_F_DIV1152; // 104 kHz + } else if (frequency < 1000000) { + port().F = I2C_F_DIV288; // 416 kHz + } else { + port().F = I2C_F_DIV128; // 0.94 MHz + } + port().FLT = 4; +#elif F_BUS == 108000000 + if (frequency < 400000) { + port().F = I2C_F_DIV1024; // 105 kHz + } else if (frequency < 1000000) { + port().F = I2C_F_DIV256; // 422 kHz + } else { + port().F = I2C_F_DIV112; // 0.96 MHz + } + port().FLT = 4; +#elif F_BUS == 96000000 + if (frequency < 400000) { + port().F = I2C_F_DIV960; // 100 kHz + } else if (frequency < 1000000) { + port().F = I2C_F_DIV240; // 400 kHz + } else { + port().F = I2C_F_DIV96; // 1.0 MHz + } + port().FLT = 4; +#elif F_BUS == 90000000 + if (frequency < 400000) { + port().F = I2C_F_DIV896; // 100 kHz + } else if (frequency < 1000000) { + port().F = I2C_F_DIV224; // 402 kHz + } else { + port().F = I2C_F_DIV88; // 1.02 MHz + } + port().FLT = 4; +#elif F_BUS == 80000000 + if (frequency < 400000) { + port().F = I2C_F_DIV768; // 104 kHz + } else if (frequency < 1000000) { + port().F = I2C_F_DIV192; // 416 kHz + } else { + port().F = I2C_F_DIV80; // 1.0 MHz + } + port().FLT = 4; +#elif F_BUS == 72000000 + if (frequency < 400000) { + port().F = I2C_F_DIV640; // 112 kHz + } else if (frequency < 1000000) { + port().F = I2C_F_DIV192; // 375 kHz + } else { + port().F = I2C_F_DIV72; // 1.0 MHz + } + port().FLT = 4; +#elif F_BUS == 64000000 + if (frequency < 400000) { + port().F = I2C_F_DIV640; // 100 kHz + } else if (frequency < 1000000) { + port().F = I2C_F_DIV160; // 400 kHz + } else { + port().F = I2C_F_DIV64; // 1.0 MHz + } + port().FLT = 4; +#elif F_BUS == 60000000 + if (frequency < 400000) { + port().F = 0x2C; // 104 kHz + } else if (frequency < 1000000) { + port().F = 0x1C; // 416 kHz + } else { + port().F = 0x12; // 938 kHz + } + port().FLT = 4; +#elif F_BUS == 56000000 + if (frequency < 400000) { + port().F = 0x2B; // 109 kHz + } else if (frequency < 1000000) { + port().F = 0x1C; // 389 kHz + } else { + port().F = 0x0E; // 1 MHz + } + port().FLT = 4; +#elif F_BUS == 54000000 + if (frequency < 400000) { + port().F = I2C_F_DIV512; // 105 kHz + } else if (frequency < 1000000) { + port().F = I2C_F_DIV128; // 422 kHz + } else { + port().F = I2C_F_DIV56; // 0.96 MHz + } + port().FLT = 4; +#elif F_BUS == 48000000 + if (frequency < 400000) { + port().F = 0x27; // 100 kHz + } else if (frequency < 1000000) { + port().F = 0x1A; // 400 kHz + } else { + port().F = 0x0D; // 1 MHz + } + port().FLT = 4; +#elif F_BUS == 40000000 + if (frequency < 400000) { + port().F = 0x29; // 104 kHz + } else if (frequency < 1000000) { + port().F = 0x19; // 416 kHz + } else { + port().F = 0x0B; // 1 MHz + } + port().FLT = 3; +#elif F_BUS == 36000000 + if (frequency < 400000) { + port().F = 0x28; // 113 kHz + } else if (frequency < 1000000) { + port().F = 0x19; // 375 kHz + } else { + port().F = 0x0A; // 1 MHz + } + port().FLT = 3; +#elif F_BUS == 24000000 + if (frequency < 400000) { + port().F = 0x1F; // 100 kHz + } else if (frequency < 1000000) { + port().F = 0x12; // 375 kHz + } else { + port().F = 0x02; // 1 MHz + } + port().FLT = 2; +#elif F_BUS == 16000000 + if (frequency < 400000) { + port().F = 0x20; // 100 kHz + } else if (frequency < 1000000) { + port().F = 0x07; // 400 kHz + } else { + port().F = 0x00; // 800 kHz + } + port().FLT = 1; +#elif F_BUS == 8000000 + if (frequency < 400000) { + port().F = 0x14; // 100 kHz + } else { + port().F = 0x00; // 400 kHz + } + port().FLT = 1; +#elif F_BUS == 4000000 + if (frequency < 400000) { + port().F = 0x07; // 100 kHz + } else { + port().F = 0x00; // 200 kHz + } + port().FLT = 1; +#elif F_BUS == 2000000 + port().F = 0x00; // 100 kHz + port().FLT = 1; +#else +#error "F_BUS must be 128, 120, 108, 96, 90, 80, 72, 64, 60, 56, 54, 48, 40, 36, 24, 16, 8, 4 or 2 MHz" +#endif +} + +void TwoWire::setSDA(uint8_t pin) +{ + if (pin == hardware.sda_pin[sda_pin_index]) return; + uint32_t newindex=0; + while (1) { + uint32_t sda_pin = hardware.sda_pin[newindex]; + if (sda_pin == 255) return; + if (sda_pin == pin) break; + if (++newindex >= sizeof(hardware.sda_pin)) return; + } + if ((hardware.clock_gate_register & hardware.clock_gate_mask)) { + volatile uint32_t *reg; + reg = portConfigRegister(hardware.sda_pin[sda_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware.sda_pin[newindex]); + uint32_t mux = PORT_PCR_MUX(hardware.sda_mux[newindex]); + *reg = mux|PORT_PCR_ODE|PORT_PCR_SRE|PORT_PCR_DSE; + } + sda_pin_index = newindex; +} + +void TwoWire::setSCL(uint8_t pin) +{ + if (pin == hardware.scl_pin[scl_pin_index]) return; + uint32_t newindex=0; + while (1) { + uint32_t scl_pin = hardware.scl_pin[newindex]; + if (scl_pin == 255) return; + if (scl_pin == pin) break; + if (++newindex >= sizeof(hardware.scl_pin)) return; + } + if ((hardware.clock_gate_register & hardware.clock_gate_mask)) { + volatile uint32_t *reg; + reg = portConfigRegister(hardware.scl_pin[scl_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware.scl_pin[newindex]); + uint32_t mux = PORT_PCR_MUX(hardware.scl_mux[newindex]); + *reg = mux|PORT_PCR_ODE|PORT_PCR_SRE|PORT_PCR_DSE; + } + scl_pin_index = newindex; +} + +void TwoWire::begin(uint8_t address) +{ + begin(); + port().A1 = address << 1; + slave_mode = 1; + port().C1 = I2C_C1_IICEN | I2C_C1_IICIE; + NVIC_ENABLE_IRQ(hardware.irq); +} + +void TwoWire::end() +{ + if (!(hardware.clock_gate_register & hardware.clock_gate_mask)) return; + NVIC_DISABLE_IRQ(hardware.irq); + // TODO: should this try to create a stop condition?? + port().C1 = 0; + volatile uint32_t *reg; + reg = portConfigRegister(hardware.scl_pin[scl_pin_index]); + *reg = 0; + reg = portConfigRegister(hardware.sda_pin[sda_pin_index]); + *reg = 0; + hardware.clock_gate_register &= ~hardware.clock_gate_mask; +} + + +void TwoWire::isr(void) +{ + uint8_t status, c1, data; + static uint8_t receiving=0; + + status = port().S; + //serial_print("."); + if (status & I2C_S_ARBL) { + // Arbitration Lost + port().S = I2C_S_ARBL; + //serial_print("a"); + if (receiving && rxBufferLength > 0) { + // TODO: does this detect the STOP condition in slave receive mode? + + + } + if (!(status & I2C_S_IAAS)) return; + } + if (status & I2C_S_IAAS) { + //serial_print("\n"); + // Addressed As A Slave + if (status & I2C_S_SRW) { + //serial_print("T"); + // Begin Slave Transmit + receiving = 0; + txBufferLength = 0; + if (user_onRequest != NULL) { + user_onRequest(); + } + if (txBufferLength == 0) { + // is this correct, transmitting a single zero + // when we should send nothing? Arduino's AVR + // implementation does this, but is it ok? + txBufferLength = 1; + txBuffer[0] = 0; + } + port().C1 = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_TX; + port().D = txBuffer[0]; + txBufferIndex = 1; + } else { + // Begin Slave Receive + //serial_print("R"); + receiving = 1; + rxBufferLength = 0; + port().C1 = I2C_C1_IICEN | I2C_C1_IICIE; + data = port().D; + } + port().S = I2C_S_IICIF; + return; + } + #if defined(WIRE_HAS_STOP_INTERRUPT) + c1 = port().FLT; + if ((c1 & I2C_FLT_STOPF) && (c1 & I2C_FLT_STOPIE)) { + port().FLT = c1 & ~I2C_FLT_STOPIE; + if (user_onReceive != NULL) { + rxBufferIndex = 0; + user_onReceive(rxBufferLength); + } + } + #endif + c1 = port().C1; + if (c1 & I2C_C1_TX) { + // Continue Slave Transmit + //serial_print("t"); + if ((status & I2C_S_RXAK) == 0) { + //serial_print("."); + // Master ACK'd previous byte + if (txBufferIndex < txBufferLength) { + port().D = txBuffer[txBufferIndex++]; + } else { + port().D = 0; + } + port().C1 = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_TX; + } else { + //serial_print("*"); + // Master did not ACK previous byte + port().C1 = I2C_C1_IICEN | I2C_C1_IICIE; + data = port().D; + } + } else { + // Continue Slave Receive + irqcount = 0; + #ifdef WIRE_HAS_STOP_INTERRUPT + port().FLT |= I2C_FLT_STOPIE; + #else + #if defined(WIRE_IMPLEMENT_WIRE) && !defined(WIRE_IMPLEMENT_WIRE1) + attachInterrupt(hardware.sda_pin[sda_pin_index], sda_rising_isr0, RISING); + #elif !defined(WIRE_IMPLEMENT_WIRE) && defined(WIRE_IMPLEMENT_WIRE1) + attachInterrupt(hardware.sda_pin[sda_pin_index], sda_rising_isr1, RISING); + #elif defined(WIRE_IMPLEMENT_WIRE) && defined(WIRE_IMPLEMENT_WIRE1) + if (this == &Wire) { + attachInterrupt(hardware.sda_pin[sda_pin_index], sda_rising_isr0, RISING); + } else if (this == &Wire1) { + attachInterrupt(hardware.sda_pin[sda_pin_index], sda_rising_isr1, RISING); + } + #endif + #endif // WIRE_HAS_STOP_INTERRUPT + //digitalWriteFast(4, HIGH); + data = port().D; + //serial_phex(data); + if (rxBufferLength < BUFFER_LENGTH && receiving) { + rxBuffer[rxBufferLength++] = data; + } + //digitalWriteFast(4, LOW); + } + port().S = I2C_S_IICIF; +} + + +// Detects the stop condition that terminates a slave receive transfer. +// Sadly, the I2C in older Kinetis K series lacks the stop detect interrupt +// This pin change interrupt hack is needed to detect the stop condition +#if !defined(WIRE_HAS_STOP_INTERRUPT) + +#if defined(WIRE_IMPLEMENT_WIRE) +void sda_rising_isr0(void) +{ + Wire.sda_rising_isr(); +} +#endif +#if defined(WIRE_IMPLEMENT_WIRE1) +void sda_rising_isr1(void) +{ + Wire1.sda_rising_isr(); +} +#endif + +void TwoWire::sda_rising_isr(void) +{ + //digitalWrite(3, HIGH); + if (!(port().S & I2C_S_BUSY)) { + detachInterrupt(hardware.sda_pin[sda_pin_index]); + if (user_onReceive != NULL) { + rxBufferIndex = 0; + user_onReceive(rxBufferLength); + } + //delayMicroseconds(100); + } else { + if (++irqcount >= 2 || !slave_mode) { + detachInterrupt(hardware.sda_pin[sda_pin_index]); + } + } + //digitalWrite(3, LOW); +} +#endif // !WIRE_HAS_STOP_INTERRUPT + + +// Chapter 44: Inter-Integrated Circuit (I2C) - Page 1012 +// I2C0_A1 // I2C Address Register 1 +// I2C0_F // I2C Frequency Divider register +// I2C0_C1 // I2C Control Register 1 +// I2C0_S // I2C Status register +// I2C0_D // I2C Data I/O register +// I2C0_C2 // I2C Control Register 2 +// I2C0_FLT // I2C Programmable Input Glitch Filter register + +size_t TwoWire::write(uint8_t data) +{ + if (transmitting || slave_mode) { + if (txBufferLength >= BUFFER_LENGTH+1) { + setWriteError(); + return 0; + } + txBuffer[txBufferLength++] = data; + return 1; + } + return 0; +} + +size_t TwoWire::write(const uint8_t *data, size_t quantity) +{ + if (transmitting || slave_mode) { + size_t avail = BUFFER_LENGTH+1 - txBufferLength; + if (quantity > avail) { + quantity = avail; + setWriteError(); + } + memcpy(txBuffer + txBufferLength, data, quantity); + txBufferLength += quantity; + return quantity; + } + return 0; +} + +bool TwoWire::wait_idle(void) +{ + bool reset=false; + uint32_t wait_begin = millis(); + + //Serial.print("busy:"); + while (i2c_status() & I2C_S_BUSY) { + //Serial.write('.') ; + uint32_t waited = millis() - wait_begin; +#if 1 + if (waited > 15 && !reset) { + reset = true; + //Serial.println("attempt forced reset"); + uint8_t sda_pin = hardware.sda_pin[sda_pin_index]; + pinMode(sda_pin, INPUT_DISABLE); + uint8_t scl_pin = hardware.scl_pin[sda_pin_index]; + pinMode(scl_pin, OUTPUT); + for (int i=0; i < 9; i++) { + digitalWrite(scl_pin, LOW); + delayMicroseconds(5); + digitalWrite(scl_pin, HIGH); + delayMicroseconds(5); + } + uint32_t mux; + volatile uint32_t *reg; + reg = portConfigRegister(hardware.sda_pin[sda_pin_index]); + mux = PORT_PCR_MUX(hardware.sda_mux[sda_pin_index]); + *reg = mux|PORT_PCR_ODE|PORT_PCR_SRE|PORT_PCR_DSE; + reg = portConfigRegister(hardware.scl_pin[scl_pin_index]); + mux = PORT_PCR_MUX(hardware.scl_mux[scl_pin_index]); + *reg = mux|PORT_PCR_ODE|PORT_PCR_SRE|PORT_PCR_DSE; + delayMicroseconds(10); + continue; + } +#endif + if (waited > 16) { + // bus stuck busy too long + port().C1 = 0; + port().C1 = I2C_C1_IICEN; + //Serial.println("abort"); + //return 4; // timeout waiting for bus + return false; + } + } + return true; +} + +uint8_t TwoWire::endTransmission(uint8_t sendStop) +{ + uint8_t i, status, ret=0; + uint32_t wait_begin; + + // clear the status flags + port().S = I2C_S_IICIF | I2C_S_ARBL; + // now take control of the bus... + if (port().C1 & I2C_C1_MST) { + // we are already the bus master, so send a repeated start + //Serial.print("rstart:"); + port().C1 = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_RSTA | I2C_C1_TX; + } else { + // we are not currently the bus master, so wait for bus ready + if (!wait_idle()) { + //Serial.printf("endTransmission err1\n"); + return 4; // timeout waiting for bus + } + // become the bus master in transmit mode (send start) + slave_mode = 0; + port().C1 = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX; + } + // wait until start condition establishes control of the bus + wait_begin = millis(); + while (1) { + status = i2c_status(); + if ((status & I2C_S_BUSY)) break; + //Serial.write('*') ; + if (millis() - wait_begin > 4) { + port().C1 = 0; + port().C1 = I2C_C1_IICEN; + //Serial.println("abort2"); + //Serial.printf("endTransmission err2\n"); + return 4; // error generating start condition + } + } + // transmit the address and data + for (i=0; i < txBufferLength; i++) { + port().D = txBuffer[i]; + //Serial.write('^'); + wait_begin = millis(); + while (1) { + status = i2c_status(); + if ((status & I2C_S_IICIF)) break; + if (!(status & I2C_S_BUSY)) break; + if (millis() - wait_begin > 5) { + port().C1 = 0; + port().C1 = I2C_C1_IICEN; + //Serial.println("abort3"); + //Serial.printf("endTransmission err3\n"); + return 4; // clock stretch too long + } + } + port().S = I2C_S_IICIF; + //Serial.write('$'); + status = i2c_status(); + if ((status & I2C_S_ARBL)) { + // we lost bus arbitration to another master + // TODO: what is the proper thing to do here?? + //Serial.printf(" c1=%02X ", port().C1); + port().C1 = I2C_C1_IICEN; + //Serial.printf("endTransmission err4\n"); + ret = 4; // 4:other error + break; + } + if (!(status & I2C_S_BUSY)) { + // suddenly lost control of the bus! + port().C1 = I2C_C1_IICEN; + //Serial.printf("endTransmission err5\n"); + ret = 4; // 4:other error + break; + } + if (status & I2C_S_RXAK) { + // the slave device did not acknowledge + if (i == 0) { + //Serial.printf("endTransmission err6\n"); + ret = 2; // 2:received NACK on transmit of address + } else { + //Serial.printf("endTransmission err7\n"); + ret = 3; // 3:received NACK on transmit of data + } + sendStop = 1; + break; + } + } + if (sendStop) { + // send the stop condition + port().C1 = I2C_C1_IICEN; + // TODO: do we wait for this somehow? + } + transmitting = 0; + //Serial.print(" ret="); + //Serial.println(ret); + return ret; +} + + +uint8_t TwoWire::requestFrom(uint8_t address, uint8_t length, uint8_t sendStop) +{ + uint8_t tmp __attribute__((unused)); + uint8_t status, count=0; + uint32_t wait_begin; + + rxBufferIndex = 0; + rxBufferLength = 0; + //serial_print("requestFrom\n"); + // clear the status flags + port().S = I2C_S_IICIF | I2C_S_ARBL; + // now take control of the bus... + if (port().C1 & I2C_C1_MST) { + // we are already the bus master, so send a repeated start + port().C1 = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_RSTA | I2C_C1_TX; + } else { + // we are not currently the bus master, so wait for bus ready + if (!wait_idle()) { + //Serial.printf("requestFrom err1\n"); + return 0; // timeout waiting for bus + } + // become the bus master in transmit mode (send start) + slave_mode = 0; + port().C1 = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX; + } + + // wait until start condition establishes control of the bus + wait_begin = millis(); + while (1) { + status = i2c_status(); + if ((status & I2C_S_BUSY)) break; + if (millis() - wait_begin > 4) { + port().C1 = 0; + port().C1 = I2C_C1_IICEN; + //Serial.printf("requestFrom err2\n"); + return 0; // error generating start condition + } + } + // send the address + port().D = (address << 1) | 1; + wait_begin = millis(); + while (!(port().S & I2C_S_IICIF)) { + if (millis() - wait_begin > 5) { + port().C1 = 0; + port().C1 = I2C_C1_IICEN; + //Serial.printf("requestFrom err3\n"); + return 0; // clock stretch too long (during address) + } + } + port().S = I2C_S_IICIF; + status = i2c_status(); + if ((status & I2C_S_RXAK) || (status & I2C_S_ARBL)) { + // the slave device did not acknowledge + // or we lost bus arbitration to another master + port().C1 = I2C_C1_IICEN; + //Serial.printf("requestFrom err4\n"); + return 0; + } + if (length == 0) { + // TODO: does anybody really do zero length reads? + // if so, does this code really work? + port().C1 = I2C_C1_IICEN | (sendStop ? 0 : I2C_C1_MST); + //Serial.printf("requestFrom err5\n"); + return 0; + } else if (length == 1) { + port().C1 = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TXAK; + } else { + port().C1 = I2C_C1_IICEN | I2C_C1_MST; + } + tmp = port().D; // initiate the first receive + //delayMicroseconds(250); + while (length > 1) { + wait_begin = millis(); + while (!(port().S & I2C_S_IICIF)) { + if (millis() - wait_begin > 5) { + port().C1 = 0; + port().C1 = I2C_C1_IICEN; + rxBufferLength = count; + //Serial.printf("requestFrom err6\n"); + return count; // clock stretch too long (during data) + } + } + port().S = I2C_S_IICIF; + status = port().S; + if ((status & I2C_S_ARBL)) { + // we lost bus arbitration to another master + // or suddenly lost control of the bus! + // TODO: what is the proper thing to do here?? + //Serial.printf("requestFrom err7a\n"); + return count; + } + if (!(status & I2C_S_BUSY)) { + // we lost bus arbitration to another master + // or suddenly lost control of the bus! + // TODO: what is the proper thing to do here?? + //Serial.printf("requestFrom err7b\n"); + return count; + } + length--; + if (length == 1) port().C1 = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TXAK; + if (count < BUFFER_LENGTH) { + rxBuffer[count++] = port().D; + } else { + tmp = port().D; + } + } + wait_begin = millis(); + while (!(port().S & I2C_S_IICIF)) { + if (millis() - wait_begin > 5) { + port().C1 = 0; + port().C1 = I2C_C1_IICEN; + rxBufferLength = count; + //Serial.printf("requestFrom err8\n"); + return count; // clock stretch too long (during data) + } + } + port().S = I2C_S_IICIF; + status = port().S; + if ((status & I2C_S_ARBL)) { + // we lost bus arbitration to another master + // or suddenly lost control of the bus! + // TODO: what is the proper thing to do here?? + //digitalWriteFast(13, HIGH); + port().S = I2C_S_ARBL; + delayMicroseconds(5); + port().C1 &= ~I2C_C1_TXAK; + //Serial.printf("requestFrom err9a\n"); + return count; + } + if (!(status & I2C_S_BUSY)) { + // we lost bus arbitration to another master + // or suddenly lost control of the bus! + // TODO: what is the proper thing to do here?? + //Serial.printf("requestFrom err9b\n"); + return count; + } + port().C1 = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX; + if (count < BUFFER_LENGTH) { + rxBuffer[count++] = port().D; + } else { + tmp = port().D; + } +#if F_CPU > 120000000 + __asm__("nop"); + __asm__("nop"); + __asm__("nop"); +#endif + if (sendStop) port().C1 = I2C_C1_IICEN; + rxBufferLength = count; + return count; +} + +uint8_t TwoWire::requestFrom(uint8_t addr, uint8_t qty, uint32_t iaddr, uint8_t n, uint8_t stop) +{ + if (n > 0) { + union { uint32_t ul; uint8_t b[4]; } iaddress; + iaddress.ul = iaddr; + beginTransmission(addr); + if (n > 3) n = 3; + do { + n = n - 1; + write(iaddress.b[n]); + } while (n > 0); + endTransmission(false); + } + if (qty > BUFFER_LENGTH) qty = BUFFER_LENGTH; + return requestFrom(addr, qty, stop); +} + +// for compatibility with examples that directly call this AVR-specific function +// https://learn.adafruit.com/adafruit-tca9548a-1-to-8-i2c-multiplexer-breakout/wiring-and-test +// https://forum.pjrc.com/threads/44922-Undefined-reference-to-twi_writeTo +extern "C" +uint8_t twi_writeTo(uint8_t address, uint8_t* data, uint8_t length, uint8_t wait, uint8_t sendStop) +{ + if (!wait) return 4; + Wire.beginTransmission(address); + while (length) { + Wire.write(*data++); + length--; + } + return Wire.endTransmission(sendStop); +} + +constexpr TwoWire::I2C_Hardware_t TwoWire::i2c0_hardware = { + SIM_SCGC4, SIM_SCGC4_I2C0, +#if defined(__MKL26Z64__) || defined(__MK20DX128__) || defined(__MK20DX256__) + 18, 17, 255, 255, 255, + 2, 2, 0, 0, 0, + 19, 16, 255, 255, 255, + 2, 2, 0, 0, 0, +#elif defined(__MK64FX512__) || defined(__MK66FX1M0__) + 18, 17, 34, 8, 48, + 2, 2, 5, 7, 2, + 19, 16, 33, 7, 47, + 2, 2, 5, 7, 2, +#endif + IRQ_I2C0 +}; + +#if defined(__MKL26Z64__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) +constexpr TwoWire::I2C_Hardware_t TwoWire::i2c1_hardware = { + SIM_SCGC4, SIM_SCGC4_I2C1, +#if defined(__MKL26Z64__) + 23, 255, 255, 255, 255, + 2, 0, 0, 0, 0, + 22, 255, 255, 255, 255, + 2, 0, 0, 0, 0, +#elif defined(__MK20DX256__) + 30, 255, 255, 255, 255, + 2, 0, 0, 0, 0, + 29, 255, 255, 255, 255, + 2, 0, 0, 0, 0, +#elif defined(__MK64FX512__) || defined(__MK66FX1M0__) + 38, 58, 255, 255, 255, + 2, 6, 0, 0, 0, + 37, 59, 255, 255, 255, + 2, 6, 0, 0, 0, +#endif + IRQ_I2C1 +}; +#endif + +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) +constexpr TwoWire::I2C_Hardware_t TwoWire::i2c2_hardware = { + SIM_SCGC1, SIM_SCGC1_I2C2, +#if defined(__MK64FX512__) || defined(__MK66FX1M0__) + 4, 255, 255, 255, 255, + 5, 0, 0, 0, 0, + 3, 26, 255, 255, 255, + 5, 5, 0, 0, 0, +#endif + IRQ_I2C2 +}; +#endif + +#if defined(__MK66FX1M0__) +constexpr TwoWire::I2C_Hardware_t TwoWire::i2c3_hardware = { + SIM_SCGC1, SIM_SCGC1_I2C3, +#if defined(__MK66FX1M0__) + 56, 255, 255, 255, 255, + 2, 0, 0, 0, 0, + 57, 255, 255, 255, 255, + 2, 0, 0, 0, 0, +#endif + IRQ_I2C3 +}; +#endif + +// Helper to transform a non-constant expression of the form +// &(*(KINETIS_I2C_t *)0x40066000) +// into a compile time constant. +#define MAKE_CONST(x) (__builtin_constant_p(x) ? (x) : (x)) + +#ifdef WIRE_IMPLEMENT_WIRE +constexpr uintptr_t i2c0_addr = uintptr_t(MAKE_CONST(&KINETIS_I2C0)); +TwoWire Wire(i2c0_addr, TwoWire::i2c0_hardware); +void i2c0_isr(void) { Wire.isr(); } +#endif +#ifdef WIRE_IMPLEMENT_WIRE1 +constexpr uintptr_t i2c1_addr = uintptr_t(MAKE_CONST(&KINETIS_I2C1)); +TwoWire Wire1(i2c1_addr, TwoWire::i2c1_hardware); +void i2c1_isr(void) { Wire1.isr(); } +#endif +#ifdef WIRE_IMPLEMENT_WIRE2 +constexpr uintptr_t i2c2_addr = uintptr_t(MAKE_CONST(&KINETIS_I2C2)); +TwoWire Wire2(i2c2_addr, TwoWire::i2c2_hardware); +void i2c2_isr(void) { Wire2.isr(); } +#endif +#ifdef WIRE_IMPLEMENT_WIRE3 +constexpr uintptr_t i2c3_addr = uintptr_t(MAKE_CONST(&KINETIS_I2C3)); +TwoWire Wire3(i2c3_addr, TwoWire::i2c3_hardware); +void i2c3_isr(void) { Wire3.isr(); } +#endif + + +#endif // __arm__ && TEENSYDUINO diff --git a/Firmware_V3/lib/Wire/WireKinetis.h b/Firmware_V3/lib/Wire/WireKinetis.h new file mode 100644 index 0000000..e7781ac --- /dev/null +++ b/Firmware_V3/lib/Wire/WireKinetis.h @@ -0,0 +1,365 @@ +/* Wire Library for Teensy LC & 3.X + * Copyright (c) 2014-2017, Paul Stoffregen, paul@pjrc.com + * + * Development of this I2C library was funded by PJRC.COM, LLC by sales of + * Teensy and related products. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice, development funding notice, and this permission + * notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef TwoWireKinetis_h +#define TwoWireKinetis_h + +#if defined(__arm__) && defined(TEENSYDUINO) && (defined(__MKL26Z64__) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__)) + +#include +#include + +#define BUFFER_LENGTH 32 +#define WIRE_HAS_END 1 + + +// Teensy LC +#if defined(__MKL26Z64__) +#define WIRE_IMPLEMENT_WIRE +//Wire1 consumes precious memory on Teensy LC +//#define WIRE_IMPLEMENT_WIRE1 +#define WIRE_HAS_STOP_INTERRUPT + +// Teensy 3.0 +#elif defined(__MK20DX128__) +#define WIRE_IMPLEMENT_WIRE + +// Teensy 3.1 & 3.2 +#elif defined(__MK20DX256__) +#define WIRE_IMPLEMENT_WIRE +#define WIRE_IMPLEMENT_WIRE1 + +// Teensy 3.5 +#elif defined(__MK64FX512__) +#define WIRE_IMPLEMENT_WIRE +#define WIRE_IMPLEMENT_WIRE1 +#define WIRE_IMPLEMENT_WIRE2 +#define WIRE_HAS_START_INTERRUPT +#define WIRE_HAS_STOP_INTERRUPT + +// Teensy 3.6 +#elif defined(__MK66FX1M0__) +#define WIRE_IMPLEMENT_WIRE +#define WIRE_IMPLEMENT_WIRE1 +#define WIRE_IMPLEMENT_WIRE2 +//Wire3 is seldom used on Teensy 3.6 +//#define WIRE_IMPLEMENT_WIRE3 +#define WIRE_HAS_START_INTERRUPT +#define WIRE_HAS_STOP_INTERRUPT + +#endif + + +class TwoWire : public Stream +{ +public: + // Hardware description struct + typedef struct { + volatile uint32_t &clock_gate_register; + uint32_t clock_gate_mask; + uint8_t sda_pin[5]; + uint8_t sda_mux[5]; + uint8_t scl_pin[5]; + uint8_t scl_mux[5]; + IRQ_NUMBER_t irq; + } I2C_Hardware_t; + static const I2C_Hardware_t i2c0_hardware; + static const I2C_Hardware_t i2c1_hardware; + static const I2C_Hardware_t i2c2_hardware; + static const I2C_Hardware_t i2c3_hardware; +public: + constexpr TwoWire(uintptr_t port_addr, const I2C_Hardware_t &myhardware) + : port_addr(port_addr), hardware(myhardware) { + } + void begin(); + void begin(uint8_t address); + void begin(int address) { + begin((uint8_t)address); + } + void end(); + void setClock(uint32_t frequency); + void setSDA(uint8_t pin); + void setSCL(uint8_t pin); + void beginTransmission(uint8_t address) { + txBuffer[0] = (address << 1); + transmitting = 1; + txBufferLength = 1; + } + void beginTransmission(int address) { + beginTransmission((uint8_t)address); + } + uint8_t endTransmission(uint8_t sendStop); + uint8_t endTransmission(void) { + return endTransmission(1); + } + uint8_t requestFrom(uint8_t address, uint8_t quantity, uint8_t sendStop); + uint8_t requestFrom(uint8_t address, uint8_t quantity) { + return requestFrom(address, quantity, (uint8_t)1); + } + uint8_t requestFrom(int address, int quantity, int sendStop) { + return requestFrom((uint8_t)address, (uint8_t)quantity, + (uint8_t)(sendStop ? 1 : 0)); + } + uint8_t requestFrom(int address, int quantity) { + return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)1); + } + uint8_t requestFrom(uint8_t addr, uint8_t qty, uint32_t iaddr, uint8_t n, uint8_t stop); + virtual size_t write(uint8_t data); + virtual size_t write(const uint8_t *data, size_t quantity); + virtual int available(void) { + return rxBufferLength - rxBufferIndex; + } + virtual int read(void) { + if (rxBufferIndex >= rxBufferLength) return -1; + return rxBuffer[rxBufferIndex++]; + } + virtual int peek(void) { + if (rxBufferIndex >= rxBufferLength) return -1; + return rxBuffer[rxBufferIndex]; + } + virtual void flush(void) { + } + void onReceive(void (*function)(int numBytes)) { + user_onReceive = function; + } + void onRequest(void (*function)(void)) { + user_onRequest = function; + } + // send() for compatibility with very old sketches and libraries + void send(uint8_t b) { + write(b); + } + void send(uint8_t *s, uint8_t n) { + write(s, n); + } + void send(int n) { + write((uint8_t)n); + } + void send(char *s) { + write(s); + } + uint8_t receive(void) { + int c = read(); + if (c < 0) return 0; + return c; + } + size_t write(unsigned long n) { + return write((uint8_t)n); + } + size_t write(long n) { + return write((uint8_t)n); + } + size_t write(unsigned int n) { + return write((uint8_t)n); + } + size_t write(int n) { + return write((uint8_t)n); + } + using Print::write; +private: + KINETIS_I2C_t& port() { return (*(KINETIS_I2C_t *) port_addr); } + uint8_t i2c_status(void) { + return port().S; + } + void isr(void); + bool wait_idle(void); + uintptr_t port_addr; + const I2C_Hardware_t &hardware; + uint8_t rxBuffer[BUFFER_LENGTH] = {}; + uint8_t rxBufferIndex = 0; + uint8_t rxBufferLength = 0; + uint8_t txAddress = 0; + uint8_t txBuffer[BUFFER_LENGTH+1] = {}; + uint8_t txBufferIndex = 0; + uint8_t txBufferLength = 0; + uint8_t transmitting = 0; + uint8_t slave_mode = 0; + uint8_t irqcount = 0; + uint8_t sda_pin_index = 0; + uint8_t scl_pin_index = 0; + void onRequestService(void); + void onReceiveService(uint8_t*, int); + void (*user_onRequest)(void) = nullptr; + void (*user_onReceive)(int) = nullptr; + void sda_rising_isr(void); + friend void i2c0_isr(void); + friend void i2c1_isr(void); + friend void i2c2_isr(void); + friend void i2c3_isr(void); + friend void sda_rising_isr0(void); + friend void sda_rising_isr1(void); +}; + +#ifdef WIRE_IMPLEMENT_WIRE +extern TwoWire Wire; +#endif +#ifdef WIRE_IMPLEMENT_WIRE1 +extern TwoWire Wire1; +#endif +#ifdef WIRE_IMPLEMENT_WIRE2 +extern TwoWire Wire2; +#endif +#ifdef WIRE_IMPLEMENT_WIRE3 +extern TwoWire Wire3; +#endif + + +class TWBRemulation +{ +public: + inline TWBRemulation & operator = (int val) __attribute__((always_inline)) { + if (val == 12 || val == ((F_CPU / 400000) - 16) / 2) { // 22, 52, 112 + I2C0_C1 = 0; + #if F_BUS == 128000000 + I2C0_F = I2C_F_DIV320; // 400 kHz + #elif F_BUS == 120000000 + I2C0_F = I2C_F_DIV288; // 416 kHz + #elif F_BUS == 108000000 + I2C0_F = I2C_F_DIV256; // 422 kHz + #elif F_BUS == 96000000 + I2C0_F = I2C_F_DIV240; // 400 kHz + #elif F_BUS == 90000000 + I2C0_F = I2C_F_DIV224; // 402 kHz + #elif F_BUS == 80000000 + I2C0_F = I2C_F_DIV192; // 416 kHz + #elif F_BUS == 72000000 + I2C0_F = I2C_F_DIV192; // 375 kHz + #elif F_BUS == 64000000 + I2C0_F = I2C_F_DIV160; // 400 kHz + #elif F_BUS == 60000000 + I2C0_F = I2C_F_DIV144; // 416 kHz + #elif F_BUS == 56000000 + I2C0_F = I2C_F_DIV144; // 389 kHz + #elif F_BUS == 54000000 + I2C0_F = I2C_F_DIV128; // 422 kHz + #elif F_BUS == 48000000 + I2C0_F = I2C_F_DIV112; // 400 kHz + #elif F_BUS == 40000000 + I2C0_F = I2C_F_DIV96; // 416 kHz + #elif F_BUS == 36000000 + I2C0_F = I2C_F_DIV96; // 375 kHz + #elif F_BUS == 24000000 + I2C0_F = I2C_F_DIV64; // 375 kHz + #elif F_BUS == 16000000 + I2C0_F = I2C_F_DIV40; // 400 kHz + #elif F_BUS == 8000000 + I2C0_F = I2C_F_DIV20; // 400 kHz + #elif F_BUS == 4000000 + I2C0_F = I2C_F_DIV20; // 200 kHz + #elif F_BUS == 2000000 + I2C0_F = I2C_F_DIV20; // 100 kHz + #endif + I2C0_C1 = I2C_C1_IICEN; + } else if (val == 72 || val == ((F_CPU / 100000) - 16) / 2) { // 112, 232, 472 + I2C0_C1 = 0; + #if F_BUS == 128000000 + I2C0_F = I2C_F_DIV1280; // 100 kHz + #elif F_BUS == 120000000 + I2C0_F = I2C_F_DIV1152; // 104 kHz + #elif F_BUS == 108000000 + I2C0_F = I2C_F_DIV1024; // 105 kHz + #elif F_BUS == 96000000 + I2C0_F = I2C_F_DIV960; // 100 kHz + #elif F_BUS == 90000000 + I2C0_F = I2C_F_DIV896; // 100 kHz + #elif F_BUS == 80000000 + I2C0_F = I2C_F_DIV768; // 104 kHz + #elif F_BUS == 72000000 + I2C0_F = I2C_F_DIV640; // 112 kHz + #elif F_BUS == 64000000 + I2C0_F = I2C_F_DIV640; // 100 kHz + #elif F_BUS == 60000000 + I2C0_F = I2C_F_DIV576; // 104 kHz + #elif F_BUS == 56000000 + I2C0_F = I2C_F_DIV512; // 109 kHz + #elif F_BUS == 54000000 + I2C0_F = I2C_F_DIV512; // 105 kHz + #elif F_BUS == 48000000 + I2C0_F = I2C_F_DIV480; // 100 kHz + #elif F_BUS == 40000000 + I2C0_F = I2C_F_DIV384; // 104 kHz + #elif F_BUS == 36000000 + I2C0_F = I2C_F_DIV320; // 113 kHz + #elif F_BUS == 24000000 + I2C0_F = I2C_F_DIV240; // 100 kHz + #elif F_BUS == 16000000 + I2C0_F = I2C_F_DIV160; // 100 kHz + #elif F_BUS == 8000000 + I2C0_F = I2C_F_DIV80; // 100 kHz + #elif F_BUS == 4000000 + I2C0_F = I2C_F_DIV40; // 100 kHz + #elif F_BUS == 2000000 + I2C0_F = I2C_F_DIV20; // 100 kHz + #endif + I2C0_C1 = I2C_C1_IICEN; + } + return *this; + } + inline operator int () const __attribute__((always_inline)) { + #if F_BUS == 128000000 + if (I2C0_F == I2C_F_DIV320) return 12; + #elif F_BUS == 120000000 + if (I2C0_F == I2C_F_DIV288) return 12; + #elif F_BUS == 108000000 + if (I2C0_F == I2C_F_DIV256) return 12; + #elif F_BUS == 96000000 + if (I2C0_F == I2C_F_DIV240) return 12; + #elif F_BUS == 90000000 + if (I2C0_F == I2C_F_DIV224) return 12; + #elif F_BUS == 80000000 + if (I2C0_F == I2C_F_DIV192) return 12; + #elif F_BUS == 72000000 + if (I2C0_F == I2C_F_DIV192) return 12; + #elif F_BUS == 64000000 + if (I2C0_F == I2C_F_DIV160) return 12; + #elif F_BUS == 60000000 + if (I2C0_F == I2C_F_DIV144) return 12; + #elif F_BUS == 56000000 + if (I2C0_F == I2C_F_DIV144) return 12; + #elif F_BUS == 54000000 + if (I2C0_F == I2C_F_DIV128) return 12; + #elif F_BUS == 48000000 + if (I2C0_F == I2C_F_DIV112) return 12; + #elif F_BUS == 40000000 + if (I2C0_F == I2C_F_DIV96) return 12; + #elif F_BUS == 36000000 + if (I2C0_F == I2C_F_DIV96) return 12; + #elif F_BUS == 24000000 + if (I2C0_F == I2C_F_DIV64) return 12; + #elif F_BUS == 16000000 + if (I2C0_F == I2C_F_DIV40) return 12; + #elif F_BUS == 8000000 + if (I2C0_F == I2C_F_DIV20) return 12; + #elif F_BUS == 4000000 + if (I2C0_F == I2C_F_DIV20) return 12; + #endif + return 72; + } +}; +extern TWBRemulation TWBR; + +#endif +#endif diff --git a/Firmware_V3/lib/Wire/utility/twi.c b/Firmware_V3/lib/Wire/utility/twi.c new file mode 100644 index 0000000..4811873 --- /dev/null +++ b/Firmware_V3/lib/Wire/utility/twi.c @@ -0,0 +1,529 @@ +/* + twi.c - TWI/I2C library for Wiring & Arduino + Copyright (c) 2006 Nicholas Zambetti. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Modified 2012 by Todd Krein (todd@krein.org) to implement repeated starts +*/ + +#if defined(__AVR__) +#include +#include +#include +#include +#include +#include +#include "Arduino.h" // for digitalWrite + + +#ifndef cbi +#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) +#endif + +#ifndef sbi +#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) +#endif + +#include "pins_arduino.h" +#include "twi.h" + +static volatile uint8_t twi_state; +static volatile uint8_t twi_slarw; +static volatile uint8_t twi_sendStop; // should the transaction end with a stop +static volatile uint8_t twi_inRepStart; // in the middle of a repeated start + +static void (*twi_onSlaveTransmit)(void); +static void (*twi_onSlaveReceive)(uint8_t*, int); + +static uint8_t twi_masterBuffer[TWI_BUFFER_LENGTH]; +static volatile uint8_t twi_masterBufferIndex; +static volatile uint8_t twi_masterBufferLength; + +static uint8_t twi_txBuffer[TWI_BUFFER_LENGTH]; +static volatile uint8_t twi_txBufferIndex; +static volatile uint8_t twi_txBufferLength; + +static uint8_t twi_rxBuffer[TWI_BUFFER_LENGTH]; +static volatile uint8_t twi_rxBufferIndex; + +static volatile uint8_t twi_error; + +/* + * Function twi_init + * Desc readys twi pins and sets twi bitrate + * Input none + * Output none + */ +void twi_init(void) +{ + // initialize state + twi_state = TWI_READY; + twi_sendStop = true; // default value + twi_inRepStart = false; + + // activate internal pullups for twi. + digitalWrite(SDA, 1); + digitalWrite(SCL, 1); + + // initialize twi prescaler and bit rate + cbi(TWSR, TWPS0); + cbi(TWSR, TWPS1); + TWBR = ((F_CPU / TWI_FREQ) - 16) / 2; + + /* twi bit rate formula from atmega128 manual pg 204 + SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR)) + note: TWBR should be 10 or higher for master mode + It is 72 for a 16mhz Wiring board with 100kHz TWI */ + + // enable twi module, acks, and twi interrupt + TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA); +} + +/* + * Function twi_slaveInit + * Desc sets slave address and enables interrupt + * Input none + * Output none + */ +void twi_setAddress(uint8_t address) +{ + // set twi slave address (skip over TWGCE bit) + TWAR = address << 1; +} + +/* + * Function twi_readFrom + * Desc attempts to become twi bus master and read a + * series of bytes from a device on the bus + * Input address: 7bit i2c device address + * data: pointer to byte array + * length: number of bytes to read into array + * sendStop: Boolean indicating whether to send a stop at the end + * Output number of bytes read + */ +uint8_t twi_readFrom(uint8_t address, uint8_t* data, uint8_t length, uint8_t sendStop) +{ + uint8_t i; + + // ensure data will fit into buffer + if(TWI_BUFFER_LENGTH < length){ + return 0; + } + + // wait until twi is ready, become master receiver + while(TWI_READY != twi_state){ + continue; + } + twi_state = TWI_MRX; + twi_sendStop = sendStop; + // reset error state (0xFF.. no error occured) + twi_error = 0xFF; + + // initialize buffer iteration vars + twi_masterBufferIndex = 0; + twi_masterBufferLength = length-1; // This is not intuitive, read on... + // On receive, the previously configured ACK/NACK setting is transmitted in + // response to the received byte before the interrupt is signalled. + // Therefor we must actually set NACK when the _next_ to last byte is + // received, causing that NACK to be sent in response to receiving the last + // expected byte of data. + + // build sla+w, slave device address + w bit + twi_slarw = TW_READ; + twi_slarw |= address << 1; + + if (true == twi_inRepStart) { + // if we're in the repeated start state, then we've already sent the start, + // (@@@ we hope), and the TWI statemachine is just waiting for the address byte. + // We need to remove ourselves from the repeated start state before we enable interrupts, + // since the ISR is ASYNC, and we could get confused if we hit the ISR before cleaning + // up. Also, don't enable the START interrupt. There may be one pending from the + // repeated start that we sent outselves, and that would really confuse things. + twi_inRepStart = false; // remember, we're dealing with an ASYNC ISR + TWDR = twi_slarw; + TWCR = _BV(TWINT) | _BV(TWEA) | _BV(TWEN) | _BV(TWIE); // enable INTs, but not START + } + else + // send start condition + TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA); + + // wait for read operation to complete + while(TWI_MRX == twi_state){ + continue; + } + + if (twi_masterBufferIndex < length) + length = twi_masterBufferIndex; + + // copy twi buffer to data + for(i = 0; i < length; ++i){ + data[i] = twi_masterBuffer[i]; + } + + return length; +} + +/* + * Function twi_writeTo + * Desc attempts to become twi bus master and write a + * series of bytes to a device on the bus + * Input address: 7bit i2c device address + * data: pointer to byte array + * length: number of bytes in array + * wait: boolean indicating to wait for write or not + * sendStop: boolean indicating whether or not to send a stop at the end + * Output 0 .. success + * 1 .. length to long for buffer + * 2 .. address send, NACK received + * 3 .. data send, NACK received + * 4 .. other twi error (lost bus arbitration, bus error, ..) + */ +uint8_t twi_writeTo(uint8_t address, uint8_t* data, uint8_t length, uint8_t wait, uint8_t sendStop) +{ + uint8_t i; + + // ensure data will fit into buffer + if(TWI_BUFFER_LENGTH < length){ + return 1; + } + + // wait until twi is ready, become master transmitter + while(TWI_READY != twi_state){ + continue; + } + twi_state = TWI_MTX; + twi_sendStop = sendStop; + // reset error state (0xFF.. no error occured) + twi_error = 0xFF; + + // initialize buffer iteration vars + twi_masterBufferIndex = 0; + twi_masterBufferLength = length; + + // copy data to twi buffer + for(i = 0; i < length; ++i){ + twi_masterBuffer[i] = data[i]; + } + + // build sla+w, slave device address + w bit + twi_slarw = TW_WRITE; + twi_slarw |= address << 1; + + // if we're in a repeated start, then we've already sent the START + // in the ISR. Don't do it again. + // + if (true == twi_inRepStart) { + // if we're in the repeated start state, then we've already sent the start, + // (@@@ we hope), and the TWI statemachine is just waiting for the address byte. + // We need to remove ourselves from the repeated start state before we enable interrupts, + // since the ISR is ASYNC, and we could get confused if we hit the ISR before cleaning + // up. Also, don't enable the START interrupt. There may be one pending from the + // repeated start that we sent outselves, and that would really confuse things. + twi_inRepStart = false; // remember, we're dealing with an ASYNC ISR + TWDR = twi_slarw; + TWCR = _BV(TWINT) | _BV(TWEA) | _BV(TWEN) | _BV(TWIE); // enable INTs, but not START + } + else + // send start condition + TWCR = _BV(TWINT) | _BV(TWEA) | _BV(TWEN) | _BV(TWIE) | _BV(TWSTA); // enable INTs + + // wait for write operation to complete + while(wait && (TWI_MTX == twi_state)){ + continue; + } + + if (twi_error == 0xFF) + return 0; // success + else if (twi_error == TW_MT_SLA_NACK) + return 2; // error: address send, nack received + else if (twi_error == TW_MT_DATA_NACK) + return 3; // error: data send, nack received + else + return 4; // other twi error +} + +/* + * Function twi_transmit + * Desc fills slave tx buffer with data + * must be called in slave tx event callback + * Input data: pointer to byte array + * length: number of bytes in array + * Output 1 length too long for buffer + * 2 not slave transmitter + * 0 ok + */ +uint8_t twi_transmit(const uint8_t* data, uint8_t length) +{ + uint8_t i; + + // ensure data will fit into buffer + if(TWI_BUFFER_LENGTH < length){ + return 1; + } + + // ensure we are currently a slave transmitter + if(TWI_STX != twi_state){ + return 2; + } + + // set length and copy data into tx buffer + twi_txBufferLength = length; + for(i = 0; i < length; ++i){ + twi_txBuffer[i] = data[i]; + } + + return 0; +} + +/* + * Function twi_attachSlaveRxEvent + * Desc sets function called before a slave read operation + * Input function: callback function to use + * Output none + */ +void twi_attachSlaveRxEvent( void (*function)(uint8_t*, int) ) +{ + twi_onSlaveReceive = function; +} + +/* + * Function twi_attachSlaveTxEvent + * Desc sets function called before a slave write operation + * Input function: callback function to use + * Output none + */ +void twi_attachSlaveTxEvent( void (*function)(void) ) +{ + twi_onSlaveTransmit = function; +} + +/* + * Function twi_reply + * Desc sends byte or readys receive line + * Input ack: byte indicating to ack or to nack + * Output none + */ +void twi_reply(uint8_t ack) +{ + // transmit master read ready signal, with or without ack + if(ack){ + TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT) | _BV(TWEA); + }else{ + TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT); + } +} + +/* + * Function twi_stop + * Desc relinquishes bus master status + * Input none + * Output none + */ +void twi_stop(void) +{ + // send stop condition + TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTO); + + // wait for stop condition to be exectued on bus + // TWINT is not set after a stop condition! + while(TWCR & _BV(TWSTO)){ + continue; + } + + // update twi state + twi_state = TWI_READY; +} + +/* + * Function twi_releaseBus + * Desc releases bus control + * Input none + * Output none + */ +void twi_releaseBus(void) +{ + // release bus + TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT); + + // update twi state + twi_state = TWI_READY; +} + +SIGNAL(TWI_vect) +{ + switch(TW_STATUS){ + // All Master + case TW_START: // sent start condition + case TW_REP_START: // sent repeated start condition + // copy device address and r/w bit to output register and ack + TWDR = twi_slarw; + twi_reply(1); + break; + + // Master Transmitter + case TW_MT_SLA_ACK: // slave receiver acked address + case TW_MT_DATA_ACK: // slave receiver acked data + // if there is data to send, send it, otherwise stop + if(twi_masterBufferIndex < twi_masterBufferLength){ + // copy data to output register and ack + TWDR = twi_masterBuffer[twi_masterBufferIndex++]; + twi_reply(1); + }else{ + if (twi_sendStop) + twi_stop(); + else { + twi_inRepStart = true; // we're gonna send the START + // don't enable the interrupt. We'll generate the start, but we + // avoid handling the interrupt until we're in the next transaction, + // at the point where we would normally issue the start. + TWCR = _BV(TWINT) | _BV(TWSTA)| _BV(TWEN) ; + twi_state = TWI_READY; + } + } + break; + case TW_MT_SLA_NACK: // address sent, nack received + twi_error = TW_MT_SLA_NACK; + twi_stop(); + break; + case TW_MT_DATA_NACK: // data sent, nack received + twi_error = TW_MT_DATA_NACK; + twi_stop(); + break; + case TW_MT_ARB_LOST: // lost bus arbitration + twi_error = TW_MT_ARB_LOST; + twi_releaseBus(); + break; + + // Master Receiver + case TW_MR_DATA_ACK: // data received, ack sent + // put byte into buffer + twi_masterBuffer[twi_masterBufferIndex++] = TWDR; + case TW_MR_SLA_ACK: // address sent, ack received + // ack if more bytes are expected, otherwise nack + if(twi_masterBufferIndex < twi_masterBufferLength){ + twi_reply(1); + }else{ + twi_reply(0); + } + break; + case TW_MR_DATA_NACK: // data received, nack sent + // put final byte into buffer + twi_masterBuffer[twi_masterBufferIndex++] = TWDR; + if (twi_sendStop) + twi_stop(); + else { + twi_inRepStart = true; // we're gonna send the START + // don't enable the interrupt. We'll generate the start, but we + // avoid handling the interrupt until we're in the next transaction, + // at the point where we would normally issue the start. + TWCR = _BV(TWINT) | _BV(TWSTA)| _BV(TWEN) ; + twi_state = TWI_READY; + } + break; + case TW_MR_SLA_NACK: // address sent, nack received + twi_stop(); + break; + // TW_MR_ARB_LOST handled by TW_MT_ARB_LOST case + + // Slave Receiver + case TW_SR_SLA_ACK: // addressed, returned ack + case TW_SR_GCALL_ACK: // addressed generally, returned ack + case TW_SR_ARB_LOST_SLA_ACK: // lost arbitration, returned ack + case TW_SR_ARB_LOST_GCALL_ACK: // lost arbitration, returned ack + // enter slave receiver mode + twi_state = TWI_SRX; + // indicate that rx buffer can be overwritten and ack + twi_rxBufferIndex = 0; + twi_reply(1); + break; + case TW_SR_DATA_ACK: // data received, returned ack + case TW_SR_GCALL_DATA_ACK: // data received generally, returned ack + // if there is still room in the rx buffer + if(twi_rxBufferIndex < TWI_BUFFER_LENGTH){ + // put byte in buffer and ack + twi_rxBuffer[twi_rxBufferIndex++] = TWDR; + twi_reply(1); + }else{ + // otherwise nack + twi_reply(0); + } + break; + case TW_SR_STOP: // stop or repeated start condition received + // put a null char after data if there's room + if(twi_rxBufferIndex < TWI_BUFFER_LENGTH){ + twi_rxBuffer[twi_rxBufferIndex] = '\0'; + } + // sends ack and stops interface for clock stretching + //twi_stop(); // Arduino issue #66 + // callback to user defined callback + twi_onSlaveReceive(twi_rxBuffer, twi_rxBufferIndex); + // since we submit rx buffer to "wire" library, we can reset it + twi_rxBufferIndex = 0; + // ack future responses and leave slave receiver state + twi_releaseBus(); + break; + case TW_SR_DATA_NACK: // data received, returned nack + case TW_SR_GCALL_DATA_NACK: // data received generally, returned nack + // nack back at master + twi_reply(0); + break; + + // Slave Transmitter + case TW_ST_SLA_ACK: // addressed, returned ack + case TW_ST_ARB_LOST_SLA_ACK: // arbitration lost, returned ack + // enter slave transmitter mode + twi_state = TWI_STX; + // ready the tx buffer index for iteration + twi_txBufferIndex = 0; + // set tx buffer length to be zero, to verify if user changes it + twi_txBufferLength = 0; + // request for txBuffer to be filled and length to be set + // note: user must call twi_transmit(bytes, length) to do this + twi_onSlaveTransmit(); + // if they didn't change buffer & length, initialize it + if(0 == twi_txBufferLength){ + twi_txBufferLength = 1; + twi_txBuffer[0] = 0x00; + } + // transmit first byte from buffer, fall + case TW_ST_DATA_ACK: // byte sent, ack returned + // copy data to output register + TWDR = twi_txBuffer[twi_txBufferIndex++]; + // if there is more to send, ack, otherwise nack + if(twi_txBufferIndex < twi_txBufferLength){ + twi_reply(1); + }else{ + twi_reply(0); + } + break; + case TW_ST_DATA_NACK: // received nack, we are done + case TW_ST_LAST_DATA: // received ack, but we are done already! + // ack future responses + twi_reply(1); + // leave slave receiver state + twi_state = TWI_READY; + break; + + // All + case TW_NO_INFO: // no state information + break; + case TW_BUS_ERROR: // bus error, illegal stop/start + twi_error = TW_BUS_ERROR; + twi_stop(); + break; + } +} +#endif // __AVR__ diff --git a/Firmware_V3/lib/Wire/utility/twi.h b/Firmware_V3/lib/Wire/utility/twi.h new file mode 100644 index 0000000..6526593 --- /dev/null +++ b/Firmware_V3/lib/Wire/utility/twi.h @@ -0,0 +1,53 @@ +/* + twi.h - TWI/I2C library for Wiring & Arduino + Copyright (c) 2006 Nicholas Zambetti. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef twi_h +#define twi_h + + #include + + //#define ATMEGA8 + + #ifndef TWI_FREQ + #define TWI_FREQ 100000L + #endif + + #ifndef TWI_BUFFER_LENGTH + #define TWI_BUFFER_LENGTH 32 + #endif + + #define TWI_READY 0 + #define TWI_MRX 1 + #define TWI_MTX 2 + #define TWI_SRX 3 + #define TWI_STX 4 + + void twi_init(void); + void twi_setAddress(uint8_t); + uint8_t twi_readFrom(uint8_t, uint8_t*, uint8_t, uint8_t); + uint8_t twi_writeTo(uint8_t, uint8_t*, uint8_t, uint8_t, uint8_t); + uint8_t twi_transmit(const uint8_t*, uint8_t); + void twi_attachSlaveRxEvent( void (*)(uint8_t*, int) ); + void twi_attachSlaveTxEvent( void (*)(void) ); + void twi_reply(uint8_t); + void twi_stop(void); + void twi_releaseBus(void); + +#endif + diff --git a/Firmware_V3/other/platformio_teensy4.zip b/Firmware_V3/other/platformio_teensy4.zip new file mode 100644 index 0000000..716550c Binary files /dev/null and b/Firmware_V3/other/platformio_teensy4.zip differ diff --git a/Firmware_V3/platformio.ini b/Firmware_V3/platformio.ini new file mode 100644 index 0000000..ce410a5 --- /dev/null +++ b/Firmware_V3/platformio.ini @@ -0,0 +1,20 @@ +[env:default] +platform = teensy +board = teensy41 +framework = arduino +build_flags = + -D TEENSY_OPT_FASTEST + -D USB_MTPDISK_SERIAL +board_build.f_cpu = 600000000L +lib_deps = + ADC + Bounce + EEPROM + LittleFS + Metro + SD + SdFat + SPI + Time + MTP + Wire \ No newline at end of file diff --git a/Firmware_V3/src/general/colorschemes.cpp b/Firmware_V3/src/general/colorschemes.cpp new file mode 100644 index 0000000..07319c6 --- /dev/null +++ b/Firmware_V3/src/general/colorschemes.cpp @@ -0,0 +1,132 @@ +/* + * + * COLOR SCHEMES - Contains 19 different color schemes to display the thermal image + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################################# INCLUDES ##################################*/ + +#include +#include + +/*############################# PUBLIC VARIABLES ##############################*/ + +const byte colorMap_arctic[] = { 15, 16, 146, 15, 16, 146, 15, 15, 153, 15, 15, 153, 15, 15, 159, 15, 15, 159, 16, 15, 167, 16, 15, 167, 15, 15, 175, 15, 15, 175, 16, 15, 182, 16, 15, 182, 16, 16, 190, 16, 16, 190, 14, 15, 197, 14, 15, 197, 15, 15, 205, 15, 15, 205, 15, 15, 211, 15, 15, 211, 16, 15, 219, 16, 15, 219, 16, 15, 227, 16, 15, 227, 16, 18, 239, 16, 18, 239, 16, 25, 240, 16, 25, 240, 15, 34, 239, 15, 34, 239, 15, 44, 238, 15, 44, 238, 14, 54, 239, 14, 54, 239, 14, 63, 239, 14, 63, 239, 14, 74, 238, 14, 74, 238, 17, 82, 238, 17, 82, 238, 19, 92, 237, 19, 92, 237, 22, 102, 239, 22, 102, 239, 24, 111, 238, 24, 111, 238, 27, 120, 237, 27, 120, 237, 28, 131, 237, 28, 131, 237, 32, 140, 237, 32, 140, 237, 34, 150, 237, 34, 150, 237, 36, 160, 236, 36, 160, 236, 39, 168, 237, 39, 168, 237, 42, 179, 237, 42, 179, 237, 44, 188, 236, 44, 188, 236, 46, 197, 236, 46, 197, 236, 49, 208, 236, 49, 208, 236, 52, 217, 235, 52, 217, 235, 54, 227, 232, 54, 227, 232, 57, 227, 230, 57, 227, 230, 58, 226, 227, 58, 226, 227, 62, 224, 225, 62, 224, 225, 64, 222, 222, 64, 222, 222, 66, 220, 220, 66, 220, 220, 67, 215, 215, 67, 215, 215, 69, 209, 210, 69, 209, 210, 73, 205, 204, 73, 205, 204, 76, 198, 199, 76, 198, 199, 79, 193, 192, 79, 193, 192, 81, 187, 187, 81, 187, 187, 83, 181, 180, 83, 181, 180, 87, 175, 175, 87, 175, 175, 88, 170, 170, 88, 170, 170, 88, 164, 165, 88, 164, 165, 90, 158, 159, 90, 158, 159, 90, 152, 153, 90, 152, 153, 90, 146, 145, 90, 146, 145, 92, 140, 140, 92, 140, 140, 92, 134, 134, 92, 134, 134, 95, 129, 129, 95, 129, 129, 95, 123, 123, 95, 123, 123, 96, 117, 116, 96, 117, 116, 97, 111, 110, 97, 111, 110, 99, 105, 105, 99, 105, 105, 102, 102, 102, 102, 102, 102, 107, 101, 97, 107, 101, 97, 112, 101, 95, 112, 101, 95, 117, 101, 90, 117, 101, 90, 123, 102, 87, 123, 102, 87, 129, 101, 84, 129, 101, 84, 134, 101, 80, 134, 101, 80, 138, 102, 76, 138, 102, 76, 143, 101, 73, 143, 101, 73, 148, 101, 69, 148, 101, 69, 153, 101, 66, 153, 101, 66, 159, 102, 63, 159, 102, 63, 165, 102, 59, 165, 102, 59, 170, 101, 56, 170, 101, 56, 175, 101, 52, 175, 101, 52, 180, 101, 48, 180, 101, 48, 185, 100, 45, 185, 100, 45, 191, 100, 41, 191, 100, 41, 197, 101, 37, 197, 101, 37, 201, 101, 35, 201, 101, 35, 206, 101, 31, 206, 101, 31, 211, 101, 26, 211, 101, 26, 216, 101, 24, 216, 101, 24, 221, 101, 19, 221, 101, 19, 228, 101, 18, 228, 101, 18, 233, 101, 14, 233, 101, 14, 237, 101, 13, 237, 101, 13, 236, 105, 13, 236, 105, 13, 236, 112, 12, 236, 112, 12, 236, 120, 13, 236, 120, 13, 237, 123, 13, 237, 123, 13, 237, 130, 12, 237, 130, 12, 237, 137, 13, 237, 137, 13, 237, 142, 12, 237, 142, 12, 237, 149, 12, 237, 149, 12, 236, 156, 13, 236, 156, 13, 236, 160, 11, 236, 160, 11, 235, 167, 12, 235, 167, 12, 235, 173, 12, 235, 173, 12, 235, 179, 12, 235, 179, 12, 235, 185, 12, 235, 185, 12, 236, 191, 13, 236, 191, 13, 236, 196, 11, 236, 196, 11, 235, 202, 12, 235, 202, 12, 236, 204, 27, 236, 204, 27, 235, 207, 34, 235, 207, 34, 236, 208, 50, 236, 208, 50, 235, 211, 65, 235, 211, 65, 235, 212, 71, 235, 212, 71, 235, 214, 87, 235, 214, 87, 235, 216, 100, 235, 216, 100, 235, 216, 108, 235, 216, 108, 236, 220, 123, 236, 220, 123, 235, 221, 138, 235, 221, 138, 235, 221, 146, 235, 221, 146, 235, 225, 160, 235, 225, 160, 235, 225, 175, 235, 225, 175, 236, 227, 182, 236, 227, 182, 235, 229, 191, 235, 229, 191, 235, 230, 194, 235, 230, 194 }; + +const byte colorMap_blackHot[] = { 235, 235, 235, 234, 234, 234, 233, 233, 233, 232, 232, 232, 231, 231, 231, 230, 230, 230, 229, 229, 229, 228, 228, 228, 227, 227, 227, 226, 226, 226, 225, 225, 225, 224, 224, 224, 223, 223, 223, 222, 222, 222, 221, 221, 221, 220, 220, 220, 219, 219, 219, 218, 218, 218, 217, 217, 217, 216, 216, 216, 215, 215, 215, 214, 214, 214, 213, 213, 213, 212, 212, 212, 211, 211, 211, 210, 210, 210, 209, 209, 209, 209, 209, 209, 208, 208, 208, 207, 207, 207, 206, 206, 206, 205, 205, 205, 204, 204, 204, 203, 203, 203, 202, 202, 202, 201, 201, 201, 200, 200, 200, 199, 199, 199, 198, 198, 198, 197, 197, 197, 196, 196, 196, 195, 195, 195, 194, 194, 194, 193, 193, 193, 192, 192, 192, 191, 191, 191, 190, 190, 190, 189, 189, 189, 188, 188, 188, 187, 187, 187, 186, 186, 186, 185, 185, 185, 184, 184, 184, 183, 183, 183, 182, 182, 182, 181, 181, 181, 180, 180, 180, 179, 179, 179, 178, 178, 178, 177, 177, 177, 176, 176, 176, 175, 175, 175, 174, 174, 174, 173, 173, 173, 172, 172, 172, 171, 171, 171, 170, 170, 170, 169, 169, 169, 168, 168, 168, 167, 167, 167, 166, 166, 166, 165, 165, 165, 164, 164, 164, 163, 163, 163, 162, 162, 162, 161, 161, 161, 160, 160, 160, 159, 159, 159, 158, 158, 158, 157, 157, 157, 156, 156, 156, 155, 155, 155, 154, 154, 154, 154, 154, 154, 153, 153, 153, 152, 152, 152, 151, 151, 151, 150, 150, 150, 149, 149, 149, 148, 148, 148, 147, 147, 147, 146, 146, 146, 145, 145, 145, 144, 144, 144, 143, 143, 143, 142, 142, 142, 141, 141, 141, 140, 140, 140, 139, 139, 139, 138, 138, 138, 137, 137, 137, 136, 136, 136, 135, 135, 135, 134, 134, 134, 133, 133, 133, 132, 132, 132, 131, 131, 131, 130, 130, 130, 129, 129, 129, 128, 128, 128, 127, 127, 127, 126, 126, 126, 125, 125, 125, 124, 124, 124, 123, 123, 123, 122, 122, 122, 121, 121, 121, 120, 120, 120, 119, 119, 119, 118, 118, 118, 117, 117, 117, 116, 116, 116, 115, 115, 115, 114, 114, 114, 113, 113, 113, 112, 112, 112, 111, 111, 111, 110, 110, 110, 109, 109, 109, 108, 108, 108, 107, 107, 107, 106, 106, 106, 105, 105, 105, 104, 104, 104, 103, 103, 103, 102, 102, 102, 101, 101, 101, 100, 100, 100, 99, 99, 99, 99, 99, 99, 98, 98, 98, 97, 97, 97, 96, 96, 96, 95, 95, 95, 94, 94, 94, 93, 93, 93, 92, 92, 92, 91, 91, 91, 90, 90, 90, 89, 89, 89, 88, 88, 88, 87, 87, 87, 86, 86, 86, 85, 85, 85, 84, 84, 84, 83, 83, 83, 82, 82, 82, 81, 81, 81, 80, 80, 80, 79, 79, 79, 78, 78, 78, 77, 77, 77, 76, 76, 76, 75, 75, 75, 74, 74, 74, 73, 73, 73, 72, 72, 72, 71, 71, 71, 70, 70, 70, 69, 69, 69, 68, 68, 68, 67, 67, 67, 66, 66, 66, 65, 65, 65, 64, 64, 64, 63, 63, 63, 62, 62, 62, 61, 61, 61, 60, 60, 60, 59, 59, 59, 58, 58, 58, 57, 57, 57, 56, 56, 56, 55, 55, 55, 54, 54, 54, 53, 53, 53, 52, 52, 52, 51, 51, 51, 50, 50, 50, 49, 49, 49, 48, 48, 48, 47, 47, 47, 46, 46, 46, 45, 45, 45, 44, 44, 44, 44, 44, 44, 43, 43, 43, 42, 42, 42, 41, 41, 41, 40, 40, 40, 39, 39, 39, 38, 38, 38, 37, 37, 37, 36, 36, 36, 35, 35, 35, 34, 34, 34, 33, 33, 33, 32, 32, 32, 31, 31, 31, 30, 30, 30, 29, 29, 29, 28, 28, 28, 27, 27, 27, 26, 26, 26, 25, 25, 25, 24, 24, 24, 23, 23, 23, 22, 22, 22, 21, 21, 21, 20, 20, 20, 19, 19, 19, 18, 18, 18, 17, 17, 17, 16, 16, 16 }; + +const byte colorMap_blueRed[] = { 19, 64, 206, 18, 65, 209, 18, 67, 210, 19, 69, 212, 18, 71, 215, 19, 73, 217, 18, 75, 218, 18, 77, 219, 19, 79, 223, 19, 82, 225, 19, 84, 226, 18, 85, 227, 19, 88, 229, 21, 90, 229, 22, 93, 231, 21, 95, 230, 22, 98, 232, 22, 101, 232, 22, 103, 232, 23, 106, 234, 23, 109, 234, 24, 112, 236, 23, 114, 235, 25, 116, 235, 25, 119, 237, 27, 122, 238, 26, 124, 237, 27, 125, 236, 27, 127, 235, 27, 130, 236, 29, 133, 234, 30, 136, 234, 31, 139, 233, 32, 141, 232, 33, 145, 231, 33, 147, 231, 33, 150, 231, 34, 154, 229, 36, 156, 228, 36, 158, 227, 36, 162, 225, 38, 165, 224, 40, 167, 222, 41, 171, 221, 43, 174, 217, 45, 176, 216, 44, 178, 215, 46, 181, 212, 48, 184, 210, 49, 188, 209, 51, 190, 207, 53, 193, 206, 54, 195, 201, 56, 198, 200, 58, 200, 198, 59, 202, 196, 61, 205, 192, 63, 206, 190, 64, 208, 187, 68, 210, 184, 69, 212, 180, 71, 215, 178, 73, 216, 176, 74, 217, 173, 78, 220, 170, 79, 222, 165, 82, 224, 164, 83, 225, 161, 85, 227, 157, 88, 228, 154, 92, 228, 152, 94, 229, 149, 96, 230, 147, 99, 231, 144, 102, 230, 141, 104, 231, 136, 106, 232, 134, 109, 233, 131, 113, 234, 129, 114, 234, 125, 116, 235, 123, 119, 236, 120, 122, 235, 117, 126, 233, 113, 128, 234, 112, 131, 233, 109, 134, 232, 107, 137, 231, 103, 139, 230, 102, 143, 231, 99, 146, 230, 96, 149, 229, 94, 152, 228, 90, 154, 227, 88, 156, 227, 87, 157, 226, 85, 161, 224, 83, 164, 223, 81, 166, 221, 79, 169, 220, 77, 172, 217, 74, 175, 216, 74, 178, 213, 70, 181, 212, 70, 183, 210, 67, 186, 207, 64, 189, 206, 62, 192, 205, 60, 194, 201, 58, 197, 200, 57, 200, 197, 54, 202, 195, 53, 205, 191, 52, 207, 189, 51, 208, 187, 48, 209, 183, 48, 212, 180, 45, 214, 178, 44, 216, 175, 43, 218, 173, 42, 220, 169, 40, 222, 166, 39, 224, 164, 38, 225, 162, 35, 228, 157, 35, 227, 153, 34, 228, 149, 32, 230, 147, 33, 232, 144, 32, 231, 141, 31, 232, 138, 29, 232, 136, 28, 233, 132, 28, 235, 130, 27, 234, 127, 27, 235, 123, 25, 237, 120, 26, 235, 118, 24, 234, 115, 24, 235, 113, 24, 234, 110, 24, 234, 108, 22, 232, 105, 22, 231, 102, 22, 231, 100, 21, 231, 97, 20, 230, 94, 20, 228, 92, 20, 228, 89, 18, 228, 87, 19, 227, 84, 18, 224, 82, 18, 223, 81, 19, 222, 78, 19, 219, 76, 19, 217, 74, 17, 215, 72, 18, 214, 71, 17, 211, 69, 17, 210, 66, 17, 207, 64, 16, 206, 63, 17, 205, 62, 18, 203, 60, 16, 202, 58, 17, 198, 57, 17, 196, 54, 16, 194, 54, 17, 190, 53, 15, 188, 50, 15, 185, 50, 16, 183, 48, 15, 179, 46, 15, 177, 46, 16, 175, 43, 16, 171, 42, 15, 169, 41, 14, 166, 40, 14, 164, 37, 14, 160, 38, 15, 157, 37, 16, 154, 35, 15, 152, 35, 16, 149, 34, 15, 147, 34, 16, 142, 33, 16, 140, 33, 15, 139, 31, 15, 134, 30, 15, 131, 30, 14, 128, 28, 14, 126, 28, 15 }; + +const byte colorMap_coldest[] = { 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239,15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 15, 15, 239, 45, 45, 45, 46, 46, 46, 47, 47, 47, 48, 48, 48, 49, 49, 49, 50, 50, 50, 51, 51, 51, 52, 52, 52, 53, 53, 53, 54, 54, 54, 55, 55, 55, 56, 56, 56, 57, 57, 57, 58, 58, 58, 59, 59, 59, 60, 60, 60, 61, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 65, 65, 65, 66, 66, 66, 67, 67, 67, 68, 68, 68, 69, 69, 69, 70, 70, 70, 71, 71, 71, 72, 72, 72, 73, 73, 73, 74, 74, 74, 75, 75, 75, 76, 76, 76, 77, 77, 77, 78, 78, 78, 79, 79, 79, 80, 80, 80, 81, 81, 81, 82, 82, 82, 83, 83, 83, 84, 84, 84, 85, 85, 85, 86, 86, 86, 87, 87, 87, 88, 88, 88, 89, 89, 89, 90, 90, 90, 91, 91, 91, 92, 92, 92, 93, 93, 93, 94, 94, 94, 95, 95, 95, 96, 96, 96, 97, 97, 97, 98, 98, 98, 99, 99, 99, 99, 99, 99, 100, 100, 100, 101, 101, 101, 102, 102, 102, 103, 103, 103, 104, 104, 104, 105, 105, 105, 106, 106, 106, 107, 107, 107, 108, 108, 108, 109, 109, 109, 110, 110, 110, 111, 111, 111, 112, 112, 112, 113, 113, 113, 114, 114, 114, 115, 115, 115, 116, 116, 116, 117, 117, 117, 118, 118, 118, 119, 119, 119, 120, 120, 120, 121, 121, 121, 122, 122, 122, 123, 123, 123, 124, 124, 124, 125, 125, 125, 126, 126, 126, 127, 127, 127, 128, 128, 128, 129, 129, 129, 130, 130, 130, 131, 131, 131, 132, 132, 132, 133, 133, 133, 134, 134, 134, 135, 135, 135, 136, 136, 136, 137, 137, 137, 138, 138, 138, 139, 139, 139, 140, 140, 140, 141, 141, 141, 142, 142, 142, 143, 143, 143, 144, 144, 144, 145, 145, 145, 146, 146, 146, 147, 147, 147, 148, 148, 148, 149, 149, 149, 150, 150, 150, 151, 151, 151, 152, 152, 152, 153, 153, 153, 154, 154, 154, 154, 154, 154, 155, 155, 155, 156, 156, 156, 157, 157, 157, 158, 158, 158, 159, 159, 159, 160, 160, 160, 161, 161, 161, 162, 162, 162, 163, 163, 163, 164, 164, 164, 165, 165, 165, 166, 166, 166, 167, 167, 167, 168, 168, 168, 169, 169, 169, 170, 170, 170, 171, 171, 171, 172, 172, 172, 173, 173, 173, 174, 174, 174, 175, 175, 175, 176, 176, 176, 177, 177, 177, 178, 178, 178, 179, 179, 179, 180, 180, 180, 181, 181, 181, 182, 182, 182, 183, 183, 183, 184, 184, 184, 185, 185, 185, 186, 186, 186, 187, 187, 187, 188, 188, 188, 189, 189, 189, 190, 190, 190, 191, 191, 191, 192, 192, 192, 193, 193, 193, 194, 194, 194, 195, 195, 195, 196, 196, 196, 197, 197, 197, 198, 198, 198, 199, 199, 199, 200, 200, 200, 201, 201, 201, 202, 202, 202, 203, 203, 203, 204, 204, 204, 205, 205, 205, 206, 206, 206, 207, 207, 207, 208, 208, 208, 209, 209, 209, 209, 209, 209, 210, 210, 210, 211, 211, 211, 212, 212, 212, 213, 213, 213, 214, 214, 214, 215, 215, 215, 216, 216, 216, 217, 217, 217, 218, 218, 218, 219, 219, 219, 220, 220, 220, 221, 221, 221, 222, 222, 222, 223, 223, 223, 224, 224, 224, 225, 225, 225, 226, 226, 226, 227, 227, 227, 228, 228, 228, 229, 229, 229, 230, 230, 230, 231, 231, 231, 232, 232, 232, 233, 233, 233, 234, 234, 234, 235, 235, 235 }; + +const byte colorMap_contrast[] = { 16, 16, 16, 23, 16, 22, 30, 15, 30, 37, 16, 37, 46, 15, 45, 53, 15, 52, 60, 15, 60, 67, 15, 67, 75, 15, 75, 82, 15, 81, 89, 15, 90, 98, 14, 96, 105, 14, 105, 112, 14, 111, 120, 15, 121, 127, 15, 127, 135, 15, 135, 143, 14, 142, 150, 14, 150, 158, 14, 157, 165, 14, 165, 172, 14, 172, 179, 14, 180, 186, 14, 187, 195, 14, 195, 202, 14, 201, 209, 14, 210, 217, 14, 216, 209, 15, 214, 202, 14, 211, 194, 15, 209, 187, 14, 206, 179, 15, 204, 172, 14, 201, 165, 15, 199, 157, 14, 196, 150, 15, 194, 141, 14, 191, 135, 15, 189, 126, 14, 186, 120, 15, 184, 112, 14, 181, 105, 15, 179, 97, 14, 176, 91, 15, 174, 82, 14, 171, 74, 15, 169, 67, 14, 166, 60, 15, 164, 52, 14, 161, 45, 15, 159, 38, 14, 156, 30, 15, 154, 23, 14, 151, 15, 15, 149, 16, 23, 152, 14, 30, 155, 15, 38, 156, 15, 44, 158, 14, 53, 162, 14, 59, 164, 15, 67, 165, 13, 74, 168, 14, 82, 171, 15, 89, 174, 13, 96, 176, 14, 104, 178, 14, 111, 180, 13, 119, 183, 13, 125, 185, 14, 133, 187, 12, 140, 189, 13, 148, 192, 14, 155, 195, 12, 162, 198, 13, 170, 199, 13, 177, 201, 12, 185, 205, 12, 191, 207, 13, 199, 208, 11, 206, 211, 12, 214, 214, 12, 208, 206, 12, 205, 200, 12, 199, 192, 12, 194, 185, 12, 190, 176, 12, 185, 169, 14, 181, 162, 12, 176, 155, 13, 170, 147, 14, 166, 141, 13, 161, 133, 13, 156, 125, 14, 151, 119, 13, 147, 110, 14, 142, 103, 14, 137, 96, 13, 132, 88, 14, 128, 81, 14, 122, 74, 13, 118, 66, 14, 113, 60, 15, 108, 52, 14, 104, 44, 15, 99, 36, 15, 93, 29, 15, 90, 22, 15, 84, 15, 23, 89, 14, 28, 93, 13, 36, 97, 15, 42, 103, 14, 49, 106, 13, 55, 112, 13, 63, 116, 12, 69, 120, 13, 76, 125, 12, 84, 129, 12, 90, 134, 11, 97, 138, 10, 104, 143, 12, 111, 147, 11, 117, 152, 10, 124, 156, 9, 130, 161, 10, 138, 165, 10, 144, 170, 9, 151, 174, 9, 159, 179, 8, 165, 183, 9, 172, 187, 8, 179, 193, 8, 186, 196, 7, 192, 202, 6, 200, 206, 8, 205, 210, 7, 213, 215, 6, 209, 208, 7, 207, 201, 7, 204, 194, 7, 200, 187, 7, 196, 180, 8, 194, 173, 8, 191, 166, 8, 187, 159, 9, 183, 153, 9, 181, 145, 9, 178, 139, 10, 174, 132, 10, 172, 124, 10, 168, 118, 11, 166, 112, 10, 162, 105, 10, 160, 98, 11, 156, 91, 11, 153, 84, 11, 150, 77, 12, 147, 70, 12, 143, 63, 12, 140, 57, 13, 137, 49, 13, 134, 43, 13, 130, 36, 14, 127, 29, 14, 124, 22, 14, 121, 15, 15, 124, 16, 17, 128, 17, 19, 130, 20, 19, 133, 21, 21, 135, 21, 22, 139, 23, 24, 141, 25, 24, 144, 26, 26, 148, 28, 28, 151, 29, 30, 153, 31, 30, 156, 32, 32, 160, 34, 34, 163, 35, 36, 164, 36, 36, 168, 38, 38, 171, 39, 40, 174, 40, 42, 176, 43, 42, 180, 44, 44, 183, 45, 46, 187, 46, 48, 189, 49, 48, 191, 49, 49, 194, 50, 51, 198, 52, 53, 200, 54, 53, 203, 55, 55, 204, 60, 61, 205, 67, 68, 206, 73, 72, 207, 79, 79, 208, 84, 84, 209, 91, 91, 210, 96, 97, 211, 103, 104, 212, 109, 108, 213, 115, 114, 214, 120, 120, 215, 127, 127, 216, 132, 133, 217, 139, 139, 218, 145, 143, 219, 151, 150, 220, 156, 156, 221, 163, 163, 222, 168, 169, 223, 175, 175, 224, 181, 179, 225, 187, 186, 226, 192, 192, 227, 199, 199, 228, 204, 204, 229, 211, 211, 230, 217, 215, 231, 223, 222, 232, 228, 228 }; + +const byte colorMap_doubleRainbow[] = { 18, 15, 18, 25, 17, 26, 34, 18, 32, 43, 19, 39, 52, 21, 48, 60, 23, 55, 69, 25, 62, 77, 26, 70, 86, 28, 75, 95, 30, 84, 103, 31, 91, 112, 34, 98, 120, 35, 106, 129, 36, 111, 138, 39, 120, 146, 40, 128, 155, 42, 136, 150, 44, 140, 145, 47, 146, 139, 51, 151, 134, 54, 157, 130, 57, 161, 124, 60, 168, 119, 63, 172, 115, 66, 179, 109, 70, 183, 104, 73, 189, 99, 76, 194, 93, 80, 200, 89, 83, 205, 84, 86, 211, 78, 90, 216, 73, 92, 222, 69, 96, 227, 63, 99, 233, 59, 103, 238, 57, 104, 230, 54, 107, 221, 50, 109, 213, 50, 113, 206, 46, 115, 196, 45, 117, 189, 42, 120, 180, 39, 123, 171, 38, 125, 164, 35, 127, 154, 32, 130, 147, 30, 133, 138, 28, 135, 129, 25, 138, 122, 24, 140, 113, 21, 144, 104, 20, 146, 97, 16, 148, 87, 14, 152, 81, 27, 153, 75, 41, 157, 70, 54, 160, 64, 69, 164, 60, 84, 166, 54, 98, 170, 49, 110, 173, 44, 123, 176, 38, 138, 180, 34, 151, 182, 28, 166, 186, 23, 179, 189, 18, 194, 193, 13, 194, 189, 13, 194, 186, 13, 193, 183, 14, 194, 180, 15, 194, 177, 15, 194, 174, 14, 193, 171, 14, 194, 169, 15, 193, 165, 15, 194, 161, 16, 194, 160, 15, 194, 156, 17, 194, 154, 18, 195, 150, 17, 194, 147, 17, 195, 145, 18, 195, 142, 18, 195, 138, 19, 194, 135, 19, 195, 133, 20, 195, 129, 20, 195, 126, 19, 195, 124, 22, 193, 118, 21, 191, 114, 24, 189, 109, 24, 188, 104, 27, 186, 100, 27, 185, 95, 29, 183, 91, 30, 181, 86, 32, 180, 82, 33, 178, 77, 35, 177, 73, 36, 176, 67, 38, 173, 63, 39, 172, 59, 41, 172, 54, 41, 169, 50, 44, 169, 45, 45, 170, 53, 60, 171, 61, 74, 174, 68, 90, 174, 76, 103, 177, 83, 119, 179, 92, 133, 181, 99, 149, 182, 107, 162, 185, 114, 178, 186, 123, 192, 187, 131, 208, 190, 139, 222, 193, 146, 238, 194, 149, 236, 195, 153, 238, 197, 158, 237, 199, 160, 237, 200, 165, 237, 203, 168, 236, 193, 169, 235, 185, 168, 232, 176, 168, 229, 166, 168, 228, 157, 168, 225, 149, 168, 222, 140, 170, 220, 131, 169, 218, 121, 170, 215, 113, 170, 212, 103, 170, 211, 94, 170, 208, 86, 171, 206, 76, 171, 203, 68, 171, 202, 59, 171, 199, 51, 170, 196, 41, 171, 195, 33, 172, 193, 23, 172, 190, 14, 172, 187, 18, 173, 181, 24, 174, 174, 30, 175, 166, 33, 176, 160, 39, 177, 152, 45, 178, 145, 49, 179, 139, 54, 180, 131, 60, 181, 126, 64, 182, 118, 69, 183, 110, 74, 183, 104, 79, 185, 97, 84, 186, 91, 88, 187, 83, 93, 187, 76, 99, 189, 71, 103, 190, 63, 107, 191, 57, 113, 192, 50, 118, 193, 42, 122, 194, 36, 127, 195, 28, 133, 195, 20, 139, 196, 15, 143, 199, 14, 148, 200, 15, 151, 202, 13, 155, 204, 13, 161, 206, 15, 164, 208, 13, 169, 209, 13, 173, 211, 14, 178, 213, 13, 182, 214, 13, 187, 216, 14, 192, 217, 13, 196, 219, 13, 201, 221, 13, 205, 223, 13, 210, 224, 13, 213, 226, 11, 217, 228, 13, 222, 229, 13, 226, 231, 11, 231, 233, 13, 236, 234, 13, 236, 229, 13, 236, 224, 16, 237, 219, 17, 236, 214, 20, 236, 209, 22, 236, 203, 22, 236, 198, 25, 237, 193, 28, 236, 188, 30, 236, 183, 31, 236, 177, 33, 236, 172, 34, 236, 167, 36, 236, 162, 39, 238, 156, 42, 237, 151, 42, 237, 146, 45, 237, 140, 47, 238, 136, 48, 238, 131, 51, 237, 125, 53, 235, 118, 52, 235, 113, 52, 234, 105, 52, 233, 99, 52, 232, 93, 52, 231, 86, 53, 229, 80, 52, 230, 73, 52, 228, 67, 53, 227, 61, 53, 226, 54, 52, 225, 48, 52, 227, 56, 61, 227, 64, 69, 227, 73, 77, 227, 80, 86, 227, 88, 93, 229, 96, 101, 228, 105, 109, 230, 113, 118, 230, 121, 126, 231, 130, 136, 231, 138, 142, 230, 146, 150, 231, 154, 158, 233, 162, 166, 233, 170, 175, 232, 175, 178, 232, 179, 183, 233, 184, 189, 233, 189, 194, 233, 195, 198, 233, 199, 202, 235, 204, 206, 235, 208, 213, 234, 213, 216, 235, 218, 222, 235, 222, 227, 234, 227, 230, 235, 232, 235 }; + +const byte colorMap_grayRed[] = { 218, 186, 175, 216, 186, 174, 214, 186, 173, 213, 185, 172, 212, 184, 171, 209, 183, 170, 206, 182, 170, 205, 181, 169, 202, 180, 168, 202, 180, 168, 199, 179, 168, 197, 178, 167, 194, 178, 166, 193, 177, 166, 191, 177, 165, 186, 176, 165, 185, 175, 164, 182, 173, 162, 180, 174, 162, 177, 172, 162, 174, 172, 161, 172, 170, 159, 170, 170, 160, 168, 169, 159, 165, 169, 158, 162, 167, 157, 160, 168, 157, 157, 167, 155, 156, 166, 154, 153, 165, 155, 149, 164, 155, 146, 164, 154, 143, 163, 152, 140, 162, 153, 137, 161, 151, 136, 160, 150, 134, 159, 149, 131, 159, 150, 128, 157, 148, 126, 158, 148, 124, 156, 147, 122, 156, 146, 120, 155, 147, 117, 155, 146, 115, 154, 145, 110, 152, 144, 109, 152, 144, 106, 151, 142, 105, 150, 141, 101, 149, 141, 100, 149, 141, 99, 148, 140, 96, 148, 139, 93, 146, 138, 92, 147, 138, 91, 146, 137, 90, 145, 138, 86, 143, 136, 85, 142, 135, 83, 142, 134, 80, 142, 133, 77, 140, 133, 76, 139, 132, 75, 138, 131, 74, 137, 130, 72, 137, 129, 71, 136, 130, 69, 137, 128, 68, 136, 127, 67, 134, 128, 66, 133, 127, 65, 134, 127, 64, 133, 126, 63, 132, 125, 62, 131, 124, 61, 130, 123, 60, 129, 122, 59, 128, 121, 59, 128, 121, 58, 127, 120, 58, 125, 119, 58, 125, 119, 57, 124, 118, 58, 123, 117, 58, 123, 117, 58, 123, 117, 57, 122, 116, 56, 121, 115, 56, 121, 115, 57, 120, 115, 58, 117, 111, 58, 117, 111, 59, 116, 111, 59, 116, 111, 60, 114, 110, 60, 115, 108, 61, 114, 108, 61, 112, 107, 61, 112, 107, 63, 112, 107, 63, 112, 107, 63, 110, 104, 65, 109, 104, 66, 109, 104, 67, 108, 104, 69, 106, 102, 72, 107, 101, 72, 105, 100, 73, 104, 100, 75, 103, 100, 78, 102, 98, 77, 101, 97, 79, 102, 98, 82, 101, 96, 83, 100, 96, 85, 99, 96, 86, 99, 95, 89, 98, 93, 90, 97, 93, 92, 95, 92, 96, 94, 91, 97, 94, 91, 100, 93, 89, 103, 93, 90, 104, 93, 88, 107, 91, 88, 107, 90, 86, 111, 89, 87, 112, 89, 87, 114, 88, 85, 117, 87, 83, 120, 87, 84, 122, 86, 84, 125, 85, 82, 126, 84, 82, 130, 82, 81, 134, 82, 80, 135, 82, 78, 138, 81, 78, 140, 80, 78, 143, 80, 77, 145, 79, 77, 148, 78, 75, 150, 77, 75, 153, 77, 75, 154, 77, 73, 157, 75, 73, 159, 73, 72, 163, 73, 71, 164, 72, 71, 168, 70, 69, 171, 71, 69, 173, 70, 69, 176, 68, 67, 178, 68, 67, 180, 67, 65, 182, 66, 65, 184, 66, 66, 187, 65, 64, 188, 64, 64, 191, 63, 63, 193, 63, 63, 194, 62, 61, 197, 61, 61, 198, 60, 61, 202, 59, 57, 204, 58, 57, 205, 57, 57, 208, 58, 56, 209, 57, 56, 212, 56, 56, 213, 55, 55, 214, 54, 54, 216, 53, 52, 218, 52, 52, 219, 51, 52, 221, 51, 50, 222, 50, 50, 223, 49, 50, 225, 49, 48, 227, 47, 47, 228, 48, 48, 228, 46, 47, 229, 45, 46, 231, 45, 46, 231, 45, 46, 232, 44, 46, 233, 43, 43, 234, 42, 43, 234, 40, 42, 234, 40, 42, 236, 40, 42, 236, 38, 41, 236, 38, 39, 238, 37, 39, 236, 35, 37, 237, 35, 35, 237, 35, 35, 236, 34, 36, 238, 33, 34, 237, 32, 35, 238, 31, 35, 237, 31, 32, 236, 30, 31, 235, 29, 30, 235, 29, 30, 235, 29, 30, 234, 28, 29, 234, 26, 28, 233, 25, 27, 232, 24, 26, 232, 24, 26, 231, 23, 25, 231, 23, 25, 230, 22, 24, 232, 21, 24, 231, 20, 23, 230, 19, 22, 229, 18, 21, 230, 18, 21, 229, 17, 20, 229, 17, 20, 228, 16, 19, 227, 15, 18 }; + +const byte colorMap_glowBow[] = { 16, 16, 16, 19, 17, 18, 22, 16, 16, 25, 17, 18, 28, 17, 19, 31, 17, 20, 34, 17, 19, 36, 18, 20, 39, 18, 19, 43, 19, 21, 45, 18, 21, 48, 20, 21, 52, 19, 22, 54, 20, 23, 58, 20, 23, 63, 21, 23, 68, 21, 25, 70, 21, 26, 73, 22, 27, 75, 22, 26, 79, 22, 27, 81, 22, 28, 84, 23, 27, 87, 22, 28, 91, 24, 30, 96, 23, 30, 102, 24, 33, 104, 25, 32, 108, 25, 33, 110, 25, 34, 117, 25, 34, 120, 27, 34, 122, 27, 35, 127, 28, 35, 129, 27, 35, 132, 29, 37, 135, 27, 37, 138, 29, 38, 141, 29, 39, 143, 29, 40, 147, 29, 41, 150, 31, 41, 152, 30, 41, 155, 29, 42, 158, 30, 41, 165, 31, 44, 167, 32, 43, 170, 32, 44, 175, 33, 45, 177, 33, 46, 178, 32, 46, 182, 32, 45, 186, 33, 47, 188, 34, 48, 190, 34, 47, 194, 34, 48, 195, 35, 49, 195, 35, 47, 197, 38, 48, 196, 39, 46, 198, 39, 45, 199, 41, 44, 200, 42, 43, 201, 43, 43, 200, 44, 41, 201, 45, 42, 203, 46, 41, 204, 47, 42, 204, 47, 40, 205, 49, 40, 205, 49, 38, 206, 52, 38, 207, 52, 36, 208, 53, 37, 209, 54, 36, 210, 55, 36, 210, 58, 35, 211, 59, 34, 212, 60, 33, 213, 60, 33, 214, 61, 33, 213, 62, 31, 215, 64, 33, 215, 64, 31, 216, 66, 30, 218, 66, 30, 218, 66, 30, 218, 68, 29, 219, 70, 28, 220, 69, 28, 221, 72, 26, 223, 73, 26, 222, 74, 24, 223, 75, 25, 224, 76, 24, 225, 78, 23, 225, 78, 22, 226, 79, 23, 227, 80, 22, 227, 81, 20, 228, 82, 21, 229, 83, 20, 230, 83, 18, 231, 86, 19, 231, 86, 17, 232, 87, 16, 233, 88, 17, 234, 90, 16, 235, 91, 14, 235, 91, 14, 236, 93, 13, 237, 94, 12, 236, 96, 13, 237, 97, 13, 237, 99, 14, 237, 101, 13, 236, 103, 12, 236, 105, 13, 237, 106, 12, 236, 108, 11, 236, 112, 12, 237, 113, 13, 236, 115, 12, 236, 117, 13, 235, 119, 12, 236, 122, 12, 237, 123, 13, 237, 125, 13, 236, 127, 12, 236, 129, 13, 237, 130, 12, 236, 132, 13, 236, 134, 12, 237, 135, 12, 237, 137, 13, 237, 142, 12, 236, 144, 13, 236, 146, 12, 237, 147, 13, 237, 149, 12, 236, 151, 13, 237, 152, 13, 237, 154, 12, 236, 156, 13, 236, 158, 12, 236, 160, 11, 235, 161, 12, 236, 163, 12, 236, 165, 13, 235, 167, 12, 236, 170, 12, 236, 172, 11, 235, 173, 12, 236, 176, 12, 235, 179, 12, 236, 180, 13, 236, 182, 12, 236, 184, 11, 237, 185, 12, 236, 187, 11, 236, 188, 12, 235, 190, 12, 236, 191, 13, 235, 194, 12, 236, 196, 11, 235, 199, 11, 236, 201, 11, 235, 202, 12, 235, 204, 14, 236, 204, 19, 236, 205, 23, 236, 204, 27, 235, 206, 30, 236, 206, 34, 236, 207, 37, 236, 207, 41, 235, 208, 45, 236, 208, 48, 236, 209, 52, 236, 209, 56, 235, 211, 65, 236, 212, 68, 235, 212, 71, 236, 212, 74, 234, 212, 78, 235, 213, 82, 235, 214, 87, 236, 214, 91, 236, 215, 94, 235, 217, 97, 235, 216, 100, 235, 217, 105, 235, 216, 108, 234, 218, 111, 235, 218, 116, 235, 219, 122, 235, 220, 127, 236, 220, 131, 235, 221, 134, 235, 221, 138, 235, 222, 142, 235, 221, 146, 234, 222, 148, 235, 223, 153, 235, 224, 157, 235, 225, 160, 236, 225, 165, 234, 225, 168, 235, 226, 171, 235, 225, 175, 236, 227, 182, 235, 228, 187, 234, 228, 190, 234, 229, 195, 235, 230, 197, 236, 230, 202, 234, 230, 205, 235, 231, 208, 235, 232, 213, 235, 231, 216, 234, 232, 219, 234, 234, 224, 235, 234, 228, 235, 235, 235 }; + +const byte colorMap_grayscale[] = { 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, + 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 20, 21, 21, 21, 22, 22, 22, 23, 23, + 23, 24, 24, 24, 25, 25, 25, 26, 26, 26, 27, 27, 27, 28, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32, 32, 32, 33, 33, 33, 34, 34, 34, 35, + 35, 35, 36, 36, 36, 37, 37, 37, 38, 38, 38, 39, 39, 39, 40, 40, 40, 41, 41, 41, 42, 42, 42, 43, 43, 43, 44, 44, 44, 45, 45, 45, 46, 46, 46, + 47, 47, 47, 48, 48, 48, 49, 49, 49, 50, 50, 50, 51, 51, 51, 52, 52, 52, 53, 53, 53, 54, 54, 54, 55, 55, 55, 56, 56, 56, 57, 57, 57, 58, 58, + 58, 59, 59, 59, 60, 60, 60, 61, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 65, 65, 65, 66, 66, 66, 67, 67, 67, 68, 68, 68, 69, 69, 69, 70, + 70, 70, 71, 71, 71, 72, 72, 72, 73, 73, 73, 74, 74, 74, 75, 75, 75, 76, 76, 76, 77, 77, 77, 78, 78, 78, 79, 79, 79, 80, 80, 80, 81, 81, 81, + 82, 82, 82, 83, 83, 83, 84, 84, 84, 85, 85, 85, 86, 86, 86, 87, 87, 87, 88, 88, 88, 89, 89, 89, 90, 90, 90, 91, 91, 91, 92, 92, 92, 93, 93, + 93, 94, 94, 94, 95, 95, 95, 96, 96, 96, 97, 97, 97, 98, 98, 98, 99, 99, 99, 100, 100, 100, 101, 101, 101, 102, 102, 102, 103, 103, 103, 104, + 104, 104, 105, 105, 105, 106, 106, 106, 107, 107, 107, 108, 108, 108, 109, 109, 109, 110, 110, 110, 111, 111, 111, 112, 112, 112, 113, 113, + 113, 114, 114, 114, 115, 115, 115, 116, 116, 116, 117, 117, 117, 118, 118, 118, 119, 119, 119, 120, 120, 120, 121, 121, 121, 122, 122, 122, + 123, 123, 123, 124, 124, 124, 125, 125, 125, 126, 126, 126, 127, 127, 127, 128, 128, 128, 129, 129, 129, 130, 130, 130, 131, 131, 131, 132, + 132, 132, 133, 133, 133, 134, 134, 134, 135, 135, 135, 136, 136, 136, 137, 137, 137, 138, 138, 138, 139, 139, 139, 140, 140, 140, 141, 141, + 141, 142, 142, 142, 143, 143, 143, 144, 144, 144, 145, 145, 145, 146, 146, 146, 147, 147, 147, 148, 148, 148, 149, 149, 149, 150, 150, 150, + 151, 151, 151, 152, 152, 152, 153, 153, 153, 154, 154, 154, 155, 155, 155, 156, 156, 156, 157, 157, 157, 158, 158, 158, 159, 159, 159, 160, + 160, 160, 161, 161, 161, 162, 162, 162, 163, 163, 163, 164, 164, 164, 165, 165, 165, 166, 166, 166, 167, 167, 167, 168, 168, 168, 169, 169, + 169, 170, 170, 170, 171, 171, 171, 172, 172, 172, 173, 173, 173, 174, 174, 174, 175, 175, 175, 176, 176, 176, 177, 177, 177, 178, 178, 178, + 179, 179, 179, 180, 180, 180, 181, 181, 181, 182, 182, 182, 183, 183, 183, 184, 184, 184, 185, 185, 185, 186, 186, 186, 187, 187, 187, 188, + 188, 188, 189, 189, 189, 190, 190, 190, 191, 191, 191, 192, 192, 192, 193, 193, 193, 194, 194, 194, 195, 195, 195, 196, 196, 196, 197, 197, + 197, 198, 198, 198, 199, 199, 199, 200, 200, 200, 201, 201, 201, 202, 202, 202, 203, 203, 203, 204, 204, 204, 205, 205, 205, 206, 206, 206, + 207, 207, 207, 208, 208, 208, 209, 209, 209, 210, 210, 210, 211, 211, 211, 212, 212, 212, 213, 213, 213, 214, 214, 214, 215, 215, 215, 216, + 216, 216, 217, 217, 217, 218, 218, 218, 219, 219, 219, 220, 220, 220, 221, 221, 221, 222, 222, 222, 223, 223, 223, 224, 224, 224, 225, 225, + 225, 226, 226, 226, 227, 227, 227, 228, 228, 228, 229, 229, 229, 230, 230, 230, 231, 231, 231, 232, 232, 232, 233, 233, 233, 234, 234, 234, + 235, 235, 235, 236, 236, 236, 237, 237, 237, 238, 238, 238, 239, 239, 239, 240, 240, 240, 241, 241, 241, 242, 242, 242, 243, 243, 243, 244, + 244, 244, 245, 245, 245, 246, 246, 246, 247, 247, 247, 248, 248, 248, 249, 249, 249, 250, 250, 250, 251, 251, 251, 252, 252, 252, 253, 253, + 253, 254, 254, 254, 255, 255, 255 }; + +const byte colorMap_hottest[] = { 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 20, 21, 21, 21, 22, 22, 22, 23, 23, 23, 24, 24, 24, 25, 25, 25, 26, 26, 26, 27, 27, 27, 28, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32, 32, 32, 33, 33, 33, 34, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 37, 38, 38, 38, 39, 39, 39, 40, 40, 40, 41, 41, 41, 42, 42, 42, 43, 43, 43, 44, 44, 44, 44, 44, 44, 45, 45, 45, 46, 46, 46, 47, 47, 47, 48, 48, 48, 49, 49, 49, 50, 50, 50, 51, 51, 51, 52, 52, 52, 53, 53, 53, 54, 54, 54, 55, 55, 55, 56, 56, 56, 57, 57, 57, 58, 58, 58, 59, 59, 59, 60, 60, 60, 61, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 65, 65, 65, 66, 66, 66, 67, 67, 67, 68, 68, 68, 69, 69, 69, 70, 70, 70, 71, 71, 71, 72, 72, 72, 73, 73, 73, 74, 74, 74, 75, 75, 75, 76, 76, 76, 77, 77, 77, 78, 78, 78, 79, 79, 79, 80, 80, 80, 81, 81, 81, 82, 82, 82, 83, 83, 83, 84, 84, 84, 85, 85, 85, 86, 86, 86, 87, 87, 87, 88, 88, 88, 89, 89, 89, 90, 90, 90, 91, 91, 91, 92, 92, 92, 93, 93, 93, 94, 94, 94, 95, 95, 95, 96, 96, 96, 97, 97, 97, 98, 98, 98, 99, 99, 99, 99, 99, 99, 100, 100, 100, 101, 101, 101, 102, 102, 102, 103, 103, 103, 104, 104, 104, 105, 105, 105, 106, 106, 106, 107, 107, 107, 108, 108, 108, 109, 109, 109, 110, 110, 110, 111, 111, 111, 112, 112, 112, 113, 113, 113, 114, 114, 114, 115, 115, 115, 116, 116, 116, 117, 117, 117, 118, 118, 118, 119, 119, 119, 120, 120, 120, 121, 121, 121, 122, 122, 122, 123, 123, 123, 124, 124, 124, 125, 125, 125, 126, 126, 126, 127, 127, 127, 128, 128, 128, 129, 129, 129, 130, 130, 130, 131, 131, 131, 132, 132, 132, 133, 133, 133, 134, 134, 134, 135, 135, 135, 136, 136, 136, 137, 137, 137, 138, 138, 138, 139, 139, 139, 140, 140, 140, 141, 141, 141, 142, 142, 142, 143, 143, 143, 144, 144, 144, 145, 145, 145, 146, 146, 146, 147, 147, 147, 148, 148, 148, 149, 149, 149, 150, 150, 150, 151, 151, 151, 152, 152, 152, 153, 153, 153, 154, 154, 154, 154, 154, 154, 155, 155, 155, 156, 156, 156, 157, 157, 157, 158, 158, 158, 159, 159, 159, 160, 160, 160, 161, 161, 161, 162, 162, 162, 163, 163, 163, 164, 164, 164, 165, 165, 165, 166, 166, 166, 167, 167, 167, 168, 168, 168, 169, 169, 169, 170, 170, 170, 171, 171, 171, 172, 172, 172, 173, 173, 173, 174, 174, 174, 175, 175, 175, 176, 176, 176, 177, 177, 177, 178, 178, 178, 179, 179, 179, 180, 180, 180, 181, 181, 181, 182, 182, 182, 183, 183, 183, 184, 184, 184, 185, 185, 185, 186, 186, 186, 187, 187, 187, 188, 188, 188, 189, 189, 189, 190, 190, 190, 191, 191, 191, 192, 192, 192, 193, 193, 193, 194, 194, 194, 195, 195, 195, 196, 196, 196, 197, 197, 197, 198, 198, 198, 199, 199, 199, 200, 200, 200, 201, 201, 201, 202, 202, 202, 203, 203, 203, 204, 204, 204, 205, 205, 205, 206, 206, 206, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13, 190, 14, 13 }; + +const byte colorMap_ironblack[] = { 255, 255, 255, 253, 253, 253, 251, 251, 251, 249, 249, 249, 247, 247, 247, 245, 245, 245, 243, 243, 243, 241, 241, + 241, 239, 239, 239, 237, 237, 237, 235, 235, 235, 233, 233, 233, 231, 231, 231, 229, 229, 229, 227, 227, 227, 225, 225, 225, 223, 223, 223, + 221, 221, 221, 219, 219, 219, 217, 217, 217, 215, 215, 215, 213, 213, 213, 211, 211, 211, 209, 209, 209, 207, 207, 207, 205, 205, 205, 203, + 203, 203, 201, 201, 201, 199, 199, 199, 197, 197, 197, 195, 195, 195, 193, 193, 193, 191, 191, 191, 189, 189, 189, 187, 187, 187, 185, 185, + 185, 183, 183, 183, 181, 181, 181, 179, 179, 179, 177, 177, 177, 175, 175, 175, 173, 173, 173, 171, 171, 171, 169, 169, 169, 167, 167, 167, + 165, 165, 165, 163, 163, 163, 161, 161, 161, 159, 159, 159, 157, 157, 157, 155, 155, 155, 153, 153, 153, 151, 151, 151, 149, 149, 149, 147, + 147, 147, 145, 145, 145, 143, 143, 143, 141, 141, 141, 139, 139, 139, 137, 137, 137, 135, 135, 135, 133, 133, 133, 131, 131, 131, 129, 129, + 129, 126, 126, 126, 124, 124, 124, 122, 122, 122, 120, 120, 120, 118, 118, 118, 116, 116, 116, 114, 114, 114, 112, 112, 112, 110, 110, 110, + 108, 108, 108, 106, 106, 106, 104, 104, 104, 102, 102, 102, 100, 100, 100, 98, 98, 98, 96, 96, 96, 94, 94, 94, 92, 92, 92, 90, 90, 90, 88, 88, + 88, 86, 86, 86, 84, 84, 84, 82, 82, 82, 80, 80, 80, 78, 78, 78, 76, 76, 76, 74, 74, 74, 72, 72, 72, 70, 70, 70, 68, 68, 68, 66, 66, 66, 64, + 64, 64, 62, 62, 62, 60, 60, 60, 58, 58, 58, 56, 56, 56, 54, 54, 54, 52, 52, 52, 50, 50, 50, 48, 48, 48, 46, 46, 46, 44, 44, 44, 42, 42, 42, + 40, 40, 40, 38, 38, 38, 36, 36, 36, 34, 34, 34, 32, 32, 32, 30, 30, 30, 28, 28, 28, 26, 26, 26, 24, 24, 24, 22, 22, 22, 20, 20, 20, 18, 18, + 18, 16, 16, 16, 14, 14, 14, 12, 12, 12, 10, 10, 10, 8, 8, 8, 6, 6, 6, 4, 4, 4, 2, 2, 2, 0, 0, 0, 0, 0, 9, 2, 0, 16, 4, 0, 24, 6, 0, 31, 8, 0, + 38, 10, 0, 45, 12, 0, 53, 14, 0, 60, 17, 0, 67, 19, 0, 74, 21, 0, 82, 23, 0, 89, 25, 0, 96, 27, 0, 103, 29, 0, 111, 31, 0, 118, 36, 0, 120, + 41, 0, 121, 46, 0, 122, 51, 0, 123, 56, 0, 124, 61, 0, 125, 66, 0, 126, 71, 0, 127, 76, 1, 128, 81, 1, 129, 86, 1, 130, 91, 1, 131, 96, 1, + 132, 101, 1, 133, 106, 1, 134, 111, 1, 135, 116, 1, 136, 121, 1, 136, 125, 2, 137, 130, 2, 137, 135, 3, 137, 139, 3, 138, 144, 3, 138, 149, 4, + 138, 153, 4, 139, 158, 5, 139, 163, 5, 139, 167, 5, 140, 172, 6, 140, 177, 6, 140, 181, 7, 141, 186, 7, 141, 189, 10, 137, 191, 13, 132, 194, + 16, 127, 196, 19, 121, 198, 22, 116, 200, 25, 111, 203, 28, 106, 205, 31, 101, 207, 34, 95, 209, 37, 90, 212, 40, 85, 214, 43, 80, 216, 46, + 75, 218, 49, 69, 221, 52, 64, 223, 55, 59, 224, 57, 49, 225, 60, 47, 226, 64, 44, 227, 67, 42, 228, 71, 39, 229, 74, 37, 230, 78, 34, 231, 81, + 32, 231, 85, 29, 232, 88, 27, 233, 92, 24, 234, 95, 22, 235, 99, 19, 236, 102, 17, 237, 106, 14, 238, 109, 12, 239, 112, 12, 240, 116, 12, + 240, 119, 12, 241, 123, 12, 241, 127, 12, 242, 130, 12, 242, 134, 12, 243, 138, 12, 243, 141, 13, 244, 145, 13, 244, 149, 13, 245, 152, 13, + 245, 156, 13, 246, 160, 13, 246, 163, 13, 247, 167, 13, 247, 171, 13, 248, 175, 14, 248, 178, 15, 249, 182, 16, 249, 185, 18, 250, 189, 19, + 250, 192, 20, 251, 196, 21, 251, 199, 22, 252, 203, 23, 252, 206, 24, 253, 210, 25, 253, 213, 27, 254, 217, 28, 254, 220, 29, 255, 224, 30, + 255, 227, 39, 255, 229, 53, 255, 231, 67, 255, 233, 81, 255, 234, 95, 255, 236, 109, 255, 238, 123, 255, 240, 137, 255, 242, 151, 255, 244, + 165, 255, 246, 179, 255, 248, 193, 255, 249, 207, 255, 251, 221, 255, 253, 235, 255, 255, 24 }; + +const byte colorMap_lava[] = { 16, 16, 16, 17, 19, 22, 19, 21, 30, 20, 24, 37, 22, 27, 43, 22, 31, 50, 24, 32, 57, 25, 37, 65, 26, 39, 70, 28, 43, 78, 29, 44, 85, 31, 47, 94, 32, 50, 100, 34, 53, 107, 34, 57, 113, 37, 59, 122, 37, 63, 128, 39, 66, 135, 40, 69, 141, 42, 71, 149, 44, 74, 156, 41, 76, 156, 41, 76, 156, 39, 78, 157, 36, 80, 155, 36, 82, 156, 34, 82, 156, 33, 85, 157, 31, 86, 157, 30, 86, 157, 29, 88, 156, 28, 91, 157, 26, 91, 157, 26, 93, 158, 23, 95, 158, 21, 97, 159, 20, 98, 159, 18, 99, 158, 17, 101, 160, 15, 102, 159, 15, 104, 160, 13, 105, 158, 13, 105, 158, 14, 106, 157, 13, 107, 157, 14, 108, 156, 14, 110, 156, 14, 111, 154, 15, 112, 155, 13, 113, 153, 13, 113, 151, 14, 114, 152, 14, 114, 151, 14, 116, 152, 14, 116, 150, 13, 118, 149, 13, 119, 147, 14, 120, 148, 13, 121, 146, 14, 122, 146, 14, 122, 146, 14, 124, 145, 14, 125, 143, 15, 126, 144, 14, 125, 143, 15, 126, 142, 13, 127, 142, 14, 128, 142, 14, 128, 140, 14, 130, 139, 14, 130, 139, 14, 130, 139, 14, 131, 137, 13, 133, 136, 13, 133, 135, 14, 134, 136, 13, 135, 134, 13, 135, 134, 13, 135, 134, 14, 137, 133, 14, 137, 131, 19, 133, 130, 24, 130, 132, 30, 125, 131, 35, 121, 130, 39, 118, 129, 46, 114, 129, 50, 109, 127, 56, 106, 127, 62, 101, 128, 67, 99, 126, 73, 94, 125, 78, 90, 126, 84, 85, 125, 89, 82, 124, 93, 78, 123, 99, 73, 122, 105, 70, 122, 109, 66, 121, 115, 62, 122, 120, 58, 121, 123, 57, 119, 124, 57, 115, 127, 56, 114, 130, 54, 112, 133, 54, 108, 134, 53, 108, 136, 51, 104, 137, 51, 102, 140, 50, 100, 144, 50, 98, 145, 50, 96, 148, 49, 94, 148, 47, 91, 151, 46, 90, 154, 45, 88, 155, 45, 84, 158, 44, 84, 161, 43, 81, 162, 42, 79, 165, 41, 77, 167, 41, 75, 168, 41, 74, 169, 40, 72, 171, 40, 72, 172, 39, 70, 174, 39, 67, 175, 38, 67, 176, 38, 65, 178, 38, 63, 180, 38, 62, 182, 38, 60, 183, 37, 60, 184, 37, 59, 186, 37, 57, 187, 36, 55, 189, 36, 53, 190, 35, 53, 191, 35, 52, 193, 34, 50, 194, 34, 48, 196, 36, 48, 199, 37, 48, 201, 39, 48, 203, 40, 47, 205, 41, 46, 207, 43, 48, 210, 43, 47, 212, 46, 48, 213, 47, 47, 215, 47, 46, 219, 49, 46, 220, 50, 46, 222, 53, 46, 224, 53, 47, 227, 55, 47, 228, 56, 46, 229, 57, 45, 233, 59, 46, 235, 60, 45, 237, 62, 45, 238, 63, 44, 237, 65, 41, 236, 67, 40, 237, 68, 39, 236, 70, 36, 237, 71, 35, 237, 73, 34, 238, 74, 33, 237, 77, 31, 237, 79, 30, 237, 79, 27, 237, 83, 25, 236, 84, 23, 237, 86, 21, 237, 88, 20, 237, 88, 16, 236, 90, 16, 237, 92, 15, 237, 94, 12, 237, 97, 13, 237, 99, 12, 236, 103, 12, 237, 106, 12, 236, 110, 12, 237, 113, 13, 237, 116, 13, 236, 120, 13, 237, 123, 13, 236, 127, 14, 237, 130, 14, 236, 132, 13, 236, 135, 13, 236, 139, 12, 235, 143, 12, 236, 146, 12, 237, 149, 12, 236, 153, 13, 235, 155, 12, 236, 158, 12, 237, 161, 12, 236, 163, 12, 236, 165, 13, 235, 167, 12, 236, 170, 12, 236, 172, 11, 235, 173, 12, 236, 176, 12, 235, 179, 12, 235, 181, 11, 236, 183, 13, 235, 185, 12, 235, 187, 11, 236, 189, 11, 236, 191, 13, 235, 194, 12, 236, 196, 11, 236, 199, 11, 236, 200, 12, 235, 202, 12, 236, 205, 23, 235, 207, 34, 235, 208, 45, 236, 209, 56, 235, 211, 67, 234, 212, 78, 236, 214, 90, 235, 216, 100, 234, 218, 111, 236, 220, 123, 234, 220, 133, 235, 221, 146, 235, 224, 157, 236, 225, 167, 235, 226, 179, 235, 229, 191, 235, 229, 201, 235, 232, 213, 235, 233, 224, 235, 235, 235 }; + +const byte colorMap_medical[] = { 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 36, 36, 198, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 70, 71, 238, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 25, 172, 193, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 14, 158, 13, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 15, 15, 123, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 237, 65, 197, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 238, 28, 28, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 236, 152, 93, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 230, 125, 12, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37, 236, 196, 37 }; + +const byte colorMap_rainbow[] = { 1, 3, 74, 0, 3, 74, 0, 3, 75, 0, 3, 75, 0, 3, 76, 0, 3, 76, 0, 3, 77, 0, 3, 79, 0, 3, 82, 0, 5, 85, 0, 7, 88, 0, 10, + 91, 0, 14, 94, 0, 19, 98, 0, 22, 100, 0, 25, 103, 0, 28, 106, 0, 32, 109, 0, 35, 112, 0, 38, 116, 0, 40, 119, 0, 42, 123, 0, 45, 128, 0, 49, + 133, 0, 50, 134, 0, 51, 136, 0, 52, 137, 0, 53, 139, 0, 54, 142, 0, 55, 144, 0, 56, 145, 0, 58, 149, 0, 61, 154, 0, 63, 156, 0, 65, 159, 0, + 66, 161, 0, 68, 164, 0, 69, 167, 0, 71, 170, 0, 73, 174, 0, 75, 179, 0, 76, 181, 0, 78, 184, 0, 79, 187, 0, 80, 188, 0, 81, 190, 0, 84, 194, + 0, 87, 198, 0, 88, 200, 0, 90, 203, 0, 92, 205, 0, 94, 207, 0, 94, 208, 0, 95, 209, 0, 96, 210, 0, 97, 211, 0, 99, 214, 0, 102, 217, 0, 103, + 218, 0, 104, 219, 0, 105, 220, 0, 107, 221, 0, 109, 223, 0, 111, 223, 0, 113, 223, 0, 115, 222, 0, 117, 221, 0, 118, 220, 1, 120, 219, 1, 122, + 217, 2, 124, 216, 2, 126, 214, 3, 129, 212, 3, 131, 207, 4, 132, 205, 4, 133, 202, 4, 134, 197, 5, 136, 192, 6, 138, 185, 7, 141, 178, 8, 142, + 172, 10, 144, 166, 10, 144, 162, 11, 145, 158, 12, 146, 153, 13, 147, 149, 15, 149, 140, 17, 151, 132, 22, 153, 120, 25, 154, 115, 28, 156, + 109, 34, 158, 101, 40, 160, 94, 45, 162, 86, 51, 164, 79, 59, 167, 69, 67, 171, 60, 72, 173, 54, 78, 175, 48, 83, 177, 43, 89, 179, 39, 93, + 181, 35, 98, 183, 31, 105, 185, 26, 109, 187, 23, 113, 188, 21, 118, 189, 19, 123, 191, 17, 128, 193, 14, 134, 195, 12, 138, 196, 10, 142, + 197, 8, 146, 198, 6, 151, 200, 5, 155, 201, 4, 160, 203, 3, 164, 204, 2, 169, 205, 2, 173, 206, 1, 175, 207, 1, 178, 207, 1, 184, 208, 0, 190, + 210, 0, 193, 211, 0, 196, 212, 0, 199, 212, 0, 202, 213, 1, 207, 214, 2, 212, 215, 3, 215, 214, 3, 218, 214, 3, 220, 213, 3, 222, 213, 4, 224, + 212, 4, 225, 212, 5, 226, 212, 5, 229, 211, 5, 232, 211, 6, 232, 211, 6, 233, 211, 6, 234, 210, 6, 235, 210, 7, 236, 209, 7, 237, 208, 8, 239, + 206, 8, 241, 204, 9, 242, 203, 9, 244, 202, 10, 244, 201, 10, 245, 200, 10, 245, 199, 11, 246, 198, 11, 247, 197, 12, 248, 194, 13, 249, 191, + 14, 250, 189, 14, 251, 187, 15, 251, 185, 16, 252, 183, 17, 252, 178, 18, 253, 174, 19, 253, 171, 19, 254, 168, 20, 254, 165, 21, 254, 164, + 21, 255, 163, 22, 255, 161, 22, 255, 159, 23, 255, 157, 23, 255, 155, 24, 255, 149, 25, 255, 143, 27, 255, 139, 28, 255, 135, 30, 255, 131, + 31, 255, 127, 32, 255, 118, 34, 255, 110, 36, 255, 104, 37, 255, 101, 38, 255, 99, 39, 255, 93, 40, 255, 88, 42, 254, 82, 43, 254, 77, 45, + 254, 69, 47, 254, 62, 49, 253, 57, 50, 253, 53, 52, 252, 49, 53, 252, 45, 55, 251, 39, 57, 251, 33, 59, 251, 32, 60, 251, 31, 60, 251, 30, 61, + 251, 29, 61, 251, 28, 62, 250, 27, 63, 250, 27, 65, 249, 26, 66, 249, 26, 68, 248, 25, 70, 248, 24, 73, 247, 24, 75, 247, 25, 77, 247, 25, 79, + 247, 26, 81, 247, 32, 83, 247, 35, 85, 247, 38, 86, 247, 42, 88, 247, 46, 90, 247, 50, 92, 248, 55, 94, 248, 59, 96, 248, 64, 98, 248, 72, + 101, 249, 81, 104, 249, 87, 106, 250, 93, 108, 250, 95, 109, 250, 98, 110, 250, 100, 111, 251, 101, 112, 251, 102, 113, 251, 109, 117, 252, + 116, 121, 252, 121, 123, 253, 126, 126, 253, 130, 128, 254, 135, 131, 254, 139, 133, 254, 144, 136, 254, 151, 140, 255, 158, 144, 255, 163, + 146, 255, 168, 149, 255, 173, 152, 255, 176, 153, 255, 178, 155, 255, 184, 160, 255, 191, 165, 255, 195, 168, 255, 199, 172, 255, 203, 175, + 255, 207, 179, 255, 211, 182, 255, 216, 185, 255, 218, 190, 255, 220, 196, 255, 222, 200, 255, 225, 202, 255, 227, 204, 255, 230, 206, 255, + 233, 208 }; + +const byte colorMap_wheel1[] = { 238, 14, 239, 234, 17, 234, 229, 22, 230, 225, 27, 225, 221, 30, 220, 216, 35, 216, 211, 39, 212, 208, 44, 207, 203, 48, 202, 198, 52, 199, 195, 56, 195, 190, 61, 191, 185, 65, 185, 180, 70, 181, 177, 74, 177, 172, 79, 172, 167, 83, 167, 163, 87, 163, 159, 92, 158, 154, 97, 154, 150, 100, 151, 145, 105, 146, 141, 109, 142, 138, 114, 138, 132, 117, 132, 127, 122, 128, 124, 127, 124, 119, 131, 119, 114, 135, 114, 111, 140, 110, 106, 144, 105, 101, 149, 101, 97, 152, 98, 93, 157, 93, 88, 162, 89, 85, 166, 85, 79, 170, 79, 75, 175, 75, 71, 179, 71, 67, 184, 66, 61, 188, 61, 57, 193, 57, 53, 197, 52, 49, 201, 50, 43, 205, 45, 40, 209, 40, 35, 214, 36, 31, 219, 32, 27, 222, 26, 22, 227, 22, 17, 232, 18, 14, 236, 13, 14, 232, 18, 14, 227, 24, 14, 224, 27, 14, 218, 31, 13, 214, 36, 14, 209, 39, 13, 205, 44, 14, 200, 50, 13, 197, 53, 14, 192, 58, 14, 188, 61, 13, 184, 66, 14, 179, 71, 13, 176, 75, 14, 171, 80, 13, 167, 85, 14, 162, 88, 14, 158, 93, 14, 153, 98, 14, 150, 102, 15, 145, 107, 14, 141, 112, 15, 136, 115, 14, 132, 120, 15, 127, 125, 14, 124, 129, 15, 119, 134, 15, 115, 139, 15, 110, 142, 15, 106, 147, 16, 102, 151, 15, 98, 156, 16, 93, 161, 15, 89, 164, 16, 84, 169, 14, 79, 173, 15, 75, 177, 15, 71, 182, 15, 66, 187, 15, 62, 190, 14, 58, 195, 15, 53, 200, 14, 49, 204, 15, 45, 209, 15, 40, 214, 15, 36, 217, 15, 32, 222, 16, 27, 227, 15, 23, 231, 16, 19, 236, 15, 14, 241, 20, 18, 235, 25, 23, 232, 28, 28, 226, 32, 32, 222, 38, 36, 219, 42, 40, 213, 45, 45, 209, 51, 50, 204, 55, 53, 200, 58, 58, 196, 62, 62, 190, 68, 66, 187, 72, 70, 181, 75, 75, 177, 81, 79, 174, 85, 83, 168, 88, 88, 164, 93, 93, 159, 98, 96, 155, 102, 100, 151, 106, 106, 146, 111, 109, 142, 115, 113, 136, 119, 119, 133, 124, 122, 129, 128, 126, 123, 133, 131, 120, 136, 136, 116, 141, 140, 110, 145, 143, 106, 149, 149, 101, 154, 153, 97, 158, 156, 93, 163, 161, 88, 166, 166, 84, 171, 170, 78, 176, 174, 75, 179, 179, 71, 184, 183, 65, 189, 187, 62, 193, 191, 56, 196, 196, 52, 202, 200, 49, 206, 204, 43, 209, 209, 39, 215, 214, 34, 219, 217, 30, 223, 221, 26, 226, 226, 20, 232, 230, 17, 236, 234, 13, 231, 235, 15, 228, 235, 21, 223, 235, 25, 219, 234, 29, 214, 235, 34, 209, 235, 38, 206, 235, 43, 201, 235, 48, 197, 235, 52, 192, 235, 56, 188, 234, 61, 184, 235, 66, 180, 235, 70, 175, 235, 74, 169, 235, 79, 166, 235, 81, 161, 236, 87, 158, 235, 91, 152, 235, 95, 149, 235, 100, 143, 235, 104, 141, 235, 109, 135, 235, 114, 130, 235, 118, 126, 235, 122, 121, 235, 126, 118, 235, 132, 113, 235, 136, 109, 235, 140, 104, 235, 145, 100, 234, 149, 96, 236, 153, 91, 236, 157, 87, 235, 161, 82, 235, 166, 78, 235, 170, 74, 236, 175, 70, 235, 180, 65, 235, 184, 61, 235, 188, 57, 236, 193, 52, 236, 198, 48, 235, 202, 43, 235, 206, 39, 235, 211, 35, 236, 216, 31, 235, 220, 26, 236, 223, 22, 235, 227, 17, 235, 232, 14, 236, 237, 17, 231, 233, 21, 227, 228, 26, 222, 224, 31, 219, 219, 34, 214, 215, 39, 209, 210, 43, 205, 206, 49, 201, 202, 53, 196, 198, 56, 192, 192, 62, 188, 189, 66, 183, 186, 71, 179, 180, 74, 174, 176, 79, 171, 172, 84, 166, 168, 88, 161, 163, 94, 157, 160, 97, 153, 154, 101, 149, 150, 106, 144, 145, 110, 140, 142, 115, 135, 137, 119, 131, 133, 123, 127, 127, 129, 122, 125, 132, 118, 119, 136, 114, 115, 142, 110, 111, 146, 105, 107, 151, 100, 101, 154, 96, 98, 159, 93, 93, 164, 87, 89, 167, 83, 84, 172, 78, 80, 177, 75, 75, 181, 70, 72, 186, 66, 66, 190, 62, 63, 195, 57, 60, 199, 53, 54, 202, 48, 50, 208, 44, 46, 212, 40, 42, 217, 35, 36, 221, 30, 33, 225, 27, 28, 230, 22, 24, 234, 18, 19, 240, 14, 16 }; + +const byte colorMap_wheel2[] = { 17, 14, 17, 16, 23, 17, 17, 32, 17, 16, 40, 16, 16, 49, 16, 15, 58, 16, 15, 65, 16, 14, 74, 16, 15, 82, 16, 15, 91, 15, 14, 100, 15, 15, 108, 15, 14, 117, 14, 15, 125, 16, 14, 134, 15, 14, 143, 15, 15, 151, 15, 14, 160, 14, 15, 168, 14, 14, 177, 14, 14, 186, 13, 13, 192, 14, 13, 201, 14, 14, 209, 13, 14, 219, 14, 13, 228, 14, 14, 236, 13, 22, 227, 22, 28, 219, 29, 37, 212, 37, 46, 204, 46, 52, 196, 53, 61, 188, 61, 69, 181, 69, 76, 172, 76, 85, 164, 85, 94, 156, 95, 102, 147, 102, 109, 140, 110, 117, 132, 117, 126, 123, 126, 133, 116, 134, 141, 108, 141, 149, 101, 149, 157, 93, 157, 165, 84, 165, 174, 76, 175, 183, 68, 183, 189, 60, 190, 198, 52, 198, 205, 45, 205, 213, 36, 214, 222, 29, 222, 228, 21, 229, 238, 14, 239, 233, 17, 238, 229, 22, 238, 223, 27, 239, 218, 31, 238, 213, 37, 238, 209, 41, 238, 204, 45, 239, 199, 51, 239, 195, 55, 238, 191, 60, 238, 185, 65, 237, 180, 69, 238, 177, 74, 239, 171, 79, 238, 167, 84, 238, 161, 89, 239, 157, 93, 238, 153, 98, 239, 147, 101, 237, 142, 107, 237, 138, 111, 238, 133, 115, 237, 128, 121, 238, 123, 125, 237, 118, 131, 237, 114, 135, 238, 109, 139, 237, 104, 145, 237, 100, 149, 238, 96, 154, 239, 91, 159, 238, 85, 163, 237, 82, 169, 237, 76, 173, 238, 71, 177, 238, 67, 182, 237, 61, 187, 236, 57, 192, 236, 52, 196, 237, 47, 201, 237, 43, 206, 237, 37, 210, 238, 33, 215, 238, 29, 220, 237, 23, 226, 237, 19, 230, 237, 15, 235, 237, 19, 229, 232, 24, 226, 228, 28, 221, 224, 33, 216, 218, 36, 212, 214, 42, 208, 210, 46, 203, 206, 49, 199, 201, 54, 194, 197, 59, 191, 192, 64, 185, 188, 68, 181, 183, 73, 176, 179, 76, 172, 175, 81, 168, 171, 86, 163, 167, 91, 158, 162, 95, 155, 157, 100, 150, 153, 104, 146, 148, 108, 141, 144, 114, 137, 139, 117, 133, 136, 121, 129, 130, 126, 123, 126, 131, 118, 123, 136, 115, 118, 139, 111, 114, 144, 106, 109, 148, 101, 105, 154, 97, 100, 157, 93, 97, 161, 89, 91, 167, 85, 86, 172, 79, 83, 176, 75, 77, 179, 71, 73, 184, 66, 70, 189, 61, 64, 194, 57, 61, 197, 53, 56, 202, 48, 52, 207, 45, 47, 212, 39, 44, 216, 35, 38, 219, 31, 34, 225, 27, 30, 229, 22, 26, 234, 17, 20, 239, 14, 18, 233, 14, 22, 230, 13, 26, 224, 14, 31, 219, 14, 35, 214, 14, 39, 210, 13, 45, 206, 14, 49, 201, 14, 55, 195, 14, 59, 190, 14, 64, 186, 14, 68, 181, 14, 72, 176, 14, 77, 173, 14, 84, 168, 14, 88, 163, 14, 92, 158, 14, 97, 152, 14, 101, 147, 14, 107, 143, 14, 111, 139, 15, 117, 133, 14, 120, 130, 14, 125, 125, 14, 131, 120, 14, 136, 115, 14, 140, 109, 14, 144, 106, 14, 149, 100, 13, 155, 96, 15, 158, 91, 15, 164, 87, 14, 169, 82, 14, 173, 77, 14, 177, 72, 14, 182, 66, 14, 186, 64, 14, 193, 58, 15, 197, 53, 15, 202, 48, 15, 206, 44, 14, 210, 39, 14, 216, 34, 14, 221, 30, 13, 225, 24, 15, 230, 20, 15, 235, 15, 14, 241, 20, 18, 237, 23, 21, 232, 27, 26, 228, 30, 29, 223, 36, 33, 220, 38, 37, 215, 42, 41, 211, 45, 45, 207, 50, 49, 203, 54, 52, 199, 58, 56, 195, 61, 60, 192, 66, 64, 188, 69, 68, 184, 73, 72, 180, 78, 75, 176, 82, 79, 172, 84, 82, 167, 89, 87, 164, 91, 90, 159, 97, 95, 156, 100, 98, 151, 104, 103, 147, 108, 105, 144, 112, 109, 140, 115, 113, 136, 120, 117, 132, 124, 121, 128, 127, 125, 124, 131, 129, 120, 134, 133, 116, 139, 137, 111, 143, 140, 107, 148, 144, 105, 151, 148, 101, 155, 152, 97, 158, 156, 93, 162, 160, 89, 167, 164, 85, 169, 167, 80, 173, 171, 75, 176, 175, 71, 181, 179, 67, 185, 182, 63, 189, 186, 61, 192, 190, 57, 197, 194, 53, 200, 198, 49, 204, 202, 45, 209, 205, 40, 213, 209, 36, 216, 213, 32, 220, 217, 28, 223, 221, 24, 228, 225, 20, 232, 229, 16, 235, 233, 14 }; + +const byte colorMap_wheel3[] = { 17, 14, 17, 20, 14, 26, 26, 15, 36, 30, 14, 46, 35, 15, 56, 39, 15, 65, 45, 15, 75, 49, 14, 84, 55, 14, 94, 60, 15, 104, 64, 14, 113, 70, 14, 123, 74, 14, 132, 79, 15, 142, 83, 14, 152, 89, 15, 162, 93, 14, 171, 98, 15, 181, 103, 14, 190, 108, 14, 200, 112, 14, 209, 118, 14, 219, 122, 13, 228, 128, 13, 240, 122, 22, 228, 115, 33, 218, 111, 41, 211, 107, 50, 201, 102, 60, 190, 96, 70, 181, 91, 80, 170, 87, 89, 160, 81, 99, 151, 76, 109, 140, 70, 119, 130, 66, 130, 120, 63, 137, 113, 58, 148, 101, 54, 158, 93, 47, 167, 83, 44, 177, 72, 39, 187, 63, 35, 196, 54, 29, 206, 42, 24, 216, 33, 19, 225, 24, 15, 235, 12, 24, 225, 23, 34, 215, 34, 43, 206, 43, 52, 196, 51, 63, 187, 63, 72, 177, 71, 82, 166, 82, 93, 157, 92, 102, 148, 100, 111, 137, 111, 121, 129, 120, 131, 119, 131, 141, 110, 140, 150, 100, 151, 159, 90, 159, 169, 81, 171, 179, 71, 179, 188, 61, 188, 199, 51, 199, 209, 42, 208, 218, 31, 218, 228, 23, 228, 238, 14, 239, 227, 22, 235, 218, 33, 227, 208, 40, 225, 200, 51, 219, 189, 60, 214, 179, 71, 207, 170, 79, 204, 161, 89, 201, 150, 99, 192, 140, 109, 191, 130, 118, 186, 120, 128, 179, 111, 137, 174, 100, 147, 169, 91, 156, 164, 81, 166, 159, 71, 175, 152, 61, 185, 147, 53, 195, 143, 42, 205, 138, 32, 216, 134, 23, 225, 129, 13, 236, 125, 25, 225, 125, 34, 215, 126, 42, 206, 124, 52, 195, 125, 63, 187, 125, 72, 177, 125, 82, 166, 126, 92, 158, 126, 102, 148, 126, 112, 138, 125, 120, 128, 126, 131, 120, 125, 140, 109, 126, 150, 100, 125, 159, 90, 125, 170, 80, 127, 180, 71, 125, 189, 61, 126, 198, 53, 126, 208, 42, 126, 218, 33, 125, 227, 23, 126, 238, 14, 125, 228, 23, 130, 219, 32, 135, 208, 42, 140, 199, 52, 145, 189, 61, 150, 178, 71, 153, 169, 80, 158, 158, 90, 163, 149, 100, 169, 140, 109, 173, 130, 118, 178, 119, 128, 181, 110, 138, 189, 101, 147, 192, 90, 157, 196, 80, 166, 201, 71, 176, 207, 60, 186, 212, 51, 195, 217, 41, 205, 220, 31, 216, 225, 21, 224, 234, 15, 235, 237, 23, 225, 227, 33, 214, 217, 43, 204, 208, 53, 195, 197, 63, 184, 187, 72, 175, 178, 83, 166, 168, 91, 157, 159, 101, 146, 151, 111, 136, 142, 120, 127, 131, 130, 117, 122, 140, 109, 113, 150, 99, 102, 160, 89, 93, 168, 80, 83, 178, 70, 72, 189, 61, 64, 198, 52, 53, 210, 41, 46, 218, 31, 34, 229, 23, 24, 239, 14, 18, 229, 18, 25, 218, 23, 35, 208, 28, 45, 199, 32, 54, 189, 37, 64, 180, 41, 72, 170, 46, 82, 160, 50, 91, 151, 56, 102, 140, 60, 111, 132, 65, 120, 121, 69, 130, 110, 75, 143, 101, 79, 150, 92, 83, 160, 81, 89, 168, 73, 93, 178, 62, 98, 188, 52, 102, 200, 43, 109, 209, 33, 112, 217, 24, 118, 228, 15, 122, 238, 24, 123, 228, 34, 122, 218, 43, 123, 208, 53, 123, 199, 62, 123, 189, 71, 122, 179, 80, 123, 170, 91, 123, 160, 100, 122, 150, 109, 122, 140, 118, 123, 130, 131, 123, 122, 142, 123, 112, 151, 122, 102, 160, 123, 92, 170, 123, 83, 179, 124, 72, 189, 122, 63, 199, 123, 52, 208, 123, 44, 219, 123, 33, 227, 123, 24, 237, 122, 15, 228, 117, 24, 218, 113, 33, 209, 107, 44, 200, 102, 51, 190, 98, 61, 178, 93, 74, 169, 90, 81, 162, 84, 94, 151, 79, 104, 141, 74, 113, 132, 70, 123, 122, 65, 132, 112, 61, 142, 103, 55, 151, 93, 51, 162, 83, 46, 173, 73, 43, 181, 63, 38, 190, 54, 33, 202, 44, 28, 209, 35, 23, 221, 25, 18, 231, 15, 14, 241, 25, 23, 230, 32, 32, 220, 42, 40, 213, 50, 49, 203, 59, 57, 194, 68, 67, 185, 78, 75, 176, 85, 84, 166, 93, 93, 157, 103, 102, 148, 113, 110, 139, 121, 118, 131, 130, 127, 120, 139, 136, 113, 148, 144, 103, 156, 154, 94, 164, 163, 85, 174, 172, 76, 182, 180, 66, 191, 189, 58, 201, 197, 49, 210, 207, 40, 217, 215, 31, 227, 223, 23, 235, 233, 14 }; + +const byte colorMap_whiteHot[] = { 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 20, 21, 21, 21, 22, 22, 22, 23, 23, 23, 24, 24, 24, 25, 25, 25, 26, 26, 26, 27, 27, 27, 28, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32, 32, 32, 33, 33, 33, 34, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 37, 38, 38, 38, 39, 39, 39, 40, 40, 40, 41, 41, 41, 42, 42, 42, 43, 43, 43, 44, 44, 44, 44, 44, 44, 45, 45, 45, 46, 46, 46, 47, 47, 47, 48, 48, 48, 49, 49, 49, 50, 50, 50, 51, 51, 51, 52, 52, 52, 53, 53, 53, 54, 54, 54, 55, 55, 55, 56, 56, 56, 57, 57, 57, 58, 58, 58, 59, 59, 59, 60, 60, 60, 61, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 65, 65, 65, 66, 66, 66, 67, 67, 67, 68, 68, 68, 69, 69, 69, 70, 70, 70, 71, 71, 71, 72, 72, 72, 73, 73, 73, 74, 74, 74, 75, 75, 75, 76, 76, 76, 77, 77, 77, 78, 78, 78, 79, 79, 79, 80, 80, 80, 81, 81, 81, 82, 82, 82, 83, 83, 83, 84, 84, 84, 85, 85, 85, 86, 86, 86, 87, 87, 87, 88, 88, 88, 89, 89, 89, 90, 90, 90, 91, 91, 91, 92, 92, 92, 93, 93, 93, 94, 94, 94, 95, 95, 95, 96, 96, 96, 97, 97, 97, 98, 98, 98, 99, 99, 99, 99, 99, 99, 100, 100, 100, 101, 101, 101, 102, 102, 102, 103, 103, 103, 104, 104, 104, 105, 105, 105, 106, 106, 106, 107, 107, 107, 108, 108, 108, 109, 109, 109, 110, 110, 110, 111, 111, 111, 112, 112, 112, 113, 113, 113, 114, 114, 114, 115, 115, 115, 116, 116, 116, 117, 117, 117, 118, 118, 118, 119, 119, 119, 120, 120, 120, 121, 121, 121, 122, 122, 122, 123, 123, 123, 124, 124, 124, 125, 125, 125, 126, 126, 126, 127, 127, 127, 128, 128, 128, 129, 129, 129, 130, 130, 130, 131, 131, 131, 132, 132, 132, 133, 133, 133, 134, 134, 134, 135, 135, 135, 136, 136, 136, 137, 137, 137, 138, 138, 138, 139, 139, 139, 140, 140, 140, 141, 141, 141, 142, 142, 142, 143, 143, 143, 144, 144, 144, 145, 145, 145, 146, 146, 146, 147, 147, 147, 148, 148, 148, 149, 149, 149, 150, 150, 150, 151, 151, 151, 152, 152, 152, 153, 153, 153, 154, 154, 154, 154, 154, 154, 155, 155, 155, 156, 156, 156, 157, 157, 157, 158, 158, 158, 159, 159, 159, 160, 160, 160, 161, 161, 161, 162, 162, 162, 163, 163, 163, 164, 164, 164, 165, 165, 165, 166, 166, 166, 167, 167, 167, 168, 168, 168, 169, 169, 169, 170, 170, 170, 171, 171, 171, 172, 172, 172, 173, 173, 173, 174, 174, 174, 175, 175, 175, 176, 176, 176, 177, 177, 177, 178, 178, 178, 179, 179, 179, 180, 180, 180, 181, 181, 181, 182, 182, 182, 183, 183, 183, 184, 184, 184, 185, 185, 185, 186, 186, 186, 187, 187, 187, 188, 188, 188, 189, 189, 189, 190, 190, 190, 191, 191, 191, 192, 192, 192, 193, 193, 193, 194, 194, 194, 195, 195, 195, 196, 196, 196, 197, 197, 197, 198, 198, 198, 199, 199, 199, 200, 200, 200, 201, 201, 201, 202, 202, 202, 203, 203, 203, 204, 204, 204, 205, 205, 205, 206, 206, 206, 207, 207, 207, 208, 208, 208, 209, 209, 209, 209, 209, 209, 210, 210, 210, 211, 211, 211, 212, 212, 212, 213, 213, 213, 214, 214, 214, 215, 215, 215, 216, 216, 216, 217, 217, 217, 218, 218, 218, 219, 219, 219, 220, 220, 220, 221, 221, 221, 222, 222, 222, 223, 223, 223, 224, 224, 224, 225, 225, 225, 226, 226, 226, 227, 227, 227, 228, 228, 228, 229, 229, 229, 230, 230, 230, 231, 231, 231, 232, 232, 232, 233, 233, 233, 234, 234, 234, 235, 235, 235 }; + +const byte colorMap_yellow[] = { 62, 16, 15, 63, 17, 16, 61, 18, 15, 62, 19, 16, 61, 20, 16, 61, 22, 15, 59, 22, 15, 60, 23, 16, 60, 23, 16, 60, 25, 15, 60, 25, 15, 61, 26, 16, 59, 27, 16, 58, 28, 14, 59, 29, 15, 59, 31, 14, 59, 31, 14, 60, 32, 15, 61, 34, 15, 59, 34, 15, 60, 36, 14, 60, 37, 15, 60, 37, 15, 61, 39, 14, 61, 39, 14, 60, 40, 15, 61, 42, 14, 61, 42, 14, 62, 43, 15, 63, 44, 15, 63, 46, 16, 64, 47, 15, 64, 47, 15, 65, 49, 14, 65, 50, 15, 66, 52, 14, 66, 52, 14, 68, 52, 15, 68, 54, 15, 68, 54, 15, 69, 55, 14, 70, 56, 15, 71, 57, 16, 72, 57, 14, 72, 59, 15, 73, 60, 14, 75, 61, 13, 76, 62, 14, 78, 64, 15, 78, 64, 15, 79, 65, 14, 80, 66, 15, 82, 67, 14, 83, 68, 15, 84, 69, 14, 84, 69, 14, 85, 71, 14, 88, 71, 15, 89, 72, 14, 90, 73, 15, 93, 75, 15, 94, 76, 14, 95, 78, 13, 98, 78, 14, 99, 79, 14, 100, 80, 15, 102, 81, 14, 103, 82, 15, 105, 82, 14, 106, 84, 13, 107, 85, 14, 110, 85, 14, 110, 85, 14, 113, 87, 14, 114, 88, 15, 117, 89, 14, 119, 91, 14, 120, 92, 14, 122, 93, 15, 123, 94, 14, 125, 95, 13, 126, 96, 14, 129, 96, 13, 130, 97, 14, 132, 98, 13, 133, 99, 13, 136, 100, 14, 138, 100, 13, 139, 101, 14, 141, 102, 13, 144, 105, 14, 147, 106, 14, 148, 107, 13, 150, 107, 14, 153, 108, 13, 154, 109, 14, 156, 110, 13, 157, 111, 12, 158, 112, 13, 160, 113, 13, 161, 114, 14, 164, 114, 13, 166, 115, 12, 167, 116, 13, 170, 116, 12, 172, 119, 13, 174, 119, 12, 176, 121, 14, 178, 122, 13, 179, 123, 14, 182, 124, 13, 183, 125, 13, 185, 125, 14, 185, 126, 12, 186, 127, 13, 189, 127, 12, 190, 128, 13, 192, 129, 12, 194, 131, 12, 195, 132, 13, 196, 134, 13, 199, 135, 13, 202, 136, 14, 203, 137, 13, 203, 137, 13, 205, 138, 12, 206, 139, 12, 207, 140, 13, 208, 141, 12, 209, 142, 13, 209, 142, 13, 212, 143, 12, 213, 144, 13, 215, 146, 13, 215, 147, 12, 217, 149, 12, 219, 149, 13, 220, 151, 12, 221, 152, 13, 221, 152, 11, 222, 153, 12, 223, 154, 13, 224, 155, 12, 224, 155, 12, 225, 157, 12, 226, 158, 13, 227, 159, 12, 228, 160, 13, 228, 160, 11, 229, 161, 12, 230, 163, 11, 231, 164, 12, 231, 166, 12, 231, 166, 12, 232, 167, 13, 233, 168, 12, 233, 168, 12, 234, 169, 13, 232, 170, 11, 233, 171, 12, 233, 171, 12, 234, 174, 12, 234, 174, 12, 235, 175, 11, 233, 176, 11, 235, 179, 12, 235, 179, 12, 235, 180, 13, 235, 181, 11, 236, 182, 12, 235, 182, 12, 236, 183, 13, 234, 184, 11, 235, 185, 12, 235, 187, 11, 235, 187, 11, 233, 188, 11, 234, 189, 12, 233, 190, 11, 234, 191, 12, 234, 193, 13, 234, 193, 11, 232, 194, 11, 232, 196, 12, 232, 196, 12, 231, 197, 13, 231, 198, 11, 231, 199, 12, 231, 199, 12, 231, 201, 11, 229, 202, 11, 230, 203, 12, 229, 204, 12, 229, 204, 11, 228, 206, 12, 227, 207, 12, 227, 208, 13, 225, 209, 11, 226, 210, 12, 225, 211, 12, 223, 212, 12, 223, 212, 12, 223, 214, 11, 223, 216, 12, 223, 216, 12, 221, 217, 10, 222, 218, 11, 221, 218, 11, 220, 220, 12, 220, 220, 12, 219, 223, 11, 219, 223, 11, 217, 224, 11, 217, 225, 12, 217, 226, 11, 215, 226, 11, 215, 228, 12, 216, 229, 11, 215, 230, 11, 215, 230, 11, 214, 232, 12, 213, 233, 12, 213, 233, 10, 212, 235, 11 }; diff --git a/Firmware_V3/src/general/globalvariables.cpp b/Firmware_V3/src/general/globalvariables.cpp new file mode 100644 index 0000000..bc01987 --- /dev/null +++ b/Firmware_V3/src/general/globalvariables.cpp @@ -0,0 +1,151 @@ +/* +* +* GLOBAL VARIABLES - Global variable declarations, that are used firmware-wide +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include + +/*############################# PUBLIC VARIABLES ##############################*/ + +//Current firmware version +char versionString[] = "Firmware 3.00 from 01.05.2021"; +uint16_t fwVersion = 300; + +//320x240 buffer +unsigned short* bigBuffer; +//160x120 buffer +unsigned short* smallBuffer; + +//Timer +Metro screenOff; +boolean screenPressed; +byte screenOffTime; + +//Button Debouncer +Bounce buttonDebouncer(pin_button, 200); +Bounce touchDebouncer(pin_touch_irq, 200); + +//SD +SdFat32 sd; +File32 sdFile; +String sdInfo; +File32 dir; + +//Save filename +char saveFilename[20]; + +//Battery ADC +ADC *batMeasure; +//Battery +int8_t batPercentage; +long batTimer; +int8_t batComp; +bool usbConnected; + +//Convert RAW to BMP +bool convertEnabled; +//Automatic mode +bool autoMode; +//Lock current limits +bool limitsLocked; +//Vertical display rotation +bool rotationVert; +bool rotationHorizont; + +//Display options +bool batteryEnabled; +bool timeEnabled; +bool dateEnabled; +bool spotEnabled; +bool colorbarEnabled; +bool storageEnabled; +byte filterType; +byte minMaxPoints; + +//Temperature format +bool tempFormat; + +//Text color +byte textColor; + +//Lepton Gain mode +bool leptonGainMode; + +//Calibration slope +float leptonCalSlope; + +//FLIR Lepton sensor version +byte leptonVersion; + +//HW diagnostic information +byte diagnostic = diag_ok; + +//Current color scheme - standard is rainbow +byte colorScheme; +//Pointer to the current color scheme +const byte *colorMap; +//Number of rgb elements inside the color scheme +int16_t colorElements; + +//Min & max lepton raw values +uint16_t maxValue; +uint16_t minValue; + +//Spot & ambient temperature +float spotTemp; +float ambTemp; + +//Position of min and maxtemp +uint16_t minTempPos; +uint16_t minTempVal; +uint16_t maxTempPos; +uint16_t maxTempVal; + +//Hot / Cold mode +byte hotColdMode; +int16_t hotColdLevel; +byte hotColdColor; + +//Array to store the tempPoints +uint16_t tempPoints[96][2]; + +//Adjust combined image +float adjCombAlpha; +float adjCombFactor; +byte adjCombLeft; +byte adjCombRight; +byte adjCombUp; +byte adjCombDown; + +//Save Image in the next cycle +volatile byte imgSave; +//Save Video in the next cycle +volatile byte videoSave; +//Show Live Mode Menu in the next cycle +volatile byte showMenu; +//Handler for a long touch press +volatile bool longTouch; +//Check if in serial mode +volatile bool serialMode; +//Load touch decision marker +volatile byte loadTouch; +//Current buffer valid +volatile bool leptonBufferValid; diff --git a/Firmware_V3/src/gui/bitmaps.cpp b/Firmware_V3/src/gui/bitmaps.cpp new file mode 100644 index 0000000..30f279d --- /dev/null +++ b/Firmware_V3/src/gui/bitmaps.cpp @@ -0,0 +1,1370 @@ +/* +* +* BITMAPS - Icons and graphics shown inside the GUI +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include + +/*############################# PUBLIC VARIABLES ##############################*/ + +const uint16_t iconChangeColorPalette[] = { + 0xDBE5, 0xDC89, 0xFFFF, 0xFFDF +}; + +const uint8_t iconChangeColorBMP[] = { + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xFF, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAF, 0x54, 0x00, 0x01, 0x7E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0xD0, 0x00, 0x00, 0x00, 0x01, 0x7A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xCE, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0x06, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7A, 0xAA, 0xAA, 0xAA, 0xAA, 0xB4, 0x03, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xAA, 0xAA, 0xAA, 0xAF, 0x40, 0x03, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, 0xAA, 0xAA, 0xD0, 0x00, 0x01, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAA, 0xA9, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xAA, 0xB4, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x00, 0x01, 0xEA, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xEA, 0x90, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0xAA, 0xAA, 0xC0, 0x00, 0x00, 0x03, 0xAA, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7A, 0xC0, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0xAA, 0xAB, 0x00, 0x00, 0x00, 0x06, 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x40, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x0E, 0xAA, 0xAC, 0x00, 0x00, 0x5D, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0xAA, 0xB4, 0x00, 0x00, 0x00, 0x0E, 0xAA, 0xAC, 0x00, 0x01, 0xAA, 0xD0, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0xAA, + 0xAA, 0xAA, 0x90, 0x00, 0x00, 0x00, 0x06, 0xAA, 0xA4, 0x00, 0x06, 0xAA, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, + 0xAA, 0xAA, 0xC0, 0x00, 0x00, 0x00, 0x03, 0xAA, 0xB0, 0x00, 0x0E, 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, + 0xAA, 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0xEA, 0xC0, 0x00, 0x0E, 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0xAA, + 0xAA, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x0E, 0xAA, 0xAC, 0x00, 0x00, 0x04, 0x00, 0x00, 0x3A, 0xAA, + 0xAA, 0xA9, 0x00, 0x01, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, 0xA4, 0x00, 0x00, 0x39, 0x00, 0x01, 0xEA, 0xAA, + 0xAA, 0xAC, 0x00, 0x06, 0xAA, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xAA, 0x90, 0x00, 0x00, 0x6A, 0xD0, 0x1E, 0xAA, 0xAA, + 0xAA, 0xA4, 0x00, 0x0E, 0xAA, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x40, 0x00, 0x01, 0xEA, 0xC0, 0x3A, 0xAA, 0xAA, + 0xAA, 0xB0, 0x00, 0x1A, 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xAA, 0x40, 0x6A, 0xAA, 0xAA, + 0xAA, 0x90, 0x00, 0x1A, 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0xAB, 0x01, 0xEA, 0xAA, 0xAA, + 0xAA, 0x90, 0x00, 0x1A, 0xAA, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAD, 0x03, 0xAA, 0xAA, 0xAA, + 0xAA, 0x80, 0x00, 0x0E, 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0xA4, 0x06, 0xAA, 0xAA, 0xAA, + 0xAA, 0xC0, 0x00, 0x07, 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, 0xB0, 0x0E, 0xAA, 0xAA, 0xAA, + 0xAA, 0xC0, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, 0x90, 0x1A, 0xAA, 0xAA, 0xAA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xAA, 0x40, 0x3A, 0xAA, 0xAA, 0xAA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0xAB, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xA9, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7A, 0xA4, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, 0xB0, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, 0xD0, 0x00, 0xEA, 0xAA, 0xAA, 0xAA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xAA, 0x40, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0x40, 0x00, 0x07, 0xAD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAB, 0x00, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0x40, 0x00, 0x1E, 0xAB, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAC, 0x00, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xC0, 0x00, 0x3A, 0xAA, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0xA4, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xC0, 0x00, 0x6A, 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0x90, 0x00, 0x7A, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0x80, 0x00, 0x6A, 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x7D, 0x00, 0xEA, 0xC0, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0x90, 0x00, 0x2A, 0xAA, 0xC0, 0x00, 0x00, 0x00, 0x03, 0xA9, 0x01, 0xAB, 0x00, 0x1E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xB0, 0x00, 0x3A, 0xAA, 0x40, 0x00, 0x00, 0x00, 0x0E, 0xAC, 0x03, 0xAD, 0x01, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xB0, 0x00, 0x0E, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xA4, 0x06, 0xA4, 0x07, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA4, 0x00, 0x01, 0x54, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xB0, 0x1E, 0x90, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x90, 0x1A, 0x40, 0x3A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xC0, 0x3B, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x40, 0x6C, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAB, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x40, 0xF0, 0x07, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x01, 0xC0, 0x1E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x01, 0x00, 0x7A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x01, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x1E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE9, 0x00, 0x7A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAB, 0x47, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x07, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAB, 0x40, 0x00, 0x00, 0x00, 0x1E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xD0, 0x00, 0x00, 0x00, 0x7A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAD, 0x00, 0x00, 0x07, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xF5, 0x01, 0x7A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA +}; + +const uint16_t iconTempLimitsPalette[] = { + 0x7ACB, 0x8BAF, 0x93F0, 0xFFDF +}; + +const uint8_t iconTempLimitsBMP[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA5, 0x5B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x02, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x02, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xAA, 0xAB, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x06, 0x40, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x2F, 0xE0, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, 0xFF, 0xFF, 0xEA, 0xAA, 0xAA, 0xAF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x03, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x6A, 0xAA, 0x6B, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xF8, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0xFF, 0xFF, 0xE0, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0x80, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x0B, 0xFF, 0xFF, 0xAF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xE9, 0x65, 0x55, 0x40, 0x00, 0x2F, 0xFF, 0xFE, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xF8, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xE0, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0x80, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFE, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xF8, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xEA, 0xAA, 0xAA, 0xAA, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x90, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFA, 0xBF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0xBF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0xBF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0xBF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0xBF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0xBF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xAA, 0xA0, 0x6A, 0xAF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0xBF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0xBF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x40, 0x06, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +const uint16_t iconLoadMenuPalette[] = { + 0xDBE5, 0xDCAA, 0xFFDF, 0xFFDF +}; + +const uint8_t iconLoadMenuBMP[] = { + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xB5, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7A, 0xAA, + 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAA, + 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0x01, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x40, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0x00, 0x0A, 0xAA, + 0xAA, 0xA0, 0x00, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0x00, 0x0A, 0xAA, + 0xAA, 0xA5, 0x55, 0xEA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0x55, 0x5A, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAB, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xEA, + 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, + 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, + 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, + 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, + 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, + 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, + 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, + 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, + 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, + 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, + 0xAA, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, + 0xAA, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xAA, + 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xAA, + 0xAA, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xAA, + 0xAA, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, + 0xAA, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, + 0xAA, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, + 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, + 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, + 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, + 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, + 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0xAA, + 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xAA, + 0xAA, 0xAB, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7A, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA +}; + +const uint16_t iconShutterPalette[] = { + 0x4249, 0x9CF3, 0xCE9A, 0xFFFF +}; + +const uint8_t iconShutterBMP[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0x54, 0x16, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x02, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x02, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x03, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x07, 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x2F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0B, 0xFD, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x0B, 0xFC, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, + 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x0B, 0xFC, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFC, 0x00, 0x02, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFC, 0x00, 0x3F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFC, 0x07, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFC, 0xBF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, + 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, + 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, + 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, + 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x03, 0xFF, + 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x03, 0xFF, + 0xFF, 0xCF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x02, 0xFF, + 0xFF, 0x82, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x01, 0xFF, + 0xFF, 0x80, 0x3F, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x40, 0x0B, 0xFF, 0xF4, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x40, 0x01, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xC0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x0B, 0xFF, 0xF8, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x0B, 0xFF, 0xF8, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x3F, 0xFF, 0xD0, 0x00, 0xFF, + 0xFF, 0x40, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x07, 0xFF, 0xFC, 0x00, 0xFF, + 0xFF, 0x80, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0x40, 0xFF, + 0xFF, 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x2F, 0xFF, 0xE2, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, + 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, + 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFE, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, + 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xE0, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFD, 0x00, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xD0, 0x00, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, + 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x3F, 0xE0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x7F, 0xD0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0xBF, 0xC0, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFD, 0x00, 0x07, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xC0, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0xBF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xC0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xCB, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0x40, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0x40, 0x01, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +const uint16_t iconSettingsMenuPalette[] = { + 0x7AEC, 0x8B6E, 0x93F0, 0xFFDF +}; + +const uint8_t iconSettingsMenuBMP[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xAA, 0xAB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xD1, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x8B, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x2F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFD, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x06, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xE0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, + 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, + 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x1F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x1B, 0xFF, 0xE0, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0xBF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xF8, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xE6, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0x4B, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +const uint16_t iconDisplaySettingsPalette[] = { + 0xD3E5, 0xDC89, 0xFFDF, 0xFFDF +}; + +const uint8_t iconDisplaySettingsBMP[] = { + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAB, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xEA, 0xAA, + 0xAA, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0xAA, + 0xAA, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xAA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, + 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, + 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xA9, 0x00, 0x05, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x50, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAF, 0xFA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x90, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x90, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xB5, 0xAA, 0xC0, 0x03, 0xAA, 0xDE, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xC0, 0x7D, 0x00, 0x00, 0x7D, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xC0, 0x00, 0x5F, 0xF5, 0x00, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0x90, 0x01, 0xAA, 0xAA, 0x40, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xC0, 0x0E, 0xAA, 0xAA, 0xB0, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0x40, 0x1A, 0xAA, 0xAA, 0xA4, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xBF, 0x40, 0x3A, 0xAA, 0xAA, 0xAD, 0x00, 0xFA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x6A, 0xAA, 0xAA, 0xA9, 0x00, 0x05, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x6A, 0xAA, 0xAA, 0xAB, 0x00, 0x01, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0xEA, 0xAA, 0xAA, 0xAB, 0x00, 0x01, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0xEA, 0xAA, 0xAA, 0xAB, 0x00, 0x01, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x6A, 0xAA, 0xAA, 0xA9, 0x00, 0x01, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xF5, 0x00, 0x6A, 0xAA, 0xAA, 0xA9, 0x00, 0x5F, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0x40, 0x3A, 0xAA, 0xAA, 0xAC, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xC0, 0x1E, 0xAA, 0xAA, 0xB4, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0x90, 0x07, 0xAA, 0xAA, 0xD0, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0x90, 0x00, 0x7A, 0xAF, 0x00, 0x07, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x15, 0x54, 0x00, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0x40, 0x14, 0x00, 0x00, 0x14, 0x01, 0xEA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0x91, 0xEB, 0x40, 0x01, 0xEB, 0x46, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAF, 0xAA, 0x80, 0x06, 0xAA, 0xFA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x90, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x90, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB5, 0x5E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xB0, 0x00, 0x6A, + 0xA9, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x6A, + 0xA9, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x6A, + 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, + 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, + 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, + 0xAA, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xAA, + 0xAA, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0xAA, + 0xAA, 0xAB, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x50, 0x00, 0x00, 0x05, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xEA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x0E, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xBF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFE, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAB, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xEA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAB, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xEA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA +}; + +const uint16_t iconDisplayOffPalette[] = { + 0x7AEC, 0x8B8E, 0x93F0, 0xFFDF +}; + +const uint8_t iconDisplayOffBMP[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xFF, 0xFF, + 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, + 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, + 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFE, 0x00, 0x0A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x2F, 0xC0, 0x2F, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x2F, 0xC0, 0x2F, 0xC0, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x2F, 0xC0, 0x2F, 0xC0, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x2F, 0xC0, 0x2F, 0xC0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x3F, 0xC0, 0x2F, 0xE0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0xBF, 0xD0, 0x2F, 0xF4, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFC, 0x02, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFC, 0x02, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x7F, 0xFF, 0xFF, 0xF8, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x2F, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xFF, 0x80, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0xBF, 0xF8, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0xAF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xBF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, + 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, + 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, + 0xFF, 0xFF, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA0, 0x00, 0x00, 0x0A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +const uint16_t iconHotColdPalette[] = { + 0x4249, 0x7BF0, 0x7C10, 0xFFDF +}; + +const uint8_t iconHotColdBMP[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x2F, 0xFD, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x2F, 0xF0, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x2F, 0xE0, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x2F, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xF0, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x0B, 0xE0, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x02, 0x80, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xE0, 0x00, 0x00, 0x0F, 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xD0, 0x00, 0x00, 0x2F, 0x80, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0xBF, 0x80, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xC0, 0x18, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xC0, 0x2C, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x40, 0x00, 0x07, 0xFF, 0xE0, 0x3E, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x40, 0x00, 0x0F, 0xFF, 0xF0, 0xBF, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x40, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0xBF, 0xFF, 0xFE, 0x1F, 0xE0, 0x3F, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xC0, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xBF, 0xFF, 0xFC, 0x03, 0xE0, 0x3E, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0xFC, 0x00, 0xE0, 0x3C, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xD0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0xFF, 0xF9, 0xFE, 0x00, 0x10, 0x10, 0x03, 0xF9, 0xBF, 0xFF, 0xFF, + 0xFF, 0xE0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x02, 0xFF, 0xE0, 0x3F, 0x80, 0x00, 0x00, 0x0F, 0xE0, 0x3F, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xE0, 0x2F, 0xE0, 0x00, 0x00, 0x3F, 0xD0, 0x3F, 0xFF, 0xFF, + 0xFF, 0xFC, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xF0, 0x2F, 0xF8, 0x00, 0x00, 0xFF, 0xC0, 0x3F, 0xFF, 0xFF, + 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x40, 0x2F, 0xEF, 0xF0, 0x0F, 0xFE, 0x00, 0x03, 0xFF, 0xC0, 0x7F, 0xAF, 0xFF, + 0xFF, 0xFF, 0x40, 0x1F, 0xFF, 0xFF, 0xFD, 0x00, 0x7F, 0x02, 0xF4, 0x0F, 0xFF, 0x80, 0x0F, 0xFF, 0x80, 0xBD, 0x0B, 0xFF, + 0xFF, 0xFF, 0xD0, 0x07, 0xFF, 0xFF, 0xF4, 0x02, 0xFF, 0x00, 0x28, 0x0B, 0xFF, 0xE0, 0x3F, 0xFF, 0x40, 0xD0, 0x03, 0xFF, + 0xFF, 0xFF, 0xF8, 0x00, 0x6F, 0xFE, 0x40, 0x0B, 0xFF, 0x00, 0x00, 0x07, 0xFF, 0xE0, 0x3F, 0xFF, 0x00, 0x00, 0x0B, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x40, 0x00, 0x7F, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xE0, 0x3F, 0xFF, 0x00, 0x00, 0x2F, 0xFF, + 0xFF, 0xFF, 0xFF, 0xE4, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFD, 0x00, 0x03, 0xFF, 0xE0, 0x3F, 0xFE, 0x00, 0x02, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xA9, 0x5A, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0xBF, 0x80, 0x0B, 0xF4, 0x00, 0x0B, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA0, 0x00, 0x00, 0x18, 0x00, 0x02, 0x80, 0x00, 0x00, 0x6F, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x02, 0xF4, 0x00, 0x0B, 0x00, 0x00, 0xB9, 0x00, 0x01, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x02, 0xBF, 0xFE, 0x40, 0x7F, 0xE0, 0x0B, 0xFF, 0xE9, 0x02, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0xFF, 0xFF, 0x80, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xEF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0xBF, 0xF4, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x0A, 0xFF, 0xFF, 0x80, 0xBF, 0xF0, 0x0B, 0xFF, 0xFE, 0x43, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x0B, 0xF8, 0x00, 0x0B, 0x80, 0x00, 0xFE, 0x40, 0x01, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x04, 0x00, 0x00, 0x40, 0x00, 0x00, 0x1B, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x7F, 0x40, 0x0B, 0xE0, 0x00, 0x06, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x02, 0xFF, 0xD0, 0x2F, 0xFD, 0x00, 0x07, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x03, 0xFF, 0xE0, 0x3F, 0xFE, 0x00, 0x00, 0xBF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xE0, 0x3F, 0xFF, 0x00, 0x00, 0x0B, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x18, 0x0B, 0xFF, 0xE0, 0x3F, 0xFF, 0x40, 0x80, 0x03, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xB8, 0x0F, 0xFF, 0xC0, 0x2F, 0xFF, 0x80, 0xF8, 0x07, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8B, 0xF0, 0x0F, 0xFF, 0x00, 0x07, 0xFF, 0x80, 0x7F, 0x5F, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x1F, 0xFC, 0x00, 0x01, 0xFF, 0xC0, 0x3F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x2F, 0xF0, 0x00, 0x00, 0xBF, 0xD0, 0x3F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x3F, 0xC0, 0x00, 0x00, 0x1F, 0xE0, 0x3F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xBF, 0x00, 0x00, 0x00, 0x07, 0xF4, 0xBF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0xE0, 0x38, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x02, 0xE0, 0x3E, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x0F, 0xE0, 0x3F, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +const uint16_t iconTempPointsPalette[] = { + 0x72AB, 0x8B6E, 0x93F0, 0xFFBE +}; + +const uint8_t iconTempPointsBMP[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF0, 0x69, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xC2, 0xFF, 0x82, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x8B, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x0F, 0xFF, 0xF8, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x1F, 0xFF, 0xFC, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xFE, 0xA8, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x90, 0x1B, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xF0, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x0A, 0x81, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xFA, 0xA8, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x7F, 0xF4, 0xBF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xFF, 0xFC, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFD, 0x2F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xFF, 0xF8, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE2, 0xFF, 0xFE, 0x2F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xF0, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xF9, 0x54, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFF, 0xAA, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xFF, 0xFC, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFD, 0x00, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xFF, 0xFC, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFF, 0xAA, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xF9, 0x54, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xF4, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFF, 0xEE, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xFF, 0xF8, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFD, 0x00, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0xFF, 0xFC, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFE, 0xAA, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0x92, 0xFC, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2F, 0x00, 0xBC, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2E, 0x00, 0x7C, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFE, 0x00, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2E, 0x00, 0x7C, 0xBF, 0xFF, 0xFF, 0xFF, 0xEA, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xFE, 0x55, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2E, 0x00, 0x7C, 0xBF, 0xFF, 0xFF, 0xFE, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xD3, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2E, 0x00, 0x7C, 0xBF, 0xFF, 0xFF, 0xF4, 0x2A, 0x43, 0xFF, 0xFF, 0xFF, 0xD3, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2E, 0x00, 0x7C, 0xBF, 0xFF, 0xFF, 0xD2, 0xFF, 0xE0, 0xFF, 0xFF, 0xFF, 0xD3, 0xE0, 0x2F, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2E, 0x00, 0x7C, 0xBF, 0xFF, 0xFF, 0x87, 0xFF, 0xF8, 0xBF, 0xFF, 0xFF, 0xD3, 0xD0, 0x0F, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2E, 0x00, 0x7C, 0xBF, 0xFF, 0xFF, 0x8B, 0xFF, 0xFC, 0x7F, 0xFF, 0xFF, 0xD3, 0xD0, 0x0F, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFE, 0x1E, 0x00, 0x7C, 0x7F, 0xFF, 0xFF, 0x4F, 0xFF, 0xFC, 0x3F, 0xFF, 0xFF, 0xD3, 0xD0, 0x0F, 0x1F, 0xFF, 0xFF, + 0xFF, 0xF8, 0x2F, 0x00, 0x7E, 0x1F, 0xFF, 0xFF, 0x4F, 0xFE, 0xA8, 0x3F, 0xFF, 0xFF, 0xD3, 0xD0, 0x0F, 0x1F, 0xFF, 0xFF, + 0xFF, 0xF0, 0xFE, 0x00, 0x2F, 0x8B, 0xFF, 0xFF, 0x4F, 0xF4, 0x00, 0x3F, 0xFF, 0xFF, 0xD3, 0xD0, 0x0F, 0x1F, 0xFF, 0xFF, + 0xFF, 0xD2, 0xF0, 0x00, 0x0B, 0xD3, 0xFF, 0xFF, 0x4F, 0xFE, 0xA8, 0x3F, 0xFF, 0xFF, 0xD3, 0xD0, 0x0F, 0x1F, 0xFF, 0xFF, + 0xFF, 0xC7, 0xD0, 0x00, 0x02, 0xE2, 0xFF, 0xFF, 0x4F, 0xFF, 0xFD, 0x3F, 0xFF, 0xFF, 0xD3, 0xD0, 0x0F, 0x1F, 0xFF, 0xFF, + 0xFF, 0x8B, 0x80, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0x4F, 0xFF, 0xB8, 0x3F, 0xFF, 0xFF, 0xD3, 0xD0, 0x0F, 0x1F, 0xFF, 0xFF, + 0xFF, 0x8F, 0x40, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0x4F, 0xF4, 0x00, 0x3F, 0xFF, 0xFF, 0x83, 0xD0, 0x0F, 0x0B, 0xFF, 0xFF, + 0xFF, 0x8F, 0x40, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0x4F, 0xFA, 0xA4, 0x3F, 0xFF, 0xFE, 0x0B, 0xC0, 0x0F, 0xC2, 0xFF, 0xFF, + 0xFF, 0x8F, 0x40, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0x4F, 0xFF, 0xFD, 0x3F, 0xFF, 0xFC, 0x2F, 0x40, 0x07, 0xF0, 0xFF, 0xFF, + 0xFF, 0x8F, 0x40, 0x00, 0x00, 0xF8, 0xFF, 0xFF, 0x4F, 0xFF, 0xFD, 0x3F, 0xFF, 0xF8, 0xBC, 0x00, 0x00, 0xF8, 0xBF, 0xFF, + 0xFF, 0x8B, 0x80, 0x00, 0x01, 0xF0, 0xFF, 0xFF, 0x4F, 0xF9, 0x54, 0x3F, 0xFF, 0xF4, 0xF4, 0x00, 0x00, 0x7C, 0x3F, 0xFF, + 0xFF, 0xC7, 0xD0, 0x00, 0x02, 0xE2, 0xFF, 0xFF, 0x4F, 0xF8, 0x00, 0x3F, 0xFF, 0xF2, 0xF0, 0x00, 0x00, 0x2E, 0x3F, 0xFF, + 0xFF, 0xE2, 0xF4, 0x00, 0x0B, 0xC3, 0xFF, 0xFF, 0x4F, 0xFF, 0xFD, 0x3F, 0xFF, 0xE2, 0xE0, 0x00, 0x00, 0x2E, 0x2F, 0xFF, + 0xFF, 0xF0, 0xBE, 0x40, 0xBF, 0x4B, 0xFF, 0xFF, 0x4F, 0xFF, 0xFD, 0x3F, 0xFF, 0xE2, 0xE0, 0x00, 0x00, 0x2E, 0x2F, 0xFF, + 0xFF, 0xF8, 0x2F, 0xFF, 0xFD, 0x1F, 0xFF, 0xFF, 0x4F, 0xD1, 0xBD, 0x3F, 0xFF, 0xE2, 0xE0, 0x00, 0x00, 0x2E, 0x2F, 0xFF, + 0xFF, 0xFE, 0x06, 0xFF, 0xE0, 0xBF, 0xFF, 0xFF, 0x4F, 0x40, 0x3D, 0x3F, 0xFF, 0xF2, 0xF0, 0x00, 0x00, 0x2E, 0x3F, 0xFF, + 0xFF, 0xFF, 0xE0, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0x4F, 0x40, 0x3D, 0x3F, 0xFF, 0xF4, 0xF4, 0x00, 0x00, 0x7C, 0x3F, 0xFF, + 0xFF, 0xFF, 0xFE, 0x55, 0xAF, 0xFF, 0xFF, 0xFF, 0x4F, 0x40, 0x3D, 0x3F, 0xFF, 0xF8, 0xBC, 0x00, 0x00, 0xF8, 0xBF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0x40, 0x3D, 0x3F, 0xFF, 0xFC, 0x2F, 0x40, 0x07, 0xF0, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0x40, 0x3D, 0x3F, 0xFF, 0xFE, 0x0B, 0xFA, 0xBF, 0xC2, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0x40, 0x3D, 0x3F, 0xFF, 0xFF, 0x82, 0xFF, 0xFE, 0x0B, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0x40, 0x3D, 0x3F, 0xFF, 0xFF, 0xE0, 0x2A, 0xA0, 0x2F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0x40, 0x3D, 0x3F, 0xFF, 0xFF, 0xFE, 0x40, 0x06, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x40, 0x3D, 0x2F, 0xFF, 0xFF, 0xFF, 0xFE, 0xBF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x2F, 0x40, 0x3E, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0xBE, 0x00, 0x2F, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE2, 0xF8, 0x00, 0x07, 0xE2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xE0, 0x00, 0x02, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0xC0, 0x00, 0x00, 0xB8, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8B, 0x80, 0x00, 0x00, 0xB8, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8B, 0x80, 0x00, 0x00, 0x7C, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8B, 0x80, 0x00, 0x00, 0x7C, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8B, 0x80, 0x00, 0x00, 0xB8, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0xC0, 0x00, 0x00, 0xB8, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xE0, 0x00, 0x02, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, 0xF8, 0x00, 0x07, 0xE2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0xBE, 0x40, 0x2F, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x2F, 0xFF, 0xFE, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x06, 0xFF, 0xE4, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x04, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x40, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +const uint16_t iconBWColors[] = { + 0x9CC, 0x63F3, 0x7475, 0xFFFF +}; + +const uint8_t iconBWBitmap[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x01, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +const uint16_t iconReturnColors[] = { + 0xA22B, 0xBBD0, 0xFFDF, 0x0 +}; + +const uint8_t iconReturnBitmap[] = { + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0x1A, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA9, 0x56, 0xAA, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x06, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x01, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xA5, 0x55, 0x55, 0x55, 0x55, 0x54, 0x00, 0x00, 0x6A, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x01, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x06, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x1A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA9, 0x56, 0xAA, 0x01, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0x06, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0x1A, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xA8, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x55, 0x55, 0x55, 0x54, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x55, 0x55, 0x55, 0x55, 0x56, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x40, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x40, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA4, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x6A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA +}; + +const uint16_t iconFWColors[] = { + 0x9CC, 0x5371, 0x7495, 0xFFFF +}; + +const uint8_t iconFWBitmap[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF9, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x64, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +const uint16_t logoColors[] = { + 0x2C56, 0x44B7, 0x6538, 0xEA06, 0xF32B, 0xF40F, 0x85DA, 0x961A, 0xAE7B, 0xB69C, 0xF534, 0xFE17, 0xC6FD, 0xD75D, 0xFE99, 0xFFFF +}; + +const uint8_t logoBitmap[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x20, 0x00, 0x00, 0x02, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xCF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9F, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x18, 0xFF, 0xFF, 0x81, 0x00, 0x00, 0x00, 0x00, 0x1F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x01, 0xDF, 0xFF, 0xFF, 0xFD, 0x10, 0x00, 0x00, 0x00, 0x0C, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x70, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x07, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x02, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xD6, 0x66, 0x66, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0x82, 0x22, 0x21, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xD6, 0x66, 0x66, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0x72, 0x22, 0x21, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xD7, 0x66, 0x66, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x62, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x22, 0x22, 0x22, 0x26, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0x43, 0x3B, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFA, 0x33, 0x33, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x7D, 0xDD, 0xDD, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xDD, 0xDD, 0xD7, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x06, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0x60, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x09, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0x90, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x70, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xCF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x9F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDC, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x82, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x28, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xCD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xCF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, + 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, + 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6F, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xFF, + 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x02, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x79, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x97, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x20, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, + 0xF9, 0x00, 0x00, 0x00, 0x00, 0x01, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x10, 0x00, 0x00, 0x00, 0x00, 0x9F, + 0xF2, 0x00, 0x00, 0x00, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x2F, + 0xF0, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x0F, + 0xC0, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x0C, + 0x80, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x08, + 0x70, 0x00, 0x00, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x01, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD6, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x00, + 0x6D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xF4, 0x33, 0x33, 0xEF, 0xFD, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x9F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF4, 0x33, 0x33, 0xEF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x06, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xF4, 0x33, 0x33, 0xEF, 0xFF, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1D, 0xFF, 0xF4, 0x33, 0x33, 0xEF, 0xFF, 0xD1, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0xFF, 0xFF, 0xF4, 0x33, 0x33, 0xEF, 0xFF, 0xFF, 0x71, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xFF, 0xFF, 0xFF, 0xF4, 0x33, 0x33, 0xEF, 0xFF, 0xFF, 0xFF, 0x81, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x33, 0x33, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0x90, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x10, 0x00, 0x00, 0x00, 0x00, 0x06, 0xFF, 0xFF, 0xFF, 0xFF, 0xEA, 0x53, 0x33, 0x33, 0x4A, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, + 0x60, 0x00, 0x00, 0x00, 0x00, 0x01, 0xDF, 0xFF, 0xFF, 0xFF, 0xD1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9F, 0xFF, 0xFF, 0xFE, 0x53, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x4A, 0xFF, 0xFF, 0xFF, + 0xF9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFD, 0x86, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x70, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xFB, 0x43, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x35, 0xFF, 0xFF, + 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x01, 0xDF, 0xFF, 0xFE, 0x43, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3A, 0xFF, + 0xFF, 0xFD, 0x10, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xA3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x4E, + 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x90, 0x00, 0x00, 0x00, 0x00, 0x9F, 0xFF, 0xF5, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0xEF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0x53, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x3E, 0xFF, 0xFF, 0x70, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF5, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0xEF, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x9F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xA3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x4F, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x06, 0xFF, 0xFE, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x34, 0x5B, 0xEE, 0xEE, 0xA5, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x35, 0xFF, 0xFF, 0x60, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x60, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xF4, 0x33, 0x33, 0x33, 0x33, 0x33, 0x34, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0xBF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x06, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xB3, 0x33, 0x33, 0x33, 0x33, 0x33, 0xAF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x4F, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0xCF, 0xFF, 0x43, 0x33, 0x33, 0x33, 0x33, 0x3E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x53, 0x33, 0x33, + 0x33, 0x33, 0x3B, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFB, 0x33, 0x33, 0x33, 0x33, 0x33, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF5, 0x33, 0x33, + 0x33, 0x33, 0x34, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x06, 0xFF, 0xF4, 0x33, 0x33, 0x33, 0x33, 0x3A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x43, 0x33, + 0x33, 0x33, 0x33, 0xEF, 0xFF, 0x60, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0x00, 0x00, 0x00, 0x09, 0xFF, 0xF3, 0x33, 0x33, 0x33, 0x33, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB3, 0x33, + 0x33, 0x33, 0x33, 0x5F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xB3, 0x33, 0x33, 0x33, 0x33, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x33, + 0x33, 0x33, 0x33, 0x4F, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0x53, 0x33, 0x33, 0x33, 0x34, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0x33, + 0x33, 0x33, 0x33, 0x3F, 0xFF, 0xF1, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0x43, 0x33, 0x33, 0x33, 0x3A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x33, + 0x33, 0x33, 0x33, 0x3E, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x90, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0x33, 0x33, 0x33, 0x33, 0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x43, + 0x33, 0x33, 0x33, 0x3A, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0x33, 0x33, 0x33, 0x33, 0x3E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x53, + 0x33, 0x33, 0x33, 0x3A, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x6F, 0xFF, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x53, + 0x33, 0x33, 0x33, 0x3A, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0x33, 0x33, 0x33, 0x33, 0x3E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x53, + 0x33, 0x33, 0x33, 0x3A, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x90, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0x33, 0x33, 0x33, 0x33, 0x3E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x53, + 0x33, 0x33, 0x33, 0x3A, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0x43, 0x33, 0x33, 0x33, 0x3A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x33, + 0x33, 0x33, 0x33, 0x3B, 0xFF, 0xF1, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0x53, 0x33, 0x33, 0x33, 0x35, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x33, + 0x33, 0x33, 0x33, 0x3E, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xA3, 0x33, 0x33, 0x33, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0x33, + 0x33, 0x33, 0x33, 0x3F, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xE3, 0x33, 0x33, 0x33, 0x33, 0xAF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF3, 0x33, + 0x33, 0x33, 0x33, 0x4F, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xF3, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x53, 0x33, + 0x33, 0x33, 0x33, 0xAF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF5, 0x33, 0x33, 0x33, 0x33, 0x34, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0x33, 0x33, + 0x33, 0x33, 0x33, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x9F, 0xFE, 0x33, 0x33, 0x33, 0x33, 0x33, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0x33, 0x33, + 0x33, 0x33, 0x35, 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0x53, 0x33, 0x33, 0x33, 0x33, 0x34, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x3E, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xE3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x4B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x53, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x5F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFA, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x4A, 0xFF, 0xFF, 0xFF, 0xFE, 0x53, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x8F, 0xFF, 0x43, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x34, 0x45, 0x44, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x3B, 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xF4, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x5F, 0xFF, 0xF1, 0x00, 0x00, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFE, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x35, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x5F, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0x43, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3A, + 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x00, 0x8F, 0xFF, 0xF5, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xEF, + 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x06, 0xFF, 0xFF, 0xE4, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xAF, 0xFF, + 0xFF, 0x60, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xB4, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x5F, 0xFF, 0xFF, + 0xF2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x01, 0xCF, 0xFF, 0xFF, 0xFA, 0x43, 0x33, 0x33, 0x33, 0x33, 0x45, 0xEF, 0xFF, 0xFF, 0xFC, + 0x10, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xDF, 0xFF, 0xFF, 0xFF, 0xEE, 0xBA, 0xAE, 0xEF, 0xFF, 0xFF, 0xFF, 0xFD, 0x60, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x60, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x6C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC6, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x67, 0x88, 0x88, 0x76, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x06, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0xDF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x7C, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x00, 0x00, 0x00, 0x07, + 0x70, 0x00, 0x00, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDC, 0x86, 0x66, 0x66, 0x68, 0xCD, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0x07, + 0x90, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0x00, 0x00, 0x00, 0x09, + 0xD0, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x0D, + 0xF0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x0F, + 0xF6, 0x00, 0x00, 0x00, 0x00, 0x2D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD2, 0x00, 0x00, 0x00, 0x00, 0x6F, + 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDF, + 0xFF, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xFF, + 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFF, + 0xFF, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9F, 0xFF, + 0xFF, 0xFF, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xC1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; diff --git a/Firmware_V3/src/gui/buttons.cpp b/Firmware_V3/src/gui/buttons.cpp new file mode 100644 index 0000000..2030565 --- /dev/null +++ b/Firmware_V3/src/gui/buttons.cpp @@ -0,0 +1,392 @@ +/* + * + * Buttons - Touch buttons for the GUI + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include + +/*################# DATA TYPES, CONSTANTS & MACRO DEFINITIONS #################*/ + +#define BUTTON_DISABLED 0x0001 +#define BUTTON_SYMBOL 0x0002 +#define BUTTON_SYMBOL_REP_3X 0x0004 +#define BUTTON_BITMAP 0x0008 +#define BUTTON_NO_BORDER 0x0010 +#define BUTTON_UNUSED 0x8000 + +typedef struct { + uint16_t pos_x, pos_y, width, height; + uint16_t flags; + boolean largetouch; + char *label; + const uint8_t* data; + const uint16_t* palette; +} button_type; + +/*######################### STATIC DATA DECLARATIONS ##########################*/ + +//Store up to 20 buttons +static button_type buttons[20]; +//Button attributes +static word buttons_colorText; +static word buttons_colorTextInactive; +static word buttons_colorBackGround; +static word buttons_colorBorder; +static word buttons_colorHilite; +//Button fonts +static uint8_t* buttons_fontText; +static uint8_t* buttons_fontSymbol; + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Add a text button */ +int buttons_addButton(uint16_t x, uint16_t y, uint16_t width, uint16_t height, char *label, uint16_t flags, boolean largetouch) +{ + int btcnt = 0; + + while (((buttons[btcnt].flags & BUTTON_UNUSED) == 0) + && (btcnt < 20)) + btcnt++; + + if (btcnt == 20) + return -1; + else { + buttons[btcnt].pos_x = x; + buttons[btcnt].pos_y = y; + buttons[btcnt].width = width; + buttons[btcnt].height = height; + buttons[btcnt].flags = flags; + buttons[btcnt].label = label; + buttons[btcnt].data = NULL; + buttons[btcnt].palette = NULL; + buttons[btcnt].largetouch = largetouch; + return btcnt; + } +} + +/* Add a bitmap button */ +int buttons_addButton(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, const uint16_t* palette, uint16_t flags) +{ + int btcnt = 0; + + while (((buttons[btcnt].flags & BUTTON_UNUSED) == 0) && (btcnt < 20)) + btcnt++; + + if (btcnt == 20) + return -1; + else + { + buttons[btcnt].pos_x = x; + buttons[btcnt].pos_y = y; + buttons[btcnt].width = width; + buttons[btcnt].height = height; + buttons[btcnt].flags = flags | BUTTON_BITMAP; + buttons[btcnt].label = NULL; + buttons[btcnt].data = data; + buttons[btcnt].palette = palette; + return btcnt; + } +} + +/* Draw a specific button */ +void buttons_drawButton(int buttonID) { + int text_x, text_y; + uint8_t *_font_current = display_getFont(); + ; + word _current_color = display_getColor(); + word _current_back = display_getBackColor(); + + if (buttons[buttonID].flags & BUTTON_BITMAP) { + display_writeRect2BPP(buttons[buttonID].pos_x, buttons[buttonID].pos_y, + buttons[buttonID].width, buttons[buttonID].height, + buttons[buttonID].data, buttons[buttonID].palette); + if (!(buttons[buttonID].flags & BUTTON_NO_BORDER)) { + if ((buttons[buttonID].flags & BUTTON_DISABLED)) + display_setColor(buttons_colorTextInactive); + else + display_setColor(buttons_colorBorder); + display_drawRoundRect(buttons[buttonID].pos_x, buttons[buttonID].pos_y, + buttons[buttonID].pos_x + buttons[buttonID].width, + buttons[buttonID].pos_y + buttons[buttonID].height); + display_drawRoundRect(buttons[buttonID].pos_x - 1, buttons[buttonID].pos_y - 1, + buttons[buttonID].pos_x + buttons[buttonID].width + 1, + buttons[buttonID].pos_y + buttons[buttonID].height + 1); + } + } + else { + display_setColor(buttons_colorBackGround); + display_fillRoundRect(buttons[buttonID].pos_x, buttons[buttonID].pos_y, + buttons[buttonID].pos_x + buttons[buttonID].width, + buttons[buttonID].pos_y + buttons[buttonID].height); + display_setColor(buttons_colorBorder); + display_drawRoundRect(buttons[buttonID].pos_x, buttons[buttonID].pos_y, + buttons[buttonID].pos_x + buttons[buttonID].width, + buttons[buttonID].pos_y + buttons[buttonID].height); + display_drawRoundRect(buttons[buttonID].pos_x - 1, buttons[buttonID].pos_y - 1, + buttons[buttonID].pos_x + buttons[buttonID].width + 1, + buttons[buttonID].pos_y + buttons[buttonID].height + 1); + if (buttons[buttonID].flags & BUTTON_DISABLED) + display_setColor(buttons_colorTextInactive); + else + display_setColor(buttons_colorText); + if (buttons[buttonID].flags & BUTTON_SYMBOL) { + display_setFont(buttons_fontSymbol); + text_x = (buttons[buttonID].width / 2) - (display_getFontXsize() / 2) + + buttons[buttonID].pos_x; + text_y = (buttons[buttonID].height / 2) + - (display_getFontYsize() / 2) + buttons[buttonID].pos_y; + } + else { + display_setFont(buttons_fontText); + text_x = ((buttons[buttonID].width / 2) + - ((strlen(buttons[buttonID].label) * display_getFontXsize()) + / 2)) + buttons[buttonID].pos_x; + text_y = (buttons[buttonID].height / 2) + - (display_getFontYsize() / 2) + buttons[buttonID].pos_y; + } + display_setBackColor(buttons_colorBackGround); + display_print(buttons[buttonID].label, text_x, text_y); + if ((buttons[buttonID].flags & BUTTON_SYMBOL) + && (buttons[buttonID].flags & BUTTON_SYMBOL_REP_3X)) { + display_print(buttons[buttonID].label, + text_x - display_getFontXsize(), text_y); + display_print(buttons[buttonID].label, + text_x + display_getFontXsize(), text_y); + } + } + display_setFont(_font_current); + display_setColor(_current_color); + display_setBackColor(_current_back); +} + +/* Draw all buttons */ +void buttons_drawButtons() { + for (int i = 0; i < 20; i++) { + if ((buttons[i].flags & BUTTON_UNUSED) == 0) + buttons_drawButton(i); + } +} + +/* Enable a specific button */ +void buttons_enableButton(int buttonID, boolean redraw) { + if (!(buttons[buttonID].flags & BUTTON_UNUSED)) { + buttons[buttonID].flags = buttons[buttonID].flags & ~BUTTON_DISABLED; + if (redraw) + buttons_drawButton(buttonID); + } +} + +/* Disable a specific button */ +void buttons_disableButton(int buttonID, boolean redraw) { + if (!(buttons[buttonID].flags & BUTTON_UNUSED)) { + buttons[buttonID].flags = buttons[buttonID].flags | BUTTON_DISABLED; + if (redraw) + buttons_drawButton(buttonID); + } +} + +/* Relabel a specific button */ +void buttons_relabelButton(int buttonID, char *label, boolean redraw) { + if (!(buttons[buttonID].flags & BUTTON_UNUSED)) { + buttons[buttonID].label = label; + if (redraw) + buttons_drawButton(buttonID); + } +} + +/* Check if the button is enabled */ +boolean buttons_buttonEnabled(int buttonID) { + return !(buttons[buttonID].flags & BUTTON_DISABLED); +} + +/* Delete a specific button */ +void buttons_deleteButton(int buttonID) { + if (!(buttons[buttonID].flags & BUTTON_UNUSED)) + buttons[buttonID].flags = BUTTON_UNUSED; +} + +/* Delete all buttons */ +void buttons_deleteAllButtons() { + for (int i = 0; i < 20; i++) { + buttons[i].pos_x = 0; + buttons[i].pos_y = 0; + buttons[i].width = 0; + buttons[i].height = 0; + buttons[i].flags = BUTTON_UNUSED; + buttons[i].label = (char*) ""; + } +} + +/* Check which button is pressed */ +int buttons_checkButtons(boolean timeout, boolean fast) { + TS_Point p = touch_getPoint(); + int x = p.x; + int y = p.y; + int result = -1; + word _current_color = display_getColor(); + int xpos, ypos, width, height; + for (int i = 0; i < 20; i++) { + xpos = buttons[i].pos_x; + ypos = buttons[i].pos_y; + width = buttons[i].width; + height = buttons[i].height; + if (buttons[i].largetouch) { + xpos -= 30; + ypos -= 20; + width += 60; + height += 40; + } + if (((buttons[i].flags & BUTTON_UNUSED) == 0) + && ((buttons[i].flags & BUTTON_DISABLED) == 0) + && (result == -1)) { + if ((x >= xpos) + && (x <= (xpos + width)) + && (y >= ypos) + && (y <= (ypos + height))) + result = i; + } + } + if (result != -1) { + if (!(buttons[result].flags & BUTTON_NO_BORDER)) { + display_setColor(buttons_colorHilite); + if (buttons[result].flags & BUTTON_BITMAP) + display_drawRoundRect(buttons[result].pos_x, + buttons[result].pos_y, + buttons[result].pos_x + buttons[result].width, + buttons[result].pos_y + buttons[result].height); + else + display_drawRoundRect(buttons[result].pos_x, + buttons[result].pos_y, + buttons[result].pos_x + buttons[result].width, + buttons[result].pos_y + buttons[result].height); + display_drawRoundRect(buttons[result].pos_x - 1, + buttons[result].pos_y - 1, + buttons[result].pos_x + buttons[result].width + 1, + buttons[result].pos_y + buttons[result].height + 1); + } + } + if (fast) { + long time = millis(); + while ((touch_touched() == 1) + && ((millis() - time) < 50)) { + }; + } + else if (timeout) { + long time = millis(); + while ((touch_touched() == 1) + && ((millis() - time) < 150)) { + }; + } + else { + while (touch_touched() == 1) { + }; + } + if (result != -1) { + if (!(buttons[result].flags & BUTTON_NO_BORDER)) { + display_setColor(buttons_colorBorder); + if (buttons[result].flags & BUTTON_BITMAP) + display_drawRoundRect(buttons[result].pos_x, + buttons[result].pos_y, + buttons[result].pos_x + buttons[result].width, + buttons[result].pos_y + buttons[result].height); + else + display_drawRoundRect(buttons[result].pos_x, + buttons[result].pos_y, + buttons[result].pos_x + buttons[result].width, + buttons[result].pos_y + buttons[result].height); + display_drawRoundRect(buttons[result].pos_x - 1, + buttons[result].pos_y - 1, + buttons[result].pos_x + buttons[result].width + 1, + buttons[result].pos_y + buttons[result].height + 1); + } + } + display_setColor(_current_color); + return result; +} + +/* Set a specific button to active */ +void buttons_setActive(int buttonID) { + int text_x, text_y; + display_setColor(VGA_AQUA); + display_fillRect(buttons[buttonID].pos_x + 3, buttons[buttonID].pos_y + 3, + buttons[buttonID].pos_x + buttons[buttonID].width - 3, + buttons[buttonID].pos_y + buttons[buttonID].height - 3); + display_setFont(buttons_fontText); + display_setColor(buttons_colorText); + text_x = ((buttons[buttonID].width / 2) + - ((strlen(buttons[buttonID].label) * display_getFontXsize()) / 2)) + + buttons[buttonID].pos_x; + text_y = (buttons[buttonID].height / 2) - (display_getFontYsize() / 2) + + buttons[buttonID].pos_y; + display_setBackColor(VGA_AQUA); + display_print(buttons[buttonID].label, text_x, text_y); +} + +/* Set a specific button to inactive */ +void buttons_setInactive(int buttonID) { + int text_x, text_y; + display_setColor(buttons_colorBackGround); + display_fillRect(buttons[buttonID].pos_x + 3, buttons[buttonID].pos_y + 3, + buttons[buttonID].pos_x + buttons[buttonID].width - 3, + buttons[buttonID].pos_y + buttons[buttonID].height - 3); + display_setFont(buttons_fontText); + display_setColor(buttons_colorText); + text_x = ((buttons[buttonID].width / 2) + - ((strlen(buttons[buttonID].label) * display_getFontXsize()) / 2)) + + buttons[buttonID].pos_x; + text_y = (buttons[buttonID].height / 2) - (display_getFontYsize() / 2) + + buttons[buttonID].pos_y; + display_setBackColor(buttons_colorBackGround); + display_print(buttons[buttonID].label, text_x, text_y); +} + +/* Set the text font of all buttons */ +void buttons_setTextFont(const uint8_t* font) { + buttons_fontText = (uint8_t*) font; +} + +/* Set the symbol font of all buttons */ +void buttons_setSymbolFont(const uint8_t* font) { + buttons_fontSymbol = (uint8_t*) font; +} + +/* Set the buttons color */ +void buttons_setButtonColors(word atxt, word iatxt, word brd, word brdhi, + word back) { + buttons_colorText = atxt; + buttons_colorTextInactive = iatxt; + buttons_colorBackGround = back; + buttons_colorBorder = brd; + buttons_colorHilite = brdhi; +} + +/* Init the buttons */ +void buttons_init() { + buttons_deleteAllButtons(); + buttons_colorText = VGA_WHITE; + buttons_colorTextInactive = VGA_GRAY; + buttons_colorBackGround = VGA_BLUE; + buttons_colorBorder = VGA_BLACK; + buttons_colorHilite = VGA_BLUE; + buttons_fontText = NULL; + buttons_fontSymbol = NULL; + buttons_setButtonColors(VGA_BLACK, VGA_BLACK, VGA_BLACK, VGA_BLUE, + VGA_WHITE); +} diff --git a/Firmware_V3/src/gui/firststart.cpp b/Firmware_V3/src/gui/firststart.cpp new file mode 100644 index 0000000..cc51d7d --- /dev/null +++ b/Firmware_V3/src/gui/firststart.cpp @@ -0,0 +1,306 @@ +/* +* +* FIRST START - Menu that is displayed on the first device start +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Check if the first start needs to be done */ +boolean checkFirstStart() +{ + return EEPROM.read(eeprom_firstStart) != eeprom_setValue; +} + +/* Show welcome Screen for the first start procedure */ +void welcomeScreen() +{ + //Background & Title + display_fillScr(200, 200, 200); + display_setBackColor(200, 200, 200); + display_setFont(smallFont); + display_printC("Welcome to the", CENTER, 20); + display_setFont(bigFont); + display_printC("DIY-Thermocam V3", CENTER, 60, VGA_BLUE); + + //Explanation + display_setFont(smallFont); + display_printC("This is the first time setup.", CENTER, 110); + display_printC("It will guide you through the", CENTER, 140); + display_printC("basic settings for your device.", CENTER, 170); + display_printC("-> Please touch screen <-", CENTER, 210, VGA_BLUE); + + //Wait for touch press or updater request + while (!touch_touched()) + checkForUpdater(); + + //Touch release again + while (touch_touched()) + ; +} + +/* Shows an info screen during the first start procedure */ +void infoScreen(String *text, bool cont) +{ + //Background & Title + display_fillScr(200, 200, 200); + display_setBackColor(200, 200, 200); + display_setFont(bigFont); + display_printC(text[0], CENTER, 20, VGA_BLUE); + + //Content + display_setFont(smallFont); + display_printC(text[1], CENTER, 55); + display_printC(text[2], CENTER, 80); + display_printC(text[3], CENTER, 105); + display_printC(text[4], CENTER, 130); + + //Show hint to touch the screen + if (cont) + { + display_printC(text[5], CENTER, 155); + display_printC(text[6], CENTER, 180); + display_printC("-> Please touch screen <-", CENTER, 212, VGA_BLUE); + //Wait for touch press or updater request + while (!touch_touched()) + checkForUpdater(); + //Touch release again + while (touch_touched()) + ; + } + + //Show more information + else + { + display_printC(text[5], CENTER, 180); + display_printC(text[6], CENTER, 205); + } +} + +/* Setting screen for the time and date */ +void timeDateScreen() +{ + //Content + String text[7]; + text[0] = "Set Time & Date"; + text[1] = "In the next screens, you can"; + text[2] = "set the time and date, so "; + text[3] = "that it matches your current"; + text[4] = "time zone. If the settings do"; + text[5] = "not survive a reboot, check"; + text[6] = "the coin cell battery voltage."; + infoScreen(text); + + //Reset values + setTime(12, 30, 30, 15, 6, 2021); + + //Adjust time + timeMenu(true); + timeMenuHandler(true); + + //Adjust date + dateMenu(true); + dateMenuHandler(true); +} + +/* Setting screen for the temperature format */ +void tempFormatScreen() +{ + //Content + String text[7]; + text[0] = "Set Temp. Format"; + text[1] = "In the next screen, you can"; + text[2] = "set the temperature format "; + text[3] = "for the temperature display_"; + text[4] = "Choose between Celsius or"; + text[5] = "Fahrenheit, the conversion will"; + text[6] = "be done automatically."; + infoScreen(text); + + //Temperature format menu + tempFormatMenu(true); +} + +/* Setting screen for the convert image option */ +void convertImageScreen() +{ + //Content + String text[7]; + text[0] = "Convert DAT to BMP"; + text[1] = "In the next screen, select if"; + text[2] = "you also want to create a bitmap"; + text[3] = "file for every saved thermal"; + text[4] = "raw image file on the device. "; + text[5] = "You can still convert images man-"; + text[6] = "ually in the load menu later."; + infoScreen(text); + + //Convert image menu + convertImageMenu(true); +} + +/* Format the SD card for the first time */ +void firstFormat() +{ + //Format the SD card + showFullMessage((char *)"Formatting SD card.."); + if (formatCard()) + showFullMessage((char *)"Formatting finished!", true); + else + showFullMessage((char *)"Error during formatting!", true); + delay(1000); +} + +/* Show the first start complete screen */ +void firstStartComplete() +{ + //Content + String text[7]; + text[0] = "Setup completed"; + text[1] = "The first-time setup is"; + text[2] = "now complete. Please reboot"; + text[3] = "the device by turning the"; + text[4] = "power switch off and on again."; + text[5] = "Afterwards, you will be redirected"; + text[6] = "to the first start helper menu."; + infoScreen(text, false); + + //Wait for hard-reset + while (true) + ; +} + +/* Check if the live mode helper needs to be shown */ +boolean checkLiveModeHelper() +{ + return EEPROM.read(eeprom_liveHelper) != eeprom_setValue; +} + +/* Help screen for the first start of live mode */ +void liveModeHelper() +{ + String text[7]; + + //Hint screen for the live mode #1 + text[0] = "First time helper"; + text[1] = "To enter the menu in live mode"; + text[2] = "touch the screen short. Pressing"; + text[3] = "the push button on top of the"; + text[4] = "device short saves an image to"; + text[5] = "the internal storage, pressing"; + text[6] = "it longer records a video."; + infoScreen(text); + + //Hint screen for the live mode #2 + text[1] = "You can lock the temperature limits"; + text[2] = "by touching the screen longer in "; + text[3] = "live mode. Doing it again switches"; + text[4] = "back to auto mode. Connecting a USB"; + text[5] = "cable will allow you to enter the"; + text[6] = "mass storage mode on your PC."; + infoScreen(text); + + //Show waiting message + showFullMessage((char *)"Please wait.."); + + //Set EEPROM marker to complete + EEPROM.write(eeprom_liveHelper, eeprom_setValue); +} + +/* Set the EEPROM values to default for the first time */ +void stdEEPROMSet() +{ + //Set device EEPROM settings + EEPROM.write(eeprom_rotationVert, false); + EEPROM.write(eeprom_rotationHorizont, false); + EEPROM.write(eeprom_spotEnabled, false); + EEPROM.write(eeprom_colorbarEnabled, true); + EEPROM.write(eeprom_batteryEnabled, true); + EEPROM.write(eeprom_timeEnabled, true); + EEPROM.write(eeprom_dateEnabled, true); + EEPROM.write(eeprom_storageEnabled, true); + EEPROM.write(eeprom_textColor, textColor_white); + EEPROM.write(eeprom_minMaxPoints, minMaxPoints_max); + EEPROM.write(eeprom_screenOffTime, screenOffTime_disabled); + EEPROM.write(eeprom_hotColdMode, hotColdMode_disabled); + + //Set Color Scheme to Rainbow + EEPROM.write(eeprom_colorScheme, colorScheme_rainbow); + + //Set filter type to box blur + EEPROM.write(eeprom_filterType, filterType_gaussian); + + //Set disable shutter to false + EEPROM.write(eeprom_noShutter, false); + + //Set enter mass storage mode to disabled + EEPROM.write(eeprom_massStorage, 0); + + //Battery gauge standard compensation values + EEPROM.write(eeprom_batComp, 6); + + //Set current firmware version + EEPROM.write(eeprom_fwVersionHigh, (fwVersion & 0xFF00) >> 8); + EEPROM.write(eeprom_fwVersionLow, fwVersion & 0x00FF); + + //Set first start marker to true + EEPROM.write(eeprom_firstStart, eeprom_setValue); + + //Set live helper to false to show it the next time + EEPROM.write(eeprom_liveHelper, false); +} + +/* First start setup*/ +void firstStart() +{ + //Clear EEPROM + clearEEPROM(); + + //Welcome screen + welcomeScreen(); + + //Hint screen for the time and date settings + timeDateScreen(); + + //Hint screen for temperature format setting + tempFormatScreen(); + + //Hint screen for the convert image settings + convertImageScreen(); + + //Format SD card for the first time + firstFormat(); + + //Set EEPROM values + stdEEPROMSet(); + + //Show completion message + firstStartComplete(); +} diff --git a/Firmware_V3/src/gui/gui.cpp b/Firmware_V3/src/gui/gui.cpp new file mode 100644 index 0000000..6bb241a --- /dev/null +++ b/Firmware_V3/src/gui/gui.cpp @@ -0,0 +1,233 @@ +/* +* +* GUI - Main Methods to lcd the Graphical-User-Interface +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Converts a given float to char array */ +void floatToChar(char *buffer, float val) +{ + int units = val; + int hundredths = val * 100; + hundredths = abs(hundredths % 100); + sprintf(buffer, "%d.%02d", units, hundredths); +} + +/* Sets the text color to the right one */ +void changeTextColor() +{ + //Red + if (textColor == textColor_red) + display_setColor(VGA_RED); + //Black + else if (textColor == textColor_black) + display_setColor(VGA_BLACK); + //Green + else if (textColor == textColor_green) + display_setColor(VGA_GREEN); + //Blue + else if (textColor == textColor_blue) + display_setColor(VGA_BLUE); + //White + else + display_setColor(VGA_WHITE); +} + +/* Shows a full screen message */ +void showFullMessage(char *message, bool small) +{ + //Fill screen complete + if (!small) + display_fillScr(200, 200, 200); + + //Make a round corner around it + else + { + display_setColor(200, 200, 200); + display_fillRoundRect(6, 6, 314, 234); + } + + //Display the text + display_setFont(smallFont); + display_setBackColor(200, 200, 200); + display_setColor(VGA_BLACK); + display_print(message, CENTER, 110); +} + +/* Shows a transparent message in live mode */ +void showTransMessage(char *msg) +{ + //Set text color + changeTextColor(); + //Set background transparent + display_setBackColor(VGA_TRANSPARENT); + //Display to screen in big font + display_setFont(bigFont); + //Display higher if spot is enabled + if (spotEnabled) + display_print(msg, CENTER, 70); + else + display_print(msg, CENTER, 110); + //Switch back to small font + display_setFont(smallFont); + //Wait some time to read the text + delay(1000); +} + +/* Draw a BigFont Text in the center of a menu*/ +void drawCenterElement(int element) +{ + display_setFont(bigFont); + display_setColor(VGA_BLACK); + display_setBackColor(200, 200, 200); + display_printNumI(element, CENTER, 80, 2, '0'); + display_setFont(smallFont); +} + +/* Draws the border for the main menu */ +void drawMainMenuBorder() +{ + display_setColor(VGA_BLACK); + display_fillRoundRect(5, 5, 315, 235); + display_fillRoundRect(4, 4, 316, 236); +} + +/* Draw a title on the screen */ +void drawTitle(char *name, bool firstStart) +{ + if (firstStart) + display_fillScr(200, 200, 200); + else + { + display_setColor(200, 200, 200); + display_fillRoundRect(6, 6, 314, 234); + } + display_setFont(bigFont); + display_setBackColor(200, 200, 200); + display_setColor(VGA_BLACK); + display_print(name, CENTER, 25); + display_setFont(smallFont); +} + +/* Shows the hadware diagnostics */ +void showDiagnostic() +{ + //Display title & background + display_fillScr(200, 200, 200); + display_setFont(bigFont); + display_setBackColor(200, 200, 200); + display_setColor(VGA_BLUE); + display_print((char *)"Self-diagnostic", CENTER, 10); + + //Change text color and font + display_setFont(smallFont); + display_setColor(VGA_BLACK); + + //Display hardware module names + display_print((char *)"Display SPI", 50, 50); + display_print((char *)"Touch SPI", 50, 78); + display_print((char *)"Battery Gauge", 50, 106); + display_print((char *)"Lepton I2C", 50, 134); + display_print((char *)"Lepton SPI", 50, 162); + display_print((char *)"SD card", 50, 190); + + //Check display SPI + if (checkDiagnostic(diag_display)) + display_print((char *)"OK ", 220, 50); + else + display_print((char *)"Failed", 220, 50); + + //Check touch SPI + if (checkDiagnostic(diag_touch)) + display_print((char *)"OK ", 220, 78); + else + display_print((char *)"Failed", 220, 78); + + //Check battery gauge + if (checkDiagnostic(diag_bat)) + display_print((char *)"OK ", 220, 106); + else + display_print((char *)"Failed", 220, 106); + + //Check lepton I2C + if (checkDiagnostic(diag_lep_conf)) + display_print((char *)"OK ", 220, 134); + else + display_print((char *)"Failed", 220, 134); + + //Check lepton SPI + if (checkDiagnostic(diag_lep_data)) + display_print((char *)"OK ", 220, 162); + else + display_print((char *)"Failed", 220, 162); + + //Check sd card + if (checkDiagnostic(diag_sd)) + display_print((char *)"OK ", 220, 190); + else + display_print((char *)"Failed", 220, 190); + + //Wait until touch + while (!touch_touched()) + + //Show hint + display_print((char *)"Touch to continue (may freeze)", CENTER, 220); +} + +/* Draw the Boot screen */ +void bootScreen() +{ + //Set rotation + setDisplayRotation(); + + //Init the buttons + buttons_init(); + + //Set Fonts + buttons_setTextFont((uint8_t *)smallFont); + display_setFont(smallFont); + + //Draw Screen + display_fillScr(255, 255, 255); + display_setFont(bigFont); + display_setBackColor(255, 255, 255); + display_setColor(VGA_BLACK); + + //Show the logo and boot text + display_writeRect4BPP(90, 35, 140, 149, logoBitmap, logoColors); + display_print((char *)"Booting", CENTER, 194); + display_setFont(smallFont); + + //Show hardware version + display_print((char *)"DIY-Thermocam V3", CENTER, 10); + + //Display version + display_print(versionString, CENTER, 220); + + //Wait some time + delay(2000); +} diff --git a/Firmware_V3/src/gui/livemode.cpp b/Firmware_V3/src/gui/livemode.cpp new file mode 100644 index 0000000..923c527 --- /dev/null +++ b/Firmware_V3/src/gui/livemode.cpp @@ -0,0 +1,238 @@ +/* +* +* LIVE MODE - GUI functions used in the live mode +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Display battery status in percentage */ +void displayBatteryStatus() +{ + //Check battery status every 60 seconds + if (batTimer == 0) + batTimer = millis(); + if ((millis() - batTimer) > 60000) + { + checkBattery(); + batTimer = millis(); + } + + //USB Power only + if (batPercentage == -1) + display_print((char *)"USB Power", 225, 0); + + //Low Battery + else if (batPercentage == 0) + display_print((char *)"LOW", 270, 0); + + //Display battery status in percentage + else + { + //Charging, show plus symbol + if (isUSBConnected() && (batPercentage != 100)) + { + display_printNumI(batPercentage, 270, 0, 3, ' '); + display_print((char *)"%", 300, 0); + display_print((char *)"+", 310, 0); + } + + //Not charging + else + { + display_printNumI(batPercentage, 280, 0, 3, ' '); + display_print((char *)"%", 310, 0); + } + } +} + +/* Display the current time on the screen*/ +void displayTime() +{ + display_printNumI(hour(), 5, 228, 2, '0'); + display_print((char *)":", 20, 228); + display_printNumI(minute(), 27, 228, 2, '0'); + display_print((char *)":", 42, 228); + display_printNumI(second(), 49, 228, 2, '0'); +} + +/* Display the date on screen */ +void displayDate() +{ + display_printNumI(day(), 5, 0, 2, '0'); + display_print((char *)".", 20, 0); + display_printNumI(month(), 27, 0, 2, '0'); + display_print((char *)".", 42, 0); + display_printNumI(year(), 49, 0, 4); +} + +/* Display the current temperature mode on top*/ +void displayTempMode() +{ + char buffer[10]; + + if (limitsLocked) + sprintf(buffer, " LOCKED"); + else if (autoMode) + sprintf(buffer, " AUTO"); + //Temperature presets + else + { + //Check which preset is active + byte minMaxPreset; + byte read = EEPROM.read(eeprom_minMaxPreset); + if ((read >= minMax_preset1) && (read <= minMax_preset3)) + minMaxPreset = read; + else + minMaxPreset = minMax_temporary; + //Choose corresponding text + if ((minMaxPreset == minMax_preset1) && (EEPROM.read(eeprom_minMax1Set) == eeprom_setValue)) + sprintf(buffer, "PRESET 1"); + else if ((minMaxPreset == minMax_preset2) && (EEPROM.read(eeprom_minMax2Set) == eeprom_setValue)) + sprintf(buffer, "PRESET 2"); + else if ((minMaxPreset == minMax_preset3) && (EEPROM.read(eeprom_minMax3Set) == eeprom_setValue)) + sprintf(buffer, "PRESET 3"); + else + sprintf(buffer, " MANUAL"); + } + + display_print(buffer, 140, 0); +} + +/* Display the minimum and maximum point on the screen */ +void displayMinMaxPoint(bool min) +{ + int16_t xpos, ypos; + + //Calculate x and y position + if (min) + calculatePointPos(&xpos, &ypos, minTempPos); + else + calculatePointPos(&xpos, &ypos, maxTempPos); + + //Draw the marker + display_drawLine(xpos, ypos, xpos, ypos); + + //Calc x position for the text + xpos -= 20; + if (xpos < 0) + xpos = 0; + if (xpos > 279) + xpos = 279; + + //Calc y position for the text + ypos += 15; + if (ypos > 229) + ypos = 229; + + //Show min or max value as absolute temperature + if (min) + display_printNumF(rawToTemp(minTempVal), 2, xpos, ypos); + else + display_printNumF(rawToTemp(maxTempVal), 2, xpos, ypos); +} + +/* Display free space on screen*/ +void displayFreeSpace() +{ + display_print(sdInfo, 205, 228); +} + +/* Show the current spot temperature on screen*/ +void showSpot() +{ + char buffer[10]; + + //Draw the spot circle + display_drawCircle(160, 120, 12); + + //Draw the lines + display_drawLine(136, 120, 148, 120); + display_drawLine(172, 120, 184, 120); + display_drawLine(160, 96, 160, 108); + display_drawLine(160, 132, 160, 144); + + //Convert spot temperature to char array + floatToChar(buffer, spotTemp); + + //Print value on display + display_print(buffer, 145, 150); +} + +/* Display addition information on the screen */ +void displayInfos() +{ + //Set text color + changeTextColor(); + //Set font and background + display_setBackColor(VGA_TRANSPARENT); + display_setFont((uint8_t *)smallFont); + + //Set write to image, not display + display_writeToImage = true; + + //If not saving image or video + if ((imgSave != imgSave_create) && (!videoSave)) + { + //Show battery status in percantage + if (batteryEnabled) + displayBatteryStatus(); + //Show the time + if (timeEnabled) + displayTime(); + //Show the date + if (dateEnabled) + displayDate(); + //Show storage information + if (storageEnabled) + displayFreeSpace(); + //Display temperature mode + displayTempMode(); + } + + //Show the minimum / maximum points + if (minMaxPoints & minMaxPoints_min) + displayMinMaxPoint(true); + if (minMaxPoints & minMaxPoints_max) + displayMinMaxPoint(false); + + //Show the spot in the middle + if (spotEnabled) + showSpot(); + + //Show the color bar + if (colorbarEnabled) + showColorBar(); + + //Show the temperature points + showTemperatures(); + + //Set write back to display + display_writeToImage = false; +} diff --git a/Firmware_V3/src/gui/loadmenu.cpp b/Firmware_V3/src/gui/loadmenu.cpp new file mode 100644 index 0000000..90e8fd4 --- /dev/null +++ b/Firmware_V3/src/gui/loadmenu.cpp @@ -0,0 +1,530 @@ +/* +* +* LOAD MENU - Display the menu to load images and videos +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Display the GUI elements for the load menu */ +void displayGUI(int imgCount, char* infoText) { + //Set text color + changeTextColor(); + //set Background transparent + display_setBackColor(VGA_TRANSPARENT); + display_setFont(bigFont); + //Delete image or video from internal storage + display_print((char*) "Delete", 220, 10); + //Find image by time and date + display_print((char*) "Find", 5, 10); + //Display prev/next if there is more than one image + if (imgCount != 1) { + display_print((char*) "<", 10, 110); + display_print((char*) ">", 295, 110); + } + //Convert image to bitmap + display_print((char*) "Convert", 5, 210); + //Exit to main menu + display_print((char*) "Exit", 250, 210); + display_setFont(smallFont); + //Display either frame number or image date and time + display_print(infoText, 80, 12); +} + +/* Asks the user if he wants to delete the video */ +void deleteVideo(char* dirname) { + //Title & Background + drawTitle((char*) "Delete Video", true); + display_setColor(VGA_BLACK); + display_setFont(smallFont); + display_setBackColor(200, 200, 200); + display_print((char*)"Do you want to delete this video?", CENTER, 66); + display_print((char*)"This will also remove the", CENTER, 105); + display_print((char*)"other related files to it. ", CENTER, 125); + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(15, 160, 140, 55, (char*) "Yes"); + buttons_addButton(165, 160, 140, 55, (char*) "No"); + buttons_drawButtons(); + buttons_setTextFont(smallFont); + //Touch handler + while (true) { + //If touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(true); + //YES + if (pressedButton == 0) { + showFullMessage((char*) "Delete video.."); + + //Delete the ending for a video + dirname[14] = '\0'; + + //Go into the video folder + sd.chdir("/"); + sd.chdir(dirname); + + //Delete all files + uint16_t videoCounter = 0; + bool exists; + char filename[] = "00000.DAT"; + + //Go through the frames + while (1) { + //Get the frame name + frameFilename(filename, videoCounter); + //Check frame existance + exists = sd.exists(filename); + //If the frame does not exists, end remove + if (!exists) + break; + //Otherwise remove file + else + sd.remove(filename); + //Remove Bitmap if there + strcpy(&filename[5], ".BMP"); + if (sd.exists(filename)) + sd.remove(filename); + //Remove Jpeg if there + strcpy(&filename[5], ".JPG"); + if (sd.exists(filename)) + sd.remove(filename); + //Reset ending + strcpy(&filename[5], ".DAT"); + //Raise counter + videoCounter++; + } + + //Remove the folder + sd.chdir("/"); + sd.rmdir(dirname); + + //End SD + showFullMessage((char*) "Video deleted"); + delay(1000); + return; + } + //NO + else if (pressedButton == 1) { + return; + } + } + } +} + +/* Asks the user if he wants to delete the image */ +void deleteImage(char* filename) { + //Title & Background + drawTitle((char*) "Delete Image", true); + display_setColor(VGA_BLACK); + display_setFont(smallFont); + display_setBackColor(200, 200, 200); + display_print((char*)"Do you want to delete this image?", CENTER, 66); + display_print((char*)"This will also remove the", CENTER, 105); + display_print((char*)"other related files to it. ", CENTER, 125); + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(165, 160, 140, 55, (char*) "Yes"); + buttons_addButton(15, 160, 140, 55, (char*) "No"); + buttons_drawButtons(); + buttons_setTextFont(smallFont);; + //Touch handler + while (true) { + //If touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(true); + //YES + if (pressedButton == 0) { + showFullMessage((char*) "Delete image.."); + //Delete .DAT file + sd.remove(filename); + //Delete .JPG file + strcpy(&filename[14], ".JPG"); + if (sd.exists(filename)) + sd.remove(filename); + //Delete .BMP file + strcpy(&filename[14], ".BMP"); + if (sd.exists(filename)) + sd.remove(filename); + showFullMessage((char*) "Image deleted"); + delay(1000); + return; + } + //NO + else if (pressedButton == 1) { + return; + } + } + } +} + +/* Asks the user if he really wants to convert the image/video */ +bool convertPrompt() { + //Title & Background + drawTitle((char*) "Conversion Prompt", true); + display_setColor(VGA_BLACK); + display_setFont(smallFont); + display_setBackColor(200, 200, 200); + display_print((char*)"Do you want to convert?", CENTER, 66); + display_print((char*)"That process will create", CENTER, 105); + display_print((char*)"bitmap(s) out of the raw data.", CENTER, 125); + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(165, 160, 140, 55, (char*) "Yes"); + buttons_addButton(15, 160, 140, 55, (char*) "No"); + buttons_drawButtons(); + buttons_setTextFont(smallFont); + //Wait for touch release + while (touch_touched()); + //Touch handler + while (true) { + //If touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(true); + //YES + if (pressedButton == 0) { + return true; + } + //NO + else if (pressedButton == 1) { + return false; + } + } + } +} + +/* Convert a raw image lately to BMP */ +void convertImage(char* filename) { + + //Check if image is a bitmap + if (filename[15] == 'B') { + showFullMessage((char*) "Image is already converted"); + delay(500); + return; + } + + //Check if the image is already there + strcpy(&filename[14], ".BMP"); + bool exists = sd.exists(filename); + + //If image is already converted, return + if (exists) { + showFullMessage((char*) "Image is already converted"); + delay(500); + strcpy(&filename[14], ".DAT"); + return; + } + + //If the user does not want to convert, return + if (!convertPrompt()) { + strcpy(&filename[14], ".DAT"); + return; + } + + //Show convert message + showFullMessage((char*) "Converting image to BMP.."); + + //Save image + saveBuffer(filename); + + //Show finish message + showFullMessage((char*) "Image converted"); + delay(1000); + strcpy(&filename[14], ".DAT"); +} + +/* Convert a raw video lately to BMP frames */ +void convertVideo(char* dirname) { + //Go into the folder + sd.chdir("/"); + sd.chdir(dirname); + + uint16_t frames = getVideoFrameNumber(); + char filename[] = "00000.BMP"; + + //Delete the ending for a video + dirname[14] = '\0'; + + //Get the frame name of the first frame + frameFilename(filename, 0); + bool exists = sd.exists(filename); + + //If video is already converted, return + if (exists) { + showFullMessage((char*) "Video is already converted"); + delay(500); + return; + } + + //If the user does not want to convert the video, return + if (!convertPrompt()) + return; + + //Show convert message + showFullMessage((char*) "Converting video to BMP.."); + delay(1000); + + //Convert video + processVideoFrames(frames, dirname); + videoSave = videoSave_disabled; + + sd.chdir("/"); +} + +/* Loads an image from the SDCard and prints it on screen */ +void openImage(char* filename, int imgCount) { + //Show message on screen + showFullMessage((char*) "Please wait, image is loading.."); + + //Display raw data + if (filename[15] == 'D') { + //Load Raw data + loadRawData(filename); + //Display Raw Data + displayRawData(); + } + + //Load bitmap + else if (filename[15] == 'B') { + loadBMPImage(filename); + } + + //Unsupported file type + else { + showFullMessage((char*) "Unsupported file type"); + delay(1000); + return; + } + + //Create string for time and date + char nameStr[20] = { + //Day + filename[6], filename[7], '.', + //Month + filename[4], filename[5], '.', + //Year + filename[2], filename[3], ' ', + //Hour + filename[8], filename[9], ':', + //Minute + filename[10], filename[11], ':', + //Second + filename[12], filename[13], '\0' + }; + + //Display GUI + displayGUI(imgCount, nameStr); + + //Attach interrupt + attachInterrupt(pin_touch_irq, loadTouchIRQ, FALLING); + + //Wait for touch press + while (loadTouch == loadTouch_none); + + //Disable touch handler + detachInterrupt(pin_touch_irq); +} + +/* Get the number of frames in the video */ +uint16_t getVideoFrameNumber() { + uint16_t videoCounter = 0; + bool exists; + char filename[] = "00000.DAT"; + + //Look how many frames we have + while (true) { + //Get the frame name + frameFilename(filename, videoCounter); + //Check frame existance + exists = sd.exists(filename); + //Raise counter + if (exists) + videoCounter++; + //Leave + else + break; + } + + return videoCounter; +} + +/* Display the selected video frame */ +void displayVideoFrame(int i) +{ + char filename[] = "00000.DAT"; + + //Get the frame name + frameFilename(filename, i); + + //Load Raw data + loadRawData(filename); + + //Display Raw Data + displayRawData(); +} + +/* Play a video from the internal storage */ +void playVideo(char* dirname, int imgCount) { + char buffer[14]; + //Save the current frame number + int frameNumber = 0; + + //Switch to video folder + sd.chdir("/"); + sd.chdir(dirname); + + //Get the total number of frames in the dir + uint16_t numberOfFrames = getVideoFrameNumber(); + + //Jump here when pausing a video +showFrame: + //Display frame + displayVideoFrame(frameNumber); + //Create string + sprintf(buffer, "%5d / %-5d", frameNumber + 1, numberOfFrames); + //Display GUI + displayGUI(imgCount, buffer); + //Display play message + display_setFont(bigFont); + if (spotEnabled) + display_print((char*) "Play", CENTER, 70); + else + display_print((char*) "Play", CENTER, 110); + display_setFont(smallFont); + + //Repeat until we get a valid touch + do { + //Wait for touch press + while (!touch_touched()); + //Interpret touch coordinates + loadTouchIRQ(); + } while (loadTouch == loadTouch_none); + + //Wait for touch to release + while (touch_touched()); + + //Check if we play the video + if (loadTouch != loadTouch_middle) + { + sd.chdir("/"); + return; + } + + loadTouch = loadTouch_none; + + //Play forever + while (true) { + //Go through the frames + for (; frameNumber < numberOfFrames; frameNumber++) { + //Check for touch press + if (touch_touched()) + //Get touch function + loadTouchIRQ(); + //Pause the video + if (loadTouch == loadTouch_middle) + { + //Wait for touch release + while (touch_touched()); + //Display the static frame + goto showFrame; + } + //Any other action + if (loadTouch != loadTouch_none) + { + sd.chdir("/"); + return; + } + + //Display frame + displayVideoFrame(frameNumber); + //Create string + sprintf(buffer, "%5d / %-5d", frameNumber + 1, numberOfFrames); + //Display GUI + displayGUI(imgCount, buffer); + } + + //Reset frame number for next play + frameNumber = 0; + } + + sd.chdir("/"); +} + +/* Shows a menu where the user can choose the time & date items for the image */ +int loadMenu(char* title, int* array, int length) { + //Draw the title on screen + drawTitle(title); + //Draw the Buttons + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 70, 70, (char*) "-"); + buttons_addButton(230, 60, 70, 70, (char*) "+"); + buttons_addButton(20, 150, 130, 70, (char*) "Back"); + buttons_addButton(170, 150, 130, 70, (char*) "Choose"); + buttons_drawButtons(); + int currentPos = 0; + //Display the first element for the array + drawCenterElement(array[currentPos]); + //Touch handler + while (true) { + //Touch pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(); + //Minus + if (pressedButton == 1) { + //Decrease element by one + if (currentPos > 0) + currentPos--; + //Go from lowest to highest element + else if (currentPos == 0) + currentPos = length - 1; + //Draw it on screen + drawCenterElement(array[currentPos]); + } + //Plus + else if (pressedButton == 0) { + //Increase element by one + if (currentPos < (length - 1)) + currentPos++; + //Go from highest to lowest element + else if (currentPos == (length - 1)) + currentPos = 0; + //Draw it on screen + drawCenterElement(array[currentPos]); + } + //Back - return minus 1 + else if (pressedButton == 2) { + return -1; + } + //Set - return element's position + else if (pressedButton == 3) { + return currentPos; + } + } + } +} diff --git a/Firmware_V3/src/gui/mainmenu.cpp b/Firmware_V3/src/gui/mainmenu.cpp new file mode 100644 index 0000000..f2ca3fd --- /dev/null +++ b/Firmware_V3/src/gui/mainmenu.cpp @@ -0,0 +1,1514 @@ +/* + * + * MAIN MENU - Display the main menu with icons + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Draws the background in the main menu */ +void mainMenuBackground() +{ + display_setColor(120, 120, 120); + display_fillRoundRect(6, 6, 314, 234); + display_setColor(200, 200, 200); + display_fillRect(6, 36, 314, 180); + display_setColor(VGA_BLACK); + display_drawHLine(6, 36, 314); + display_drawHLine(6, 180, 314); +} + +/* Draws the content of the selection menu*/ +void drawSelectionMenu() +{ + //Buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(15, 45, 38, 77, (char *)"<", 0, true); + buttons_addButton(267, 45, 38, 77, (char *)">", 0, true); + buttons_addButton(15, 188, 120, 40, (char *)"Back"); + buttons_addButton(95, 132, 130, 35, (char *)"OK"); + buttons_drawButtons(); + //Border + display_setColor(VGA_BLUE); + display_drawRect(65, 57, 257, 111); +} + +/* Draws the title in the main menu */ +void mainMenuTitle(char *title) +{ + display_setFont(bigFont); + display_setBackColor(120, 120, 120); + display_setColor(VGA_WHITE); + display_print(title, CENTER, 14); +} + +/* Draws the current selection in the menu */ +void mainMenuSelection(char *selection) +{ + //Clear the old content + display_setColor(VGA_WHITE); + display_fillRect(66, 58, 257, 111); + //Print the text + display_setBackColor(VGA_WHITE); + display_setColor(VGA_BLUE); + display_print(selection, CENTER, 77); +} + +/* Asks the user if he really wants to enter mass storage mode */ +bool massStoragePrompt() +{ + //Title & Background + drawTitle((char *)"USB File Transfer"); + display_setColor(VGA_BLACK); + display_setFont(smallFont); + display_setBackColor(200, 200, 200); + display_print((char *)"Do you want to enter mass storage", CENTER, 65); + display_print((char *)"to transfer files to the PC?", CENTER, 85); + display_print((char *)"Do not use this for FW updates", CENTER, 105); + display_print((char *)"or for the USB serial connection.", CENTER, 125); + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(165, 160, 140, 55, (char *)"Yes"); + buttons_addButton(15, 160, 140, 55, (char *)"No"); + buttons_drawButtons(); + buttons_setTextFont(smallFont); + //Wait for touch release + while (touch_touched()) + ; + //Touch handler + while (true) + { + //If touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //YES + if (pressedButton == 0) + { + return true; + } + //NO + if (pressedButton == 1) + { + return false; + } + } + } +} + +/* Calibration Repeat Choose */ +bool calibrationRepeat() +{ + //Title & Background + mainMenuBackground(); + mainMenuTitle((char *)"Bad Calibration"); + display_setColor(VGA_BLACK); + display_setFont(bigFont); + display_setBackColor(200, 200, 200); + display_print((char *)"Try again?", CENTER, 66); + display_setFont(smallFont); + display_setBackColor(120, 120, 120); + display_setColor(VGA_WHITE); + display_print((char *)"Use different calibration objects", CENTER, 201); + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(165, 106, 140, 55, (char *)"Yes"); + buttons_addButton(15, 106, 140, 55, (char *)"No"); + buttons_drawButtons(); + //Touch handler + while (true) + { + //If touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //YES + if (pressedButton == 0) + { + return true; + } + //NO + if (pressedButton == 1) + { + return false; + } + } + } +} + +/* Calibration*/ +void calibrationScreen(bool firstStart) +{ + //Normal mode + if (firstStart == false) + { + mainMenuBackground(); + mainMenuTitle((char *)"Calibrating.."); + display_setColor(VGA_BLACK); + display_setBackColor(200, 200, 200); + display_setFont(smallFont); + display_print((char *)"Point the camera to different", CENTER, 63); + display_print((char *)"hot and cold objects in the area.", CENTER, 96); + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(90, 188, 140, 40, (char *)"Abort"); + buttons_drawButtons(); + display_setFont(bigFont); + display_print((char *)"Status: 0%", CENTER, 140); + } + //First start + else + { + display_fillScr(200, 200, 200); + display_setFont(bigFont); + display_setBackColor(200, 200, 200); + display_setColor(VGA_BLACK); + display_print((char *)"Calibrating..", CENTER, 100); + display_print((char *)"Status: 0%", CENTER, 140); + } +} + +/* Menu to add or remove temperature points to the thermal image */ +bool tempPointsMenu() +{ +redraw: + //Background + mainMenuBackground(); + //Title + mainMenuTitle((char *)"Temp. points"); + //Draw the selection menu + buttons_deleteAllButtons(); + buttons_setTextFont(smallFont); + buttons_addButton(15, 45, 90, 122, (char *)"Add"); + buttons_addButton(115, 45, 90, 122, (char *)"Remove"); + buttons_addButton(215, 45, 90, 122, (char *)"Clear"); + buttons_addButton(15, 188, 120, 40, (char *)"Back"); + buttons_drawButtons(); + //Save the current position inside the menu + while (true) + { + //Touch screen pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //Add + if (pressedButton == 0) + { + tempPointFunction(false); + goto redraw; + } + //Remove + if (pressedButton == 1) + { + tempPointFunction(true); + goto redraw; + } + //Clear + if (pressedButton == 2) + { + clearTempPoints(); + showFullMessage((char *)"All points cleared", true); + delay(1000); + goto redraw; + } + //BACK + if (pressedButton == 3) + return false; + } + } +} + +/* Select the color for the live mode string */ +void hotColdColorMenuString(int pos) +{ + char *text = (char *)""; + switch (pos) + { + //White + case 0: + text = (char *)"White"; + break; + //Black + case 1: + text = (char *)"Black"; + break; + //Red + case 2: + text = (char *)"Red"; + break; + //Green + case 3: + text = (char *)"Green"; + break; + //Blue + case 4: + text = (char *)"Blue"; + break; + } + mainMenuSelection(text); +} + +/* Menu to display the color in hot/cold color mode */ +bool hotColdColorMenu() +{ + //Save the current position inside the menu + byte hotColdColorMenuPos; + if (hotColdMode == hotColdMode_hot) + hotColdColorMenuPos = 2; + else if (hotColdMode == hotColdMode_cold) + hotColdColorMenuPos = 4; + else + hotColdColorMenuPos = 0; + //Background + mainMenuBackground(); + //Title + mainMenuTitle((char *)"Select color"); + //Draw the selection menu + drawSelectionMenu(); + //Draw the current item + hotColdColorMenuString(hotColdColorMenuPos); + //Save the current position inside the menu + while (true) + { + //Touch screen pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //SELECT + if (pressedButton == 3) + { + //Save + hotColdColor = hotColdColorMenuPos; + //Write to EEPROM + EEPROM.write(eeprom_hotColdColor, hotColdColor); + return true; + } + //BACK + if (pressedButton == 2) + return false; + //BACKWARD + else if (pressedButton == 0) + { + if (hotColdColorMenuPos > 0) + hotColdColorMenuPos--; + else if (hotColdColorMenuPos == 0) + hotColdColorMenuPos = 4; + } + //FORWARD + else if (pressedButton == 1) + { + if (hotColdColorMenuPos < 4) + hotColdColorMenuPos++; + else if (hotColdColorMenuPos == 4) + hotColdColorMenuPos = 0; + } + //Change the menu name + hotColdColorMenuString(hotColdColorMenuPos); + } + } +} + +/* Touch handler for the hot & cold limit changer menu */ +void hotColdChooserHandler() +{ + //Help variables + char margin[14]; + + //Display level as temperature + if (!tempFormat) + { + sprintf(margin, "Limit: %dC", hotColdLevel); + } + else + { + sprintf(margin, "Limit: %dF", hotColdLevel); + } + display_print(margin, CENTER, 153); + + //Touch handler + while (true) + { + waitTouch: + + //If touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //RESET + if (pressedButton == 0) + { + if (hotColdMode == hotColdMode_cold) + hotColdLevel = (int16_t)round( + rawToTemp( + 0.2 * (maxValue - minValue) + minValue)); + if (hotColdMode == hotColdMode_hot) + hotColdLevel = (int16_t)round( + rawToTemp( + 0.8 * (maxValue - minValue) + minValue)); + } + //SELECT + else if (pressedButton == 1) + { + //Save to EEPROM + EEPROM.write(eeprom_hotColdLevelHigh, + (hotColdLevel & 0xFF00) >> 8); + EEPROM.write(eeprom_hotColdLevelLow, hotColdLevel & 0x00FF); + break; + } + //MINUS + else if (pressedButton == 2) + { + if (hotColdLevel > round(rawToTemp(minValue))) + hotColdLevel--; + else + goto waitTouch; + } + //PLUS + else if (pressedButton == 3) + { + if (hotColdLevel < round(rawToTemp(maxValue))) + hotColdLevel++; + else + goto waitTouch; + } + + createThermalImg(true); + display_drawBitmap(80, 48, 160, 120, smallBuffer); + + //Display level as temperature + if (!tempFormat) + { + sprintf(margin, "Limit: %dC", hotColdLevel); + } + else + { + sprintf(margin, "Limit: %dF", hotColdLevel); + } + display_print(margin, CENTER, 153); + } + } +} + +/* Select the limit in hot/cold mode */ +void hotColdChooser() +{ + //Background & title + mainMenuBackground(); + mainMenuTitle((char *)"Set Limit"); + + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(15, 188, 140, 40, (char *)"Reset"); + buttons_addButton(165, 188, 140, 40, (char *)"OK"); + buttons_addButton(15, 48, 55, 120, (char *)"-"); + buttons_addButton(250, 48, 55, 120, (char *)"+"); + buttons_drawButtons(); + + //Draw the border for the preview image + display_setColor(VGA_BLACK); + display_drawRect(79, 47, 241, 169); + + //Set text color + display_setFont(smallFont); + display_setBackColor(VGA_TRANSPARENT); + changeTextColor(); + + //Find min and max values + if ((autoMode) && (!limitsLocked)) + { + autoMode = true; + createThermalImg(true); + autoMode = false; + } + + //Calculate initial level + if (hotColdMode == hotColdMode_cold) + hotColdLevel = (int16_t)round( + rawToTemp(0.2 * (maxValue - minValue) + minValue)); + if (hotColdMode == hotColdMode_hot) + hotColdLevel = (int16_t)round( + rawToTemp(0.8 * (maxValue - minValue) + minValue)); + + createThermalImg(true); + display_drawBitmap(80, 48, 160, 120, smallBuffer); + + //Go into the normal touch handler + hotColdChooserHandler(); +} + +/* Menu to display hot or cold areas */ +bool hotColdMenu() +{ +redraw: + //Background + mainMenuBackground(); + + //Title + mainMenuTitle((char *)"Hot / Cold"); + + //Draw the selection menu + buttons_deleteAllButtons(); + buttons_setTextFont(smallFont); + buttons_addButton(15, 45, 90, 122, (char *)"Hot"); + buttons_addButton(115, 45, 90, 122, (char *)"Cold"); + buttons_addButton(215, 45, 90, 122, (char *)"Disabled"); + buttons_addButton(15, 188, 120, 40, (char *)"Back"); + buttons_drawButtons(); + + //Save the current position inside the menu + while (true) + { + //Touch screen pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //Hot + if (pressedButton == 0) + { + //Set marker to hot + hotColdMode = hotColdMode_hot; + + //Choose the color + if (hotColdColorMenu()) + //Set the limit + hotColdChooser(); + + //Go back + else + { + hotColdMode = hotColdMode_disabled; + goto redraw; + } + + //Leave loop + break; + } + //Cold + if (pressedButton == 1) + { + //Set marker to cold + hotColdMode = hotColdMode_cold; + + //Choose the color + if (hotColdColorMenu()) + //Set the limit + hotColdChooser(); + + //Go back + else + { + hotColdMode = hotColdMode_disabled; + goto redraw; + } + + //Leave loop + break; + } + //Disabled + if (pressedButton == 2) + { + //Set marker to disabled + hotColdMode = hotColdMode_disabled; + + //Leave loop + break; + } + //Back to main menu + if (pressedButton == 3) + return false; + } + } + + //Write to EEPROM + EEPROM.write(eeprom_hotColdMode, hotColdMode); + + //Disable auto FFC for isotherm mode + if (hotColdMode != hotColdMode_disabled) + lepton_ffcMode(false); + //Enable it when isotherm disabled + else + lepton_ffcMode(true); + + //Go back + return true; +} + +/* Switch the current preset menu item */ +void tempLimitsPresetSaveString(int pos) +{ + char *text = (char *)""; + switch (pos) + { + case 0: + text = (char *)"Temporary"; + break; + case 1: + text = (char *)"Preset 1"; + break; + case 2: + text = (char *)"Preset 2"; + break; + case 3: + text = (char *)"Preset 3"; + break; + } + mainMenuSelection(text); +} + +/* Menu to save the temperature limits to a preset */ +bool tempLimitsPresetSaveMenu() +{ + //Save the current position inside the menu + byte menuPos = 1; + //Background + mainMenuBackground(); + //Title + mainMenuTitle((char *)"Select Preset"); + //Draw the selection menu + drawSelectionMenu(); + //Draw the current item + tempLimitsPresetSaveString(menuPos); + //Save the current position inside the menu + while (true) + { + //Touch screen pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //SELECT + if (pressedButton == 3) + { + uint8_t farray[4]; + float calComp = -273.15; + + switch (menuPos) + { + //Temporary + case 0: + EEPROM.write(eeprom_minMaxPreset, minMax_temporary); + break; + //Preset 1 + case 1: + EEPROM.write(eeprom_minValue1High, + (minValue & 0xFF00) >> 8); + EEPROM.write(eeprom_minValue1Low, minValue & 0x00FF); + EEPROM.write(eeprom_maxValue1High, + (maxValue & 0xFF00) >> 8); + EEPROM.write(eeprom_maxValue1Low, maxValue & 0x00FF); + floatToBytes(farray, (float)calComp); + for (int i = 0; i < 4; i++) + EEPROM.write(eeprom_minMax1Comp + i, (farray[i])); + EEPROM.write(eeprom_minMax1Set, eeprom_setValue); + EEPROM.write(eeprom_minMaxPreset, minMax_preset1); + break; + //Preset 2 + case 2: + EEPROM.write(eeprom_minValue2High, + (minValue & 0xFF00) >> 8); + EEPROM.write(eeprom_minValue2Low, minValue & 0x00FF); + EEPROM.write(eeprom_maxValue2High, + (maxValue & 0xFF00) >> 8); + EEPROM.write(eeprom_maxValue2Low, maxValue & 0x00FF); + floatToBytes(farray, (float)calComp); + for (int i = 0; i < 4; i++) + EEPROM.write(eeprom_minMax2Comp + i, (farray[i])); + EEPROM.write(eeprom_minMax2Set, eeprom_setValue); + EEPROM.write(eeprom_minMaxPreset, minMax_preset2); + break; + //Preset 3 + case 3: + EEPROM.write(eeprom_minValue3High, + (minValue & 0xFF00) >> 8); + EEPROM.write(eeprom_minValue3Low, minValue & 0x00FF); + EEPROM.write(eeprom_maxValue3High, + (maxValue & 0xFF00) >> 8); + EEPROM.write(eeprom_maxValue3Low, maxValue & 0x00FF); + floatToBytes(farray, (float)calComp); + for (int i = 0; i < 4; i++) + EEPROM.write(eeprom_minMax3Comp + i, (farray[i])); + EEPROM.write(eeprom_minMax3Set, eeprom_setValue); + EEPROM.write(eeprom_minMaxPreset, minMax_preset3); + break; + } + return true; + } + //BACKWARD + else if (pressedButton == 0) + { + if (menuPos > 0) + menuPos--; + else if (menuPos == 0) + menuPos = 3; + } + //FORWARD + else if (pressedButton == 1) + { + if (menuPos < 3) + menuPos++; + else if (menuPos == 3) + menuPos = 0; + } + //BACK + else if (pressedButton == 2) + return false; + //Change the menu name + tempLimitsPresetSaveString(menuPos); + } + } +} + +/* Touch Handler for the limit chooser menu */ +bool tempLimitsManualHandler() +{ + + //Set both modes to false for the first time + bool minChange = false; + bool maxChange = false; + //Buffer + int min, max; + char minC[10]; + char maxC[10]; + + //Touch handler + while (true) + { + //Set font & text color + display_setFont(smallFont); + display_setBackColor(VGA_TRANSPARENT); + changeTextColor(); + + //Update minimum & maximum + min = (int)round(rawToTemp(minValue)); + max = (int)round(rawToTemp(maxValue)); + if (tempFormat == tempFormat_celcius) + { + sprintf(minC, "Min:%dC", min); + sprintf(maxC, "Max:%dC", max); + } + else + { + sprintf(minC, "Min:%dF", min); + sprintf(maxC, "Max:%dF", max); + } + display_print(maxC, 180, 153); + display_print(minC, 85, 153); + display_setFont(bigFont); + + //If touch pressed + if (touch_touched() == true) + { + int pressedButton; + //Change values continously and fast when the user holds the plus or minus button + if (minChange || maxChange) + pressedButton = buttons_checkButtons(true, true); + //Normal check when not in minChange or maxChange mode + else + pressedButton = buttons_checkButtons(); + //RESET + if (pressedButton == 0) + { + autoMode = true; + createThermalImg(true); + autoMode = false; + } + //SELECT + else if (pressedButton == 1) + { + //Leave the minimum or maximum change mode + if (minChange || maxChange) + { + buttons_relabelButton(1, (char *)"OK", true); + buttons_relabelButton(2, (char *)"Min", true); + buttons_relabelButton(3, (char *)"Max", true); + if (minChange == true) + minChange = false; + if (maxChange == true) + maxChange = false; + } + //Go back + else + { + if (tempLimitsPresetSaveMenu()) + return true; + else + return false; + } + } + //DECREASE + else if (pressedButton == 2) + { + //In minimum change mode - decrease minimum temp + if ((minChange == true) && (maxChange == false)) + { + //Check if minimum is in range + if (min > -70) + { + min--; + minValue = tempToRaw(min); + } + } + //Enter minimum change mode + else if ((minChange == false) && (maxChange == false)) + { + buttons_relabelButton(1, (char *)"Back", true); + buttons_relabelButton(2, (char *)"-", true); + buttons_relabelButton(3, (char *)"+", true); + minChange = true; + } + //In maximum change mode - decrease maximum temp + else if ((minChange == false) && (maxChange == true)) + { + //Check if maximum is bigger than minimum + if (max > (min + 1)) + { + max--; + maxValue = tempToRaw(max); + } + } + } + //INCREASE + else if (pressedButton == 3) + { + //In maximum change mode - increase maximum temp + if ((minChange == false) && (maxChange == true)) + { + //Check if maximum is in range + if (max < 380) + { + max++; + maxValue = tempToRaw(max); + } + } + //Enter maximum change mode + else if ((minChange == false) && (maxChange == false)) + { + buttons_relabelButton(1, (char *)"Back", true); + buttons_relabelButton(2, (char *)"-", true); + buttons_relabelButton(3, (char *)"+", true); + maxChange = true; + } + //In minimum change mode - increase minimum temp + else if ((minChange == true) && (maxChange == false)) + { + //Check if minimum is smaller than maximum + if (min < (max - 1)) + { + min++; + minValue = tempToRaw(min); + } + } + } + + createThermalImg(true); + display_drawBitmap(80, 48, 160, 120, smallBuffer); + } + } +} + +/* Select the limits in Manual Mode*/ +void tempLimitsManual() +{ +redraw: + //Background & title + mainMenuBackground(); + mainMenuTitle((char *)"Temp. Limits"); + + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(15, 188, 140, 40, (char *)"Reset"); + buttons_addButton(165, 188, 140, 40, (char *)"OK"); + buttons_addButton(15, 48, 55, 120, (char *)"Min"); + buttons_addButton(250, 48, 55, 120, (char *)"Max"); + buttons_drawButtons(); + + //Prepare the preview image + autoMode = true; + createThermalImg(true); + autoMode = false; + + //Display the preview image + display_drawBitmap(80, 48, 160, 120, smallBuffer); + + //Draw the border for the preview image + display_setColor(VGA_BLACK); + display_drawRect(79, 47, 241, 169); + + //Go into the normal touch handler + if (!tempLimitsManualHandler()) + goto redraw; +} + +/* Switch the temperature limits preset string */ +void tempLimitsPresetsString(int pos) +{ + char *text = (char *)""; + switch (pos) + { + case 0: + text = (char *)"New"; + break; + case 1: + text = (char *)"Preset 1"; + break; + case 2: + text = (char *)"Preset 2"; + break; + case 3: + text = (char *)"Preset 3"; + break; + } + mainMenuSelection(text); +} + +/* Menu to save the temperature limits to a preset */ +bool tempLimitsPresets() +{ + //Save the current position inside the menu + byte tempLimitsMenuPos = 0; + //Background + mainMenuBackground(); + //Title + mainMenuTitle((char *)"Choose Preset"); + //Draw the selection menu + drawSelectionMenu(); + //Draw the current item + tempLimitsPresetsString(tempLimitsMenuPos); + //Save the current position inside the menu + while (true) + { + //Touch screen pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //SELECT + if (pressedButton == 3) + { + switch (tempLimitsMenuPos) + { + //New + case 0: + tempLimitsManual(); + return true; + //Load Preset 1 + case 1: + if (EEPROM.read(eeprom_minMax1Set) == eeprom_setValue) + EEPROM.write(eeprom_minMaxPreset, minMax_preset1); + else + { + showFullMessage((char *)"Preset 1 not saved", true); + delay(1000); + return false; + } + break; + //Load Preset 2 + case 2: + if (EEPROM.read(eeprom_minMax2Set) == eeprom_setValue) + EEPROM.write(eeprom_minMaxPreset, minMax_preset2); + else + { + showFullMessage((char *)"Preset 2 not saved", true); + delay(1000); + return false; + } + break; + //Load Preset 3 + case 3: + if (EEPROM.read(eeprom_minMax2Set) == eeprom_setValue) + EEPROM.write(eeprom_minMaxPreset, minMax_preset3); + else + { + showFullMessage((char *)"Preset 3 not saved", true); + delay(1000); + return false; + } + break; + } + //Read temperature limits from EEPROM + readTempLimits(); + return true; + } + //BACKWARD + else if (pressedButton == 0) + { + if (tempLimitsMenuPos > 0) + tempLimitsMenuPos--; + else if (tempLimitsMenuPos == 0) + tempLimitsMenuPos = 3; + } + //FORWARD + else if (pressedButton == 1) + { + if (tempLimitsMenuPos < 3) + tempLimitsMenuPos++; + else if (tempLimitsMenuPos == 3) + tempLimitsMenuPos = 0; + } + //BACK + else if (pressedButton == 2) + return false; + //Change the menu name + tempLimitsPresetsString(tempLimitsMenuPos); + } + } +} + +/* Temperature Limit Mode Selection */ +bool tempLimits() +{ + //Title & Background + mainMenuBackground(); + mainMenuTitle((char *)"Temp. Limits"); + + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(15, 47, 140, 120, (char *)"Auto"); + buttons_addButton(165, 47, 140, 120, (char *)"Manual"); + buttons_addButton(15, 188, 140, 40, (char *)"Back"); + buttons_drawButtons(); + + //Touch handler + while (true) + { + //If touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //AUTO + if (pressedButton == 0) + { + //Show message + showFullMessage((char *)"Please wait..", true); + + //Enable auto mode again and disable limits locked + autoMode = true; + limitsLocked = false; + + //Set temperature presets to temporary, so it does not load + EEPROM.write(eeprom_minMaxPreset, minMax_temporary); + + //Enable auto FFC + lepton_ffcMode(true); + + //Go back + return true; + } + + //MANUAL + else if (pressedButton == 1) + { + //Disable auto mode and limits locked + autoMode = false; + limitsLocked = false; + + //Let the user choose the new limits + return tempLimitsPresets(); + } + + //BACK + else if (pressedButton == 2) + return false; + } + } +} + +/* Switch the current display option item */ +void liveDispMenuString(int pos) +{ + char *text = (char *)""; + switch (pos) + { + //Battery + case 0: + if (batteryEnabled) + text = (char *)"Battery On"; + else + text = (char *)"Battery Off"; + break; + //Time + case 1: + if (timeEnabled) + text = (char *)"Time On"; + else + text = (char *)"Time Off"; + break; + //Date + case 2: + if (dateEnabled) + text = (char *)"Date On"; + else + text = (char *)"Date Off"; + break; + //Spot + case 3: + if (spotEnabled) + text = (char *)"Spot On"; + else + text = (char *)"Spot Off"; + break; + //Colorbar + case 4: + if (colorbarEnabled) + text = (char *)"Bar On"; + else + text = (char *)"Bar Off"; + break; + //Storage + case 5: + if (storageEnabled) + text = (char *)"Storage On"; + else + text = (char *)"Storage Off"; + break; + //Filter + case 6: + if (filterType == filterType_box) + text = (char *)"Box-Filter"; + else if (filterType == filterType_gaussian) + text = (char *)"Gaus-Filter"; + else + text = (char *)"No Filter"; + break; + //Text Color + case 7: + if (textColor == textColor_black) + text = (char *)"Black Text"; + else if (textColor == textColor_red) + text = (char *)"Red Text"; + else if (textColor == textColor_green) + text = (char *)"Green Text"; + else if (textColor == textColor_blue) + text = (char *)"Blue Text"; + else + text = (char *)"White Text"; + break; + //Hottest or coldest + case 8: + if (minMaxPoints == minMaxPoints_disabled) + text = (char *)"No Cold/Hot"; + else if (minMaxPoints == minMaxPoints_min) + text = (char *)"Coldest"; + else if (minMaxPoints == minMaxPoints_max) + text = (char *)"Hottest"; + else + text = (char *)"Both C/H"; + break; + } + mainMenuSelection(text); +} + +/* Change the live display options */ +bool liveDispMenu() +{ + //Save the current position inside the menu + static byte displayOptionsPos = 0; + //Background + mainMenuBackground(); + //Title + mainMenuTitle((char *)"Live Disp. Options"); + //Draw the selection menu + drawSelectionMenu(); + //Rename OK button + buttons_relabelButton(3, (char *)"Switch", true); + //Draw the current item + liveDispMenuString(displayOptionsPos); + while (true) + { + //Touch screen pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //SELECT + if (pressedButton == 3) + { + changeDisplayOptions(&displayOptionsPos); + } + //BACK + if (pressedButton == 2) + { + return false; + } + //BACKWARD + else if (pressedButton == 0) + { + if (displayOptionsPos > 0) + displayOptionsPos--; + else if (displayOptionsPos == 0) + displayOptionsPos = 8; + } + //FORWARD + else if (pressedButton == 1) + { + if (displayOptionsPos < 8) + displayOptionsPos++; + else if (displayOptionsPos == 8) + displayOptionsPos = 0; + } + //Change the menu name + liveDispMenuString(displayOptionsPos); + } + } +} + +/* Switch the current color scheme item */ +void colorMenuString(int pos) +{ + char *text = (char *)""; + switch (pos) + { + case colorScheme_arctic: + text = (char *)"Arctic"; + break; + case colorScheme_blackHot: + text = (char *)"Black-Hot"; + break; + case colorScheme_blueRed: + text = (char *)"Blue-Red"; + break; + case colorScheme_coldest: + text = (char *)"Coldest"; + break; + case colorScheme_contrast: + text = (char *)"Contrast"; + break; + case colorScheme_doubleRainbow: + text = (char *)"Double-Rain"; + break; + case colorScheme_grayRed: + text = (char *)"Gray-Red"; + break; + case colorScheme_glowBow: + text = (char *)"Glowbow"; + break; + case colorScheme_grayscale: + text = (char *)"Grayscale"; + break; + case colorScheme_hottest: + text = (char *)"Hottest"; + break; + case colorScheme_ironblack: + text = (char *)"Ironblack"; + break; + case colorScheme_lava: + text = (char *)"Lava"; + break; + case colorScheme_medical: + text = (char *)"Medical"; + break; + case colorScheme_rainbow: + text = (char *)"Rainbow"; + break; + case colorScheme_wheel1: + text = (char *)"Wheel 1"; + break; + case colorScheme_wheel2: + text = (char *)"Wheel 2"; + break; + case colorScheme_wheel3: + text = (char *)"Wheel 3"; + break; + case colorScheme_whiteHot: + text = (char *)"White-Hot"; + break; + case colorScheme_yellow: + text = (char *)"Yellow"; + break; + } + mainMenuSelection(text); +} + +/* Choose the applied color scale */ +bool colorMenu() +{ + //Save the current position inside the menu + byte changeColorPos = colorScheme; + //Background + mainMenuBackground(); + //Title + mainMenuTitle((char *)"Change Color"); + //Draw the selection menu + drawSelectionMenu(); + //Draw the current item + colorMenuString(changeColorPos); + while (true) + { + //Touch screen pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //SELECT + if (pressedButton == 3) + { + changeColorScheme(&changeColorPos); + return true; + } + //BACK + if (pressedButton == 2) + return false; + //BACKWARD + else if (pressedButton == 0) + { + if (changeColorPos > 0) + changeColorPos--; + else if (changeColorPos == 0) + changeColorPos = colorSchemeTotal - 1; + } + //FORWARD + else if (pressedButton == 1) + { + if (changeColorPos < (colorSchemeTotal - 1)) + changeColorPos++; + else if (changeColorPos == (colorSchemeTotal - 1)) + changeColorPos = 0; + } + //Change the menu name + colorMenuString(changeColorPos); + } + } +} + +/* Draws the content of the main menu*/ +void drawMainMenu(byte pos) +{ + //Border + drawMainMenuBorder(); + //Background + display_setColor(200, 200, 200); + display_fillRoundRect(6, 6, 314, 234); + //Buttons + buttons_deleteAllButtons(); + + //Page 1 + if (pos == 0) + { + //1.1 Temperature points + buttons_addButton(23, 28, 80, 80, iconTempPointsBMP, iconTempPointsPalette); + //1.2 Temperature limits + buttons_addButton(120, 28, 80, 80, iconTempLimitsBMP, iconTempLimitsPalette); + //1.3 Hot / Cold + buttons_addButton(217, 28, 80, 80, iconHotColdBMP, iconHotColdPalette); + } + + //Page 2 + if (pos == 1) + { + //2.1 Load menu + buttons_addButton(23, 28, 80, 80, iconLoadMenuBMP, iconLoadMenuPalette); + //2.2 Settings menu + buttons_addButton(120, 28, 80, 80, iconSettingsMenuBMP, iconSettingsMenuPalette); + //2.3 Change color + buttons_addButton(217, 28, 80, 80, iconChangeColorBMP, iconChangeColorPalette); + } + + //Page 3 + if (pos == 2) + { + //3.1 Display settings + buttons_addButton(23, 28, 80, 80, iconDisplaySettingsBMP, iconDisplaySettingsPalette); + //3.2 Trigger shutter + buttons_addButton(120, 28, 80, 80, iconShutterBMP, iconShutterPalette); + //3.3 Display off for radiometric + buttons_addButton(217, 28, 80, 80, iconDisplayOffBMP, iconDisplayOffPalette); + } + + buttons_addButton(23, 132, 80, 80, iconBWBitmap, iconBWColors); + buttons_addButton(120, 132, 80, 80, iconReturnBitmap, iconReturnColors); + buttons_addButton(217, 132, 80, 80, iconFWBitmap, iconFWColors); + buttons_drawButtons(); +} + +/* Select the action when the select button is pressed */ +bool mainMenuSelect(byte pos, byte page) +{ + //Page 1 + if (page == 0) + { + //1.1 Temperature points + if (pos == 0) + { + return tempPointsMenu(); + } + //1.2 Temperature limits + if (pos == 1) + { + return tempLimits(); + } + //1.3 Hot / Cold + if (pos == 2) + { + return hotColdMenu(); + } + } + + //Page 2 + if (page == 1) + { + //2.1 Load menu + if (pos == 0) + { + loadFiles(); + } + //2.2 Settings menu + if (pos == 1) + { + settingsMenu(); + settingsMenuHandler(); + } + //2.3 Change color + if (pos == 2) + { + return colorMenu(); + } + } + + //Page 3 + if (page == 2) + { + //3.1 Display settings + if (pos == 0) + { + return liveDispMenu(); + } + //3.2 Trigger shutter + if (pos == 1) + { + lepton_ffc(true); + } + //3.3 Display off + if (pos == 2) + { + toggleDisplay(); + } + } + + return false; +} + +/* Touch Handler for the Live Menu */ +void mainMenuHandler(byte *pos) +{ + int numPages = 3; + + //Main loop + while (true) + { + //Check for screen sleep + if (screenOffCheck()) + drawMainMenu(*pos); + + //Touch screen pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //FIRST BUTTON + if (pressedButton == 0) + { + //Leave menu + if (mainMenuSelect(0, *pos)) + break; + } + //SECOND BUTTON + if (pressedButton == 1) + { + //Leave menu + if (mainMenuSelect(1, *pos)) + break; + } + //THIRD BUTTON + if (pressedButton == 2) + { + //Leave menu + if (mainMenuSelect(2, *pos)) + break; + } + //BACKWARD + else if (pressedButton == 3) + { + if (*pos > 0) + *pos = *pos - 1; + else if (*pos == 0) + *pos = numPages - 1; + } + //EXIT + if (pressedButton == 4) + { + showFullMessage((char *)"Please wait..", true); + return; + } + //FORWARD + else if (pressedButton == 5) + { + if (*pos < numPages - 1) + *pos = *pos + 1; + else if (*pos == numPages - 1) + *pos = 0; + } + drawMainMenu(*pos); + } + } +} + +/* Start live menu */ +void mainMenu() +{ + //Set show menu to opened + showMenu = showMenu_opened; + + //Position in the main menu + static byte mainMenuPos = 0; + + //Draw content + drawMainMenu(mainMenuPos); + + //Touch handler - return true if exit to Main menu, otherwise false + mainMenuHandler(&mainMenuPos); + + //Restore old fonts + display_setFont(smallFont); + buttons_setTextFont(smallFont); + + //Delete the old buttons + buttons_deleteAllButtons(); + + //Wait a short time + delay(500); + Serial.clear(); + showMenu = showMenu_disabled; + lepton_startFrame(); +} diff --git a/Firmware_V3/src/gui/settingsmenu.cpp b/Firmware_V3/src/gui/settingsmenu.cpp new file mode 100644 index 0000000..bdfe14f --- /dev/null +++ b/Firmware_V3/src/gui/settingsmenu.cpp @@ -0,0 +1,1053 @@ +/* +* +* SETTINGS MENU - Adjust different on-device settings +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Second Menu */ +void secondMenu(bool firstStart) +{ + drawTitle((char *)"Second", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 70, 70, (char *)"-"); + buttons_addButton(230, 60, 70, 70, (char *)"+"); + buttons_addButton(20, 150, 280, 70, (char *)"Back"); + buttons_drawButtons(); + drawCenterElement(second()); + //Touch handler + while (true) + { + //touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //Minus + if (pressedButton == 0) + { + if (second() >= 0) + { + if (second() > 0) + setTime(hour(), minute(), second() - 1, day(), month(), + year()); + else if (second() == 0) + setTime(hour(), minute(), 59, day(), month(), year()); + drawCenterElement(second()); + } + } + //Plus + else if (pressedButton == 1) + { + if (second() <= 59) + { + if (second() < 59) + setTime(hour(), minute(), second() + 1, day(), month(), + year()); + else if (second() == 59) + setTime(hour(), minute(), 0, day(), month(), year()); + drawCenterElement(second()); + } + } + //Back + else if (pressedButton == 2) + { + Teensy3Clock.set(now()); + timeMenu(firstStart); + break; + } + } + } +} + +/* Minute Menu */ +void minuteMenu(bool firstStart) +{ + drawTitle((char *)"Minute", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 70, 70, (char *)"-"); + buttons_addButton(230, 60, 70, 70, (char *)"+"); + buttons_addButton(20, 150, 280, 70, (char *)"Back"); + buttons_drawButtons(); + drawCenterElement(minute()); + //Touch handler + while (true) + { + //touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //Minus + if (pressedButton == 0) + { + if (minute() >= 0) + { + if (minute() > 0) + setTime(hour(), minute() - 1, second(), day(), month(), + year()); + else if (minute() == 0) + setTime(hour(), 59, second(), day(), month(), year()); + drawCenterElement(minute()); + } + } + //Plus + else if (pressedButton == 1) + { + if (minute() <= 59) + { + if (minute() < 59) + setTime(hour(), minute() + 1, second(), day(), month(), + year()); + else if (minute() == 59) + setTime(hour(), 0, second(), day(), month(), year()); + drawCenterElement(minute()); + } + } + //Back + else if (pressedButton == 2) + { + Teensy3Clock.set(now()); + timeMenu(firstStart); + break; + } + } + } +} + +/* Hour menu */ +void hourMenu(bool firstStart) +{ + drawTitle((char *)"Hour", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 70, 70, (char *)"-"); + buttons_addButton(230, 60, 70, 70, (char *)"+"); + buttons_addButton(20, 150, 280, 70, (char *)"Back"); + buttons_drawButtons(); + drawCenterElement(hour()); + //Touch handler + while (true) + { + //touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //Minus + if (pressedButton == 0) + { + if (hour() >= 0) + { + if (hour() > 0) + setTime(hour() - 1, minute(), second(), day(), month(), + year()); + else if (hour() == 0) + setTime(23, minute(), second(), day(), month(), year()); + drawCenterElement(hour()); + } + } + //Plus + else if (pressedButton == 1) + { + if (hour() <= 23) + { + if (hour() < 23) + setTime(hour() + 1, minute(), second(), day(), month(), + year()); + else if (hour() == 23) + setTime(0, minute(), second(), day(), month(), year()); + drawCenterElement(hour()); + } + } + //Back + else if (pressedButton == 2) + { + Teensy3Clock.set(now()); + timeMenu(firstStart); + break; + } + } + } +} + +/* Day Menu */ +void dayMenu(bool firstStart) +{ + drawTitle((char *)"Day", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 70, 70, (char *)"-"); + buttons_addButton(230, 60, 70, 70, (char *)"+"); + buttons_addButton(20, 150, 280, 70, (char *)"Back"); + buttons_drawButtons(); + drawCenterElement(day()); + //Touch handler + while (true) + { + //touch press + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //Minus + if (pressedButton == 0) + { + if (day() >= 1) + { + if (day() > 1) + setTime(hour(), minute(), second(), day() - 1, month(), + year()); + else if (day() == 1) + setTime(hour(), minute(), second(), 31, month(), + year()); + drawCenterElement(day()); + } + } + //Plus + else if (pressedButton == 1) + { + if (day() <= 31) + { + if (day() < 31) + setTime(hour(), minute(), second(), day() + 1, month(), + year()); + else if (day() == 31) + setTime(hour(), minute(), second(), 1, month(), year()); + drawCenterElement(day()); + } + } + //Back + else if (pressedButton == 2) + { + Teensy3Clock.set(now()); + dateMenu(firstStart); + break; + } + } + } +} + +/* Month Menu */ +void monthMenu(bool firstStart) +{ + drawTitle((char *)"Month", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 70, 70, (char *)"-"); + buttons_addButton(230, 60, 70, 70, (char *)"+"); + buttons_addButton(20, 150, 280, 70, (char *)"Back"); + buttons_drawButtons(); + drawCenterElement(month()); + //Touch handler + while (true) + { + //touch press + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //Minus + if (pressedButton == 0) + { + if (month() >= 1) + { + if (month() > 1) + setTime(hour(), minute(), second(), day(), month() - 1, + year()); + else if (month() == 1) + setTime(hour(), minute(), second(), day(), 12, year()); + drawCenterElement(month()); + } + } + //Plus + else if (pressedButton == 1) + { + if (month() <= 12) + { + if (month() < 12) + setTime(hour(), minute(), second(), day(), month() + 1, + year()); + else if (month() == 12) + setTime(hour(), minute(), second(), day(), 1, year()); + drawCenterElement(month()); + } + } + //Back + else if (pressedButton == 2) + { + Teensy3Clock.set(now()); + dateMenu(firstStart); + break; + } + } + } +} + +/* Year Menu */ +void yearMenu(bool firstStart) +{ + drawTitle((char *)"Year", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 70, 70, (char *)"-"); + buttons_addButton(230, 60, 70, 70, (char *)"+"); + buttons_addButton(20, 150, 280, 70, (char *)"Back"); + buttons_drawButtons(); + drawCenterElement(year()); + //Touch handler + while (true) + { + //touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //Minus + if (pressedButton == 0) + { + if (year() > 2021) + { + setTime(hour(), minute(), second(), day(), month(), + year() - 1); + drawCenterElement(year()); + } + } + //Plus + else if (pressedButton == 1) + { + if (year() < 2099) + { + setTime(hour(), minute(), second(), day(), month(), + year() + 1); + drawCenterElement(year()); + } + } + //Back + else if (pressedButton == 2) + { + Teensy3Clock.set(now()); + dateMenu(firstStart); + break; + } + } + } +} + +/* Calibrate the battery gauge */ +void batteryGauge() +{ + //Title & Background + drawTitle((char *)"Battery Gauge", true); + display_setColor(VGA_BLACK); + display_setFont(smallFont); + display_setBackColor(200, 200, 200); + display_print((char *)"Do you want to calibrate the", CENTER, 75); + display_print((char *)"battery gauge? Fully charge the", CENTER, 95); + display_print((char *)"battery first (LED green/blue).", CENTER, 115); + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(165, 160, 140, 55, (char *)"Yes"); + buttons_addButton(15, 160, 140, 55, (char *)"No"); + buttons_drawButtons(); + buttons_setTextFont(smallFont); + ; + //Touch handler + while (true) + { + //If touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //YES + if (pressedButton == 0) + { + //Calc the compensation value + checkBattery(false, true); + + //Show Message + showFullMessage((char *)"Battery gauge calibrated", true); + delay(1000); + break; + } + + //NO + else if (pressedButton == 1) + { + break; + } + } + } + + hardwareMenu(); +} + +/* Date Menu */ +void dateMenu(bool firstStart) +{ + drawTitle((char *)"Date", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 130, 70, (char *)"Day"); + buttons_addButton(170, 60, 130, 70, (char *)"Month"); + buttons_addButton(20, 150, 130, 70, (char *)"Year"); + if (firstStart) + buttons_addButton(170, 150, 130, 70, (char *)"Save"); + else + buttons_addButton(170, 150, 130, 70, (char *)"Back"); + buttons_drawButtons(); +} + +/* Date Menu Handler */ +void dateMenuHandler(bool firstStart) +{ + while (true) + { + //touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(); + //Day + if (pressedButton == 0) + { + dayMenu(firstStart); + } + //Month + else if (pressedButton == 1) + { + monthMenu(firstStart); + } + //Year + else if (pressedButton == 2) + { + yearMenu(firstStart); + } + //Back + else if (pressedButton == 3) + { + if (!firstStart) + generalMenu(); + break; + } + } + } +} + +/* Time Menu */ +void timeMenu(bool firstStart) +{ + drawTitle((char *)"Time", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 130, 70, (char *)"Hour"); + buttons_addButton(170, 60, 130, 70, (char *)"Minute"); + buttons_addButton(20, 150, 130, 70, (char *)"Second"); + if (firstStart) + buttons_addButton(170, 150, 130, 70, (char *)"Save"); + else + buttons_addButton(170, 150, 130, 70, (char *)"Back"); + buttons_drawButtons(); +} + +/* Time Menu Handler */ +void timeMenuHandler(bool firstStart) +{ + while (true) + { + //touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(); + //Hours + if (pressedButton == 0) + { + hourMenu(firstStart); + } + //Minutes + else if (pressedButton == 1) + { + minuteMenu(firstStart); + } + //Seconds + else if (pressedButton == 2) + { + secondMenu(firstStart); + } + //Back + else if (pressedButton == 3) + { + if (!firstStart) + generalMenu(); + break; + } + } + } +} + +/* General Menu */ +void generalMenu() +{ + drawTitle((char *)"Other Settings"); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 130, 70, (char *)"Time"); + buttons_addButton(170, 60, 130, 70, (char *)"Date"); + buttons_addButton(20, 150, 130, 70, (char *)"BMP Conversion"); + buttons_addButton(170, 150, 130, 70, (char *)"Back"); + buttons_drawButtons(); +} + +/* General Menu Handler */ +void generalMenuHandler() +{ + while (true) + { + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(); + + //Time + if (pressedButton == 0) + { + timeMenu(); + timeMenuHandler(); + } + + //Date + else if (pressedButton == 1) + { + dateMenu(); + dateMenuHandler(); + } + + //BMP Conversion + else if (pressedButton == 2) + { + convertImageMenu(); + } + + //Back + else if (pressedButton == 3) + { + settingsMenu(); + break; + } + } + } +} + +/* Convert image selection menu */ +void convertImageMenu(bool firstStart) +{ + drawTitle((char *)"BMP Conversion", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 130, 70, (char *)"Disabled"); + buttons_addButton(170, 60, 130, 70, (char *)"Enabled"); + if (firstStart) + { + buttons_addButton(20, 150, 280, 70, (char *)"Set"); + convertEnabled = false; + } + else + buttons_addButton(20, 150, 280, 70, (char *)"Back"); + buttons_drawButtons(); + if (!convertEnabled) + buttons_setActive(0); + else + buttons_setActive(1); + + //Touch handler + while (true) + { + //touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(); + //Diabled + if (pressedButton == 0) + { + if (convertEnabled) + { + convertEnabled = false; + buttons_setActive(0); + buttons_setInactive(1); + } + } + //Enabled + else if (pressedButton == 1) + { + if (!convertEnabled) + { + convertEnabled = true; + buttons_setActive(1); + buttons_setInactive(0); + } + } + + //Save + else if (pressedButton == 2) + { + //Write new settings to EEPROM + EEPROM.write(eeprom_convertEnabled, convertEnabled); + + if (!firstStart) + generalMenu(); + return; + } + } + } +} + +/* Asks the user if he really wants to format */ +void formatStorage() +{ + //Title & Background + drawTitle((char *)"Storage"); + display_setColor(VGA_BLACK); + display_setFont(smallFont); + display_setBackColor(200, 200, 200); + display_print((char *)"Do you really want to format?", CENTER, 66); + display_print((char *)"This will delete all images", CENTER, 105); + display_print((char *)"and videos on the internal storage.", CENTER, 125); + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(165, 160, 140, 55, (char *)"Yes"); + buttons_addButton(15, 160, 140, 55, (char *)"No"); + buttons_drawButtons(); + buttons_setTextFont(smallFont); + //Touch handler + while (true) + { + //If touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //YES + if (pressedButton == 0) + { + showFullMessage((char *)"Format storage..", true); + if (!formatCard()) + { + showFullMessage((char *)"Error during formatting!", true); + delay(1000); + break; + } + refreshFreeSpace(); + showFullMessage((char *)"Formatting finished", true); + delay(1000); + break; + } + //NO + if (pressedButton == 1) + { + break; + } + } + } + + hardwareMenu(); +} + +void changeLeptonGain() +{ + /* Generate menu */ + drawTitle((char *)"Lepton Gain"); + buttons_deleteAllButtons(); + buttons_setTextFont(smallFont); + buttons_addButton(20, 60, 130, 70, (char *)"-10C - +140C"); + buttons_addButton(170, 60, 130, 70, (char *)"-10C - +450C"); + buttons_addButton(20, 150, 280, 70, (char *)"Save"); + buttons_drawButtons(); + if (leptonGainMode == lepton_gain_high) + buttons_setActive(0); + else + buttons_setActive(1); + //Touch handler + while (true) + { + //touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(); + //High gain + if (pressedButton == 0) + { + if (leptonGainMode == lepton_gain_low) + { + leptonGainMode = lepton_gain_high; + buttons_setActive(0); + buttons_setInactive(1); + } + } + //Low gain + else if (pressedButton == 1) + { + if (leptonGainMode == lepton_gain_high) + { + leptonGainMode = lepton_gain_low; + buttons_setActive(1); + buttons_setInactive(0); + } + } + //Save + else if (pressedButton == 2) + { + //Change gain mode + if (leptonGainMode == lepton_gain_low) + { + lepton_setLowGain(); + } + else + { + lepton_setHighGain(); + } + + //Write new settings to EEPROM + EEPROM.write(eeprom_lepton_gain, leptonGainMode); + + //Trigger shutter + lepton_ffc(true, true); + + hardwareMenu(); + return; + } + } + } +} + +/* Hardware menu handler*/ +void hardwareMenuHandler() +{ + while (true) + { + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(); + //Battery Gauge + if (pressedButton == 0) + { + batteryGauge(); + } + //Lepton Gain + else if (pressedButton == 1) + { + changeLeptonGain(); + } + //Format + else if (pressedButton == 2) + { + formatStorage(); + } + //Back + else if (pressedButton == 3) + { + settingsMenu(); + break; + } + } + } +} + +/* Hardware menu */ +void hardwareMenu() +{ + drawTitle((char *)"Hardware Settings"); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 130, 70, (char *)"Battery Gauge"); + buttons_addButton(170, 60, 130, 70, (char *)"Lepton Gain"); + buttons_addButton(20, 150, 130, 70, (char *)"Format"); + buttons_addButton(170, 150, 130, 70, (char *)"Back"); + buttons_drawButtons(); +} + +/* Temperature format menu */ +void tempFormatMenu(bool firstStart) +{ + drawTitle((char *)"Temp. Format", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 130, 70, (char *)"Celcius"); + buttons_addButton(170, 60, 130, 70, (char *)"Fahrenheit"); + buttons_addButton(20, 150, 280, 70, (char *)"Save"); + if (firstStart) + { + buttons_relabelButton(2, (char *)"Set", false); + tempFormat = tempFormat_celcius; + } + buttons_drawButtons(); + if (tempFormat == tempFormat_celcius) + buttons_setActive(tempFormat_celcius); + else + buttons_setActive(tempFormat_fahrenheit); + //Touch handler + while (true) + { + //touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(); + //Celcius + if (pressedButton == 0) + { + if (tempFormat == tempFormat_fahrenheit) + { + tempFormat = tempFormat_celcius; + buttons_setActive(0); + buttons_setInactive(1); + } + } + //Fahrenheit + else if (pressedButton == 1) + { + if (tempFormat == tempFormat_celcius) + { + tempFormat = tempFormat_fahrenheit; + buttons_setActive(1); + buttons_setInactive(0); + } + } + //Save + else if (pressedButton == 2) + { + //Write new settings to EEPROM + EEPROM.write(eeprom_tempFormat, tempFormat); + if (firstStart) + return; + else + { + displayMenu(); + } + break; + } + } + } +} + +/* Rotate display menu */ +void rotateDisplayMenu(bool firstStart) +{ + drawTitle((char *)"Disp. rotation", firstStart); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 130, 70, (char *)"Rotation"); + buttons_addButton(170, 60, 130, 70, (char *)"Hor. Flip"); + buttons_addButton(20, 150, 130, 70, (char *)"Disabled"); + buttons_addButton(170, 150, 130, 70, (char *)"Save"); + if (firstStart) + buttons_relabelButton(3, (char *)"Set", false); + buttons_drawButtons(); + if (rotationVert) + buttons_setActive(0); + else if (rotationHorizont) + buttons_setActive(1); + else + buttons_setActive(2); + //Touch handler + while (true) + { + //touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(); + //Rotate 180° + if (pressedButton == 0) + { + if (!rotationVert) + { + rotationVert = true; + rotationHorizont = false; + buttons_setActive(0); + buttons_setInactive(1); + buttons_setInactive(2); + } + } + //Mirror horizontally + else if (pressedButton == 1) + { + if (!rotationHorizont) + { + rotationHorizont = true; + rotationVert = false; + buttons_setActive(1); + buttons_setInactive(0); + buttons_setInactive(2); + } + } + //Disable + else if (pressedButton == 2) + { + rotationVert = false; + rotationHorizont = false; + buttons_setActive(2); + buttons_setInactive(0); + buttons_setInactive(1); + } + //Save + else if (pressedButton == 3) + { + //Write new settings to EEPROM + EEPROM.write(eeprom_rotationVert, rotationVert); + EEPROM.write(eeprom_rotationHorizont, rotationHorizont); + if (firstStart) + return; + //Set the rotation + setDisplayRotation(); + //Show the display menu + displayMenu(); + break; + } + } + } +} + +/* Screen timeout menu */ +void screenTimeoutMenu() +{ + drawTitle((char *)"Screen timeout"); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 130, 70, (char *)"Disabled"); + buttons_addButton(170, 60, 130, 70, (char *)"5 Min."); + buttons_addButton(20, 150, 130, 70, (char *)"20 Min."); + buttons_addButton(170, 150, 130, 70, (char *)"Back"); + buttons_drawButtons(); + //Set current one active + buttons_setActive(screenOffTime); + //Touch handler + while (true) + { + //Touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(); + //Set to new color + if ((pressedButton == 0) || (pressedButton == 1) || (pressedButton == 2)) + { + buttons_setInactive(screenOffTime); + screenOffTime = pressedButton; + buttons_setActive(screenOffTime); + } + //Save + else if (pressedButton == 3) + { + //Write new settings to EEPROM + EEPROM.write(eeprom_screenOffTime, screenOffTime); + //Init timer + initScreenOffTimer(); + //Return to display menu + displayMenu(); + break; + } + } + } +} + +/* Display menu handler*/ +void displayMenuHandler() +{ + while (true) + { + //touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(); + //Temp. format + if (pressedButton == 0) + { + tempFormatMenu(); + } + //Rotate display + else if (pressedButton == 1) + { + rotateDisplayMenu(); + } + //Screen timeout + else if (pressedButton == 2) + { + screenTimeoutMenu(); + } + //Back + else if (pressedButton == 3) + { + settingsMenu(); + break; + } + } + } +} + +/* Display menu */ +void displayMenu() +{ + drawTitle((char *)"Display Settings"); + buttons_deleteAllButtons(); + buttons_addButton(20, 60, 130, 70, (char *)"Temp. format"); + buttons_addButton(170, 60, 130, 70, (char *)"Disp. rotation"); + buttons_addButton(20, 150, 130, 70, (char *)"Screen timeout"); + buttons_addButton(170, 150, 130, 70, (char *)"Back"); + buttons_drawButtons(); +} + +/* Touch handler for the settings menu */ +void settingsMenuHandler() +{ + while (true) + { + //touch press + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(); + + //General + if (pressedButton == 0) + { + generalMenu(); + generalMenuHandler(); + } + //Hardware + else if (pressedButton == 1) + { + hardwareMenu(); + hardwareMenuHandler(); + } + //Display + else if (pressedButton == 2) + { + displayMenu(); + displayMenuHandler(); + } + //Back + else if (pressedButton == 3) + break; + } + } +} + +/* Settings menu main screen */ +void settingsMenu() +{ + drawTitle((char *)"Settings"); + buttons_deleteAllButtons(); + buttons_setTextFont(smallFont); + buttons_addButton(20, 60, 130, 70, (char *)"General"); + buttons_addButton(170, 60, 130, 70, (char *)"Hardware"); + buttons_addButton(20, 150, 130, 70, (char *)"Display"); + buttons_addButton(170, 150, 130, 70, (char *)"Back"); + buttons_drawButtons(); +} diff --git a/Firmware_V3/src/gui/videomenu.cpp b/Firmware_V3/src/gui/videomenu.cpp new file mode 100644 index 0000000..bde6a1e --- /dev/null +++ b/Firmware_V3/src/gui/videomenu.cpp @@ -0,0 +1,400 @@ +/* +* +* VIDEO MENU - Record single frames or time interval videos +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################### STATIC DATA DECLARATIONS ##########################*/ + +//Video save interval in seconds +static int16_t videoInterval; + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Switch the video interval string*/ +void videoIntervalString(int pos) { + char* text = (char*) ""; + switch (pos) { + //1 second + case 0: + text = (char*) "1 second"; + break; + //5 seconds + case 1: + text = (char*) "5 seconds"; + break; + //10 seconds + case 2: + text = (char*) "10 seconds"; + break; + //20 seconds + case 3: + text = (char*) "20 seconds"; + break; + //30 seconds + case 4: + text = (char*) "30 seconds"; + break; + //1 minute + case 5: + text = (char*) "1 minute"; + break; + //5 minutes + case 6: + text = (char*) "5 minutes"; + break; + //10 minutes + case 7: + text = (char*) "10 minutes"; + break; + } + //Draws the current selection + mainMenuSelection(text); +} + +/* Touch Handler for the video interval chooser */ +bool videoIntervalHandler(byte* pos) { + //Main loop + while (true) { + //Touch screen pressed + if (touch_touched() == true) { + int pressedButton = buttons_checkButtons(true); + //SELECT + if (pressedButton == 3) { + switch (*pos) { + //1 second + case 0: + videoInterval = 1; + break; + //5 seconds + case 1: + videoInterval = 5; + break; + //10 seconds + case 2: + videoInterval = 10; + break; + //20 seconds + case 3: + videoInterval = 20; + break; + //30 seconds + case 4: + videoInterval = 30; + break; + //1 minute + case 5: + videoInterval = 60; + break; + //5 minutes + case 6: + videoInterval = 300; + break; + //10 minutes + case 7: + videoInterval = 600; + break; + } + return true; + } + //BACK + else if (pressedButton == 2) { + return false; + } + //BACKWARD + else if (pressedButton == 0) { + if (*pos > 0) + *pos = *pos - 1; + else if (*pos == 0) + *pos = 7; + } + //FORWARD + else if (pressedButton == 1) { + if (*pos < 7) + *pos = *pos + 1; + else if (*pos == 7) + *pos = 0; + } + //Change the menu name + videoIntervalString(*pos); + } + } +} + +/* Start video menu to choose interval */ +bool videoIntervalChooser() { + bool rtn; + static byte videoIntervalPos = 0; + //Background + mainMenuBackground(); + //Title + mainMenuTitle((char*) "Choose interval"); + //Draw the selection menu + drawSelectionMenu(); + //Current choice name + videoIntervalString(videoIntervalPos); + //Touch handler - return true if exit to Main menu, otherwise false + rtn = videoIntervalHandler(&videoIntervalPos); + //Restore old fonts + display_setFont(smallFont); + buttons_setTextFont(smallFont); + //Delete the old buttons + buttons_deleteAllButtons(); + return rtn; +} + +/* Captures video frames in an interval */ +void videoCaptureInterval(int16_t* remainingTime, int* framesCaptured) { + char buffer[30]; + + //Measure time + long measure = millis(); + + //If there is no more time or the first frame + if ((*remainingTime <= 0) || (*framesCaptured == 0)) { + saveRawData(false, NULL, *framesCaptured); + } + + //Convert lepton data to RGB565 colors + convertColors(); + + //Display infos + displayInfos(); + + //Write to image buffer + display_writeToImage = true; + + //Display title + display_print((char*) "Interval capture", 105, 20); + + //Show saving message + if ((*remainingTime <= 0) || (*framesCaptured == 0)) + sprintf(buffer, "Saving now.."); + //Show waiting time + else + sprintf(buffer, "Saving in %ds", *remainingTime); + + //Display message on buffer + display_print(buffer, 120, 200); + + //Disable image buffer + display_writeToImage = false; + + //Draw thermal image on screen + displayBuffer(); + + //If there is no more time or the first frame + if ((*remainingTime <= 0) || (*framesCaptured == 0)) { + *remainingTime = videoInterval; + *framesCaptured = *framesCaptured + 1; + } + else + { + //Wait rest of the time + measure = millis() - measure; + if (measure < 1000) + delay(1000 - measure); + + //Decrease remaining time by one + *remainingTime -= 1; + } +} + +/* Normal video capture */ +void videoCaptureNormal(int* framesCaptured) { + char buffer[30]; + + //Save video raw frame + saveRawData(false, NULL, *framesCaptured); + + //Convert the colors + convertColors(); + + //Display infos + displayInfos(); + + //Write to image buffer + display_writeToImage = true; + + //Display title + display_print((char*) "Video capture", 115, 20); + + //Raise capture counter + *framesCaptured = *framesCaptured + 1; + + //Display current frames captured + sprintf(buffer, "Frames captured: %5d", *framesCaptured); + display_print(buffer, 70, 200); + + //Disable image buffer + display_writeToImage = false; + + //Refresh capture + displayBuffer(); +} + +/* This screen is shown during the video capture */ +void videoCapture() { + //Help variables + char dirname[20]; + int16_t delayTime = videoInterval; + int framesCaptured = 0; + + //Show message + showFullMessage((char*)"Touch screen to turn it off"); + display_print((char*) "STARTING VIDEO MODE", CENTER, 50); + display_print((char*) "Press the button to abort", CENTER, 170); + delay(1000); + + //Create folder + createSDName(dirname, true); + sd.chdir("/"); + sd.mkdir(dirname); + sd.chdir(dirname); + + //Switch to recording mode + videoSave = videoSave_recording; + + //Main loop + lepton_startFrame(); + while (videoSave == videoSave_recording) { + + //Touch - turn display on or off + if (!digitalRead(pin_touch_irq)) { + digitalWrite(pin_lcd_backlight, !(checkScreenLight())); + while (!digitalRead(pin_touch_irq)); + } + + //Create the thermal image + createThermalImg(); + + //Video capture + if (videoInterval == 0) { + videoCaptureNormal(&framesCaptured); + } + //Interval capture + else { + videoCaptureInterval(&delayTime, &framesCaptured); + } + + lepton_startFrame(); + } + + //Turn the display on if it was off before + if (!checkScreenLight()) + enableScreenLight(); + + //Post processing for interval videos if enabled and wished + if ((framesCaptured > 0) && (convertPrompt())) + processVideoFrames(framesCaptured, dirname); + + //Show finished message + else { + showFullMessage((char*) "Video capture finished"); + delay(1000); + } + + //Go back to root directory + sd.chdir("/"); + + //Refresh free space + refreshFreeSpace(); + + //Disable mode + videoSave = videoSave_disabled; +} + +/* Video mode, choose intervall or normal */ +void videoMode() { + //Check if there is at least 1MB of space left + if (getSDSpace() < 1000) { + //Show message + showFullMessage((char*) "The SD card is full"); + delay(1000); + + //Disable mode and return + videoSave = videoSave_disabled; + return; + } + + //Border + drawMainMenuBorder(); + +redraw: + //Title & Background + mainMenuBackground(); + mainMenuTitle((char*)"Video Mode"); + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(15, 47, 140, 120, (char*) "Normal"); + buttons_addButton(165, 47, 140, 120, (char*) "Interval"); + buttons_addButton(15, 188, 140, 40, (char*) "Back"); + buttons_drawButtons(); + + //Touch handler + while (true) { + + //If touch pressed + if (touch_touched() == true) { + //Check which button has been pressed + int pressedButton = buttons_checkButtons(true); + + //Normal + if (pressedButton == 0) { + //Set video interval to zero, means normal + videoInterval = 0; + //Start capturing a video + videoCapture(); + break; + } + + //Interval + if (pressedButton == 1) { + //Choose the time interval + if (!videoIntervalChooser()) + //Redraw video mode if user pressed back + goto redraw; + //Start capturing a video + videoCapture(); + break; + } + + //Back + if (pressedButton == 2) { + //Disable mode and return + videoSave = videoSave_disabled; + return; + } + } + } +} diff --git a/Firmware_V3/src/hardware/battery.cpp b/Firmware_V3/src/hardware/battery.cpp new file mode 100644 index 0000000..fe2c32d --- /dev/null +++ b/Firmware_V3/src/hardware/battery.cpp @@ -0,0 +1,98 @@ +/* + * + * BATTERY - Measure the lithium battery status + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* A method to calculate the lipo percentage out of its voltage */ +int getLipoPerc(float vol) { + if (vol >= 4.10) + return 100; + if (vol >= 4.00) + return 90; + if (vol >= 3.93) + return 80; + if (vol >= 3.88) + return 70; + if (vol >= 3.84) + return 60; + if (vol >= 3.80) + return 50; + if (vol >= 3.76) + return 40; + if (vol >= 3.73) + return 30; + if (vol >= 3.70) + return 20; + if (vol >= 3.66) + return 10; + if (vol >= 3.00) + return 0; + return -1; +} + +/* Measure the battery voltage and convert it to percent */ +void checkBattery(bool start, bool calibrate) { + //Read battery voltage + float vBat = (batMeasure->analogRead(pin_bat_measure) * 1.5 * 3.3) / batMeasure->adc0->getMaxValue(); + + //Check if the device is charging + int vUSB = analogRead(pin_usb_measure); + //Battery is not working if no voltage measured and not connected to USB + if ((vBat == -1) && (vUSB <= 50)) + setDiagnostic(diag_bat); + + //If not charging, add some value to correct the voltage + if (vUSB <= 50) + vBat += 0.15; + + //Recalibrate the battery gauge + if (calibrate) + { + //Calculate value to correct + float compensation = (4.15 - vBat) * 100; + batComp = (int8_t) round(compensation); + + //Save to EEPROM + EEPROM.write(eeprom_batComp, batComp); + } + + //At first launch, read value from EEPROM + if(start) + batComp = EEPROM.read(eeprom_batComp); + + //Correct voltage + if (batComp != 0) + vBat += (float) batComp / 100.0; + + //Calculate the percentage out of the voltage + batPercentage = getLipoPerc(vBat); + + //Show warning on startup if the battery is low + if ((batPercentage <= 20) && (batPercentage != -1) && (start)) { + showFullMessage((char*) "Battery almost empty, charge"); + delay(1000); + } +} diff --git a/Firmware_V3/src/hardware/connection.cpp b/Firmware_V3/src/hardware/connection.cpp new file mode 100644 index 0000000..824e358 --- /dev/null +++ b/Firmware_V3/src/hardware/connection.cpp @@ -0,0 +1,1100 @@ +/* +* +* CONNECTION - Communication protocol for the USB serial data transmission +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################### STATIC DATA DECLARATIONS ##########################*/ + +//Command, default send frame +static byte sendCmd = FRAME_NORMAL; + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Get integer out of a text string */ +int getInt(String text) +{ + char temp[6]; + text.toCharArray(temp, 5); + int x = atoi(temp); + return x; +} + +/* Enter the serial connection mode if no display attached */ +bool checkNoDisplay() +{ + //No connection to ILI9341 and touch screen -> go to USB serial + if (!checkDiagnostic(diag_display) && !checkDiagnostic(diag_touch)) + return true; + //Display connected + return false; +} + +/* Send the lepton raw limits */ +void sendRawLimits() +{ + //Send min + Serial.write((minValue & 0xFF00) >> 8); + Serial.write(minValue & 0x00FF); + //Send max + Serial.write((maxValue & 0xFF00) >> 8); + Serial.write(maxValue & 0x00FF); +} + +/* Send the lepton raw data*/ +void sendRawData(bool color) +{ + //For the Lepton2.5 sensor, write 4800 raw values + if ((leptonVersion == leptonVersion_2_5_shutter) && (!color)) + { + for (int line = 0; line < 60; line++) + { + for (int column = 0; column < 80; column++) + { + uint16_t result = smallBuffer[(line * 2 * 160) + (column * 2)]; + Serial.write((result & 0xFF00) >> 8); + Serial.write(result & 0x00FF); + } + } + } + //For the Lepton3.5 sensor, write 19200 raw values + else + { + for (int i = 0; i < 19200; i++) + { + Serial.write((smallBuffer[i] & 0xFF00) >> 8); + Serial.write(smallBuffer[i] & 0x00FF); + } + } +} + +/* Sends the framebuffer */ +void sendFramebuffer() +{ + for (uint32_t i = 0; i < 76800; i++) + { + Serial.write((bigBuffer[i] & 0xFF00) >> 8); + Serial.write(bigBuffer[i] & 0x00FF); + } +} + +/* Sends the configuration data */ +void sendConfigData() +{ + //Lepton Version + Serial.write(leptonVersion); + //Rotation + Serial.write(rotationVert); + //Send color scheme + Serial.write(colorScheme); + //Send the temperature format + Serial.write(tempFormat); + //Send the show spot attribute + Serial.write(spotEnabled); + //Send the show colorbar attribute + Serial.write(colorbarEnabled); + //Send the show hottest / coldest attribute + Serial.write(minMaxPoints); + //Send the text color + Serial.write(textColor); + //Send the filter type + Serial.write(filterType); + //Send adjust limits allowed + Serial.write((autoMode) && (!limitsLocked)); +} + + +/* Sends the calibration data */ +void sendCalibrationData() +{ + uint8_t farray[4]; + + //Send the calibration offset first + float calOffset = -273.15; + floatToBytes(farray, (float)calOffset); + for (int i = 0; i < 4; i++) + Serial.write(farray[i]); + //Send the calibration slope + floatToBytes(farray, (float)leptonCalSlope); + for (int i = 0; i < 4; i++) + Serial.write(farray[i]); +} + +/* Sends the spot temp*/ +void sendSpotTemp() +{ + //Array to store the byte-converted float value + uint8_t farray[4]; + + //Convert float to bytes + floatToBytes(farray, spotTemp); + + //Send the four bytes out + for (int i = 0; i < 4; i++) + Serial.write(farray[i]); +} + +/* Sets the time */ +void setTime() +{ + //Wait for time string, maximum 1 second + uint32_t timer = millis(); + while (!Serial.available() && ((millis() - timer) < 1000)) + ; + + //If there was no timestring + if (Serial.available() == 0) + { + //Send ACK and return + Serial.write(CMD_SET_TIME); + return; + } + + //Read time + String dateIn = Serial.readString(); + + //Check if valid + if (getInt(dateIn.substring(0, 4) >= 2021)) + { + //Set the clock + setTime(getInt(dateIn.substring(11, 13)), getInt(dateIn.substring(14, 16)), getInt(dateIn.substring(17, 19)), + getInt(dateIn.substring(8, 10)), getInt(dateIn.substring(5, 7)), getInt(dateIn.substring(0, 4))); + //Set the RTC + Teensy3Clock.set(now()); + } + + //Send ACK + Serial.write(CMD_SET_TIME); +} + +/* Send the temperature points */ +void sendTempPoints() +{ + for (byte i = 0; i < 96; i++) + { + //Send index value + Serial.write((tempPoints[i][0] & 0xFF00) >> 8); + Serial.write(tempPoints[i][0] & 0x00FF); + //Send raw value + Serial.write((tempPoints[i][1] & 0xFF00) >> 8); + Serial.write(tempPoints[i][1] & 0x00FF); + } +} + +/* Send the battery status in percentage */ +void sendBatteryStatus() +{ + Serial.write(batPercentage); +} + +/* Send the current firmware version */ +void sendFWVersion() +{ + Serial.write((fwVersion & 0xFF00) >> 8); + Serial.write(fwVersion & 0x00FF); +} + +/* Set the temperature limits */ +void setLimits() +{ + //If not enough data available, leave + if (Serial.available() < 1) + { + Serial.write(CMD_INVALID); + return; + } + + //Read byte from serial port + byte read = Serial.read(); + + //Check if it has a valid number + if ((read >= 0) && (read <= 1)) + { + //Lock limits + if (read == 0) + limitsLocked = true; + + //Auto mode + else + { + //Enable auto mode + autoMode = true; + //Disable limits locked + limitsLocked = false; + } + } + //Send invalid + else + { + Serial.write(CMD_INVALID); + return; + } + + //Send ACK + Serial.write(CMD_SET_LIMITS); +} + +/* Set the text color */ +void setTextColor() +{ + //If not enough data available, leave + if (Serial.available() < 1) + { + Serial.write(CMD_INVALID); + return; + } + + //Read byte from serial port + byte read = Serial.read(); + + //Check if read result is valid + if ((read >= textColor_white) && (read <= textColor_blue)) + { + //Set text color to input + textColor = read; + //Change it + changeTextColor(); + //Save to EEPROM + EEPROM.write(eeprom_textColor, textColor); + } + //Send invalid + else + { + Serial.write(CMD_INVALID); + return; + } + + //Send ACK + Serial.write(CMD_SET_TEXTCOLOR); +} + +/* Set the color scheme */ +void setColorScheme() +{ + //If not enough data available, leave + if (Serial.available() < 1) + { + Serial.write(CMD_INVALID); + return; + } + + //Read byte from serial port + byte read = Serial.read(); + + //Check if it has a valid number + if ((read >= 0) && (read <= (colorSchemeTotal - 1))) + { + //Set color scheme to input + colorScheme = read; + //Select right color scheme + selectColorScheme(); + //Save to EEPROM + EEPROM.write(eeprom_colorScheme, colorScheme); + } + //Send invalid + else + { + Serial.write(CMD_INVALID); + return; + } + + //Send ACK + Serial.write(CMD_SET_COLORSCHEME); +} + +/* Set the temperature format */ +void setTempFormat() +{ + //If not enough data available, leave + if (Serial.available() < 1) + { + Serial.write(CMD_INVALID); + return; + } + + //Read byte from serial port + byte read = Serial.read(); + + //Check if it has a valid number + if ((read >= 0) && (read <= 1)) + { + //Set temperature format to input + tempFormat = read; + //Save to EEPROM + EEPROM.write(eeprom_tempFormat, tempFormat); + } + //Send invalid + else + { + Serial.write(CMD_INVALID); + return; + } + + //Send ACK + Serial.write(CMD_SET_TEMPFORMAT); +} + +/* Set the show spot information */ +void setShowSpot() +{ + //If not enough data available, leave + if (Serial.available() < 1) + { + Serial.write(CMD_INVALID); + return; + } + + //Read byte from serial port + byte read = Serial.read(); + + //Check if it has a valid number + if ((read >= 0) && (read <= 1)) + { + //Set show spot to input + spotEnabled = read; + //Save to EEPROM + EEPROM.write(eeprom_spotEnabled, spotEnabled); + } + //Send invalid + else + { + Serial.write(CMD_INVALID); + return; + } + + //Send ACK + Serial.write(CMD_SET_SHOWSPOT); +} + +/* Set the show colorbar information */ +void setShowColorbar() +{ + //If not enough data available, leave + if (Serial.available() < 1) + { + Serial.write(CMD_INVALID); + return; + } + + //Read byte from serial port + byte read = Serial.read(); + + //Check if it has a valid number + if ((read >= 0) && (read <= 1)) + { + //Set show colorbar to input + colorbarEnabled = read; + //Save to EEPROM + EEPROM.write(eeprom_colorbarEnabled, colorbarEnabled); + } + //Send invalid + else + { + Serial.write(CMD_INVALID); + return; + } + + //Send ACK + Serial.write(CMD_SET_SHOWCOLORBAR); +} + +/* Set the show colorbar information */ +void setMinMax() +{ + //If not enough data available, leave + if (Serial.available() < 1) + { + Serial.write(CMD_INVALID); + return; + } + + //Read byte from serial port + byte read = Serial.read(); + + //Check if it has a valid number + if ((read >= minMaxPoints_disabled) && (read <= minMaxPoints_both)) + { + //Set show colorbar to input + minMaxPoints = read; + //Save to EEPROM + EEPROM.write(eeprom_minMaxPoints, minMaxPoints); + } + //Send invalid + else + { + Serial.write(CMD_INVALID); + return; + } + + //Send ACK + Serial.write(CMD_SET_SHOWMINMAX); +} + +/* Set the shutter mode */ +void setShutterMode() +{ + //If not enough data available, leave + if (Serial.available() < 1) + { + Serial.write(CMD_INVALID); + return; + } + + //Read byte from serial port + byte read = Serial.read(); + + //Check if it has a valid number + if ((read >= 0) && (read <= 1)) + //Set lepton shutter mode + lepton_ffcMode(read); + //Send invalid + else + { + Serial.write(CMD_INVALID); + return; + } + + //Send ACK + Serial.write(CMD_SET_SHUTTERMODE); +} + +/* Set the fitler type */ +void setFilterType() +{ + //If not enough data available, leave + if (Serial.available() < 1) + { + Serial.write(CMD_INVALID); + return; + } + + //Read byte from serial port + byte read = Serial.read(); + + //Check if it has a valid number + if ((read >= filterType_none) && (read <= filterType_box)) + { + //Set filter type to input + filterType = read; + //Save to EEPROM + EEPROM.write(eeprom_filterType, filterType); + } + //Send invalid + else + { + Serial.write(CMD_INVALID); + return; + } + + //Send ACK + Serial.write(CMD_SET_FILTERTYPE); +} + +/* Set the rotation */ +void setRotation() +{ + //If not enough data available, leave + if (Serial.available() < 1) + { + Serial.write(CMD_INVALID); + return; + } + + //Read byte from serial port + byte read = Serial.read(); + + //Check if it has a valid number + if ((read >= 0) && (read <= 1)) + { + //Set rotation to input + rotationVert = read; + //Apply to display + setDisplayRotation(); + //Save to EEPROM + EEPROM.write(eeprom_rotationVert, rotationVert); + } + //Send invalid + else + { + Serial.write(CMD_INVALID); + return; + } + + //Send ACK + Serial.write(CMD_SET_ROTATION); +} + +/* Send the hardware version */ +void sendHardwareVersion() +{ + //Send hardware version + Serial.write(3); +} + +/* Send the diagnostic information */ +void sendDiagnostic() +{ + //Send the diag byte + Serial.write(diagnostic); +} + +/* Send the HQ Resolution information */ +void sendHQResolution() +{ + Serial.write(1); +} + +/* Set temperature points array */ +void setTempPoints() +{ + //If not enough data available, leave + if (Serial.available() < 384) + { + Serial.write(CMD_INVALID); + return; + } + + //Go through the temp points array + for (byte i = 0; i < 96; i++) + { + //Read index + tempPoints[i][0] = (Serial.read() << 8) + Serial.read(); + + //Correct old not_set marker + if (tempPoints[i][0] == 65535) + tempPoints[i][0] = 0; + + //Read value + tempPoints[i][1] = (Serial.read() << 8) + Serial.read(); + } + + //Send ACK + Serial.write(CMD_SET_TEMPPOINTS); +} + +/* Sends a raw or color frame */ +void sendFrame(bool color) +{ + //Send type of frame response + Serial.write(sendCmd); + Serial.flush(); + + //Send frame + if (sendCmd == FRAME_NORMAL) + { + //Clear all serial buffers + Serial.clear(); + //Convert to colors + if (color) + { + //Apply low-pass filter + if (filterType == filterType_box) + boxFilter(); + else if (filterType == filterType_gaussian) + gaussianFilter(); + //Convert to RGB565 + convertColors(true); + } + //Send raw data + sendRawData(color); + + //Send limits + sendRawLimits(); + //Send spot temp + sendSpotTemp(); + //Send calibration data + sendCalibrationData(); + } + //Switch back to send frame the next time + else + sendCmd = FRAME_NORMAL; +} + +/* Saves a frame to the internal sd card*/ +void saveFrame() +{ + if (getSDSpace() < 1000) + { + Serial.write(CMD_INVALID); + return; + } + + //Build save filename from the current time & date + createSDName(saveFilename); + + //Enable image save marker + imgSave = imgSave_create; + + //Create image and save raw file + lepton_startFrame(); + createThermalImg(); + + //Save Bitmap image if activated + if (convertEnabled) + { + displayInfos(); + saveBuffer(saveFilename); + } + + //Refresh free space + refreshFreeSpace(); + + //Disable image save marker + imgSave = imgSave_disabled; + + //Send ACK + Serial.write(CMD_FRAME_SAVE); +} + +/* Sends the display content as frame */ +void sendDisplayFrame() +{ + //Send type of frame response + Serial.write(sendCmd); + Serial.flush(); + + //Send frame + if (sendCmd == FRAME_NORMAL) + { + //Find min / max position + if (minMaxPoints != minMaxPoints_disabled) + refreshMinMax(); + + //Apply low-pass filter + if (filterType == filterType_box) + boxFilter(); + else if (filterType == filterType_gaussian) + gaussianFilter(); + + //Resize to big buffer + smallToBigBuffer(); + + //Convert lepton data to RGB565 colors + convertColors(); + + //Display additional information + imgSave = imgSave_create; + displayInfos(); + imgSave = imgSave_disabled; + + //Send the framebuffer + sendFramebuffer(); + } + + //Switch back to send frame the next time + else + sendCmd = FRAME_NORMAL; +} + +/* Evaluates commands from the serial port*/ +bool serialHandler() +{ + //Read command from Serial Port + byte recCmd = Serial.read(); + + //Decide what to do + switch (recCmd) + { + //Send raw limits + case CMD_GET_RAWLIMITS: + sendRawLimits(); + break; + //Send raw data + case CMD_GET_RAWDATA: + sendRawData(); + break; + //Send config data + case CMD_GET_CONFIGDATA: + sendConfigData(); + break; + //Send the calibration status + case CMD_GET_CALSTATUS: + Serial.write(CMD_INVALID); + break; + //Send calibration data + case CMD_GET_CALIBDATA: + sendCalibrationData(); + break; + //Send spot temp + case CMD_GET_SPOTTEMP: + sendSpotTemp(); + break; + //Change time + case CMD_SET_TIME: + setTime(); + break; + //Send temperature points + case CMD_GET_TEMPPOINTS: + sendTempPoints(); + break; + //Toggle laser + case CMD_SET_LASER: + Serial.write(CMD_INVALID); + break; + //Send laser state + case CMD_GET_LASER: + Serial.write(CMD_INVALID); + break; + //Run the shutter + case CMD_SET_SHUTTERRUN: + lepton_ffc(); + //Send ACK + Serial.write(CMD_SET_SHUTTERRUN); + break; + //Set shutter mode + case CMD_SET_SHUTTERMODE: + setShutterMode(); + break; + //Set the filter type + case CMD_SET_FILTERTYPE: + setFilterType(); + break; + //Get the shutter mode + case CMD_GET_SHUTTERMODE: + Serial.write(CMD_INVALID); + break; + //Send battery status + case CMD_GET_BATTERYSTATUS: + sendBatteryStatus(); + break; + //Set calibration offset + case CMD_SET_CALOFFSET: + Serial.write(CMD_INVALID); + break; + //Set calibration slope + case CMD_SET_CALSLOPE: + Serial.write(CMD_INVALID); + break; + //Send visual image + case CMD_GET_VISUALIMG: + Serial.write(CMD_INVALID); + break; + //Send firmware version + case CMD_GET_FWVERSION: + sendFWVersion(); + break; + //Set limits + case CMD_SET_LIMITS: + setLimits(); + break; + //Set limits to locked + case CMD_SET_TEXTCOLOR: + setTextColor(); + break; + //Change colorscheme + case CMD_SET_COLORSCHEME: + setColorScheme(); + break; + //Set temperature format + case CMD_SET_TEMPFORMAT: + setTempFormat(); + break; + //Set show spot temp + case CMD_SET_SHOWSPOT: + setShowSpot(); + break; + //Set show color bar + case CMD_SET_SHOWCOLORBAR: + setShowColorbar(); + break; + //Set show min max + case CMD_SET_SHOWMINMAX: + setMinMax(); + break; + //Set temperature points + case CMD_SET_TEMPPOINTS: + setTempPoints(); + break; + //Get hardware version + case CMD_GET_HWVERSION: + sendHardwareVersion(); + break; + //Set rotation + case CMD_SET_ROTATION: + setRotation(); + break; + //Run calibration + case CMD_SET_CALIBRATION: + Serial.write(CMD_INVALID); + break; + //Get diagnostic information + case CMD_GET_DIAGNOSTIC: + sendDiagnostic(); + break; + //Get HQ resolution information + case CMD_GET_HQRESOLUTION: + sendHQResolution(); + break; + //Send raw frame + case CMD_FRAME_RAW: + sendFrame(false); + break; + //Send color frame + case CMD_FRAME_COLOR: + sendFrame(true); + break; + //Send display frame + case CMD_FRAME_DISPLAY: + sendDisplayFrame(); + break; + //Save display frame + case CMD_FRAME_SAVE: + saveFrame(); + break; + //End connection + case CMD_END: + return true; + //Start connection, send ACK + case CMD_START: + Serial.write(CMD_START); + break; + //Invalid command + default: + Serial.write(CMD_INVALID); + break; + } + Serial.flush(); + return false; +} + +/* Evaluate button presses */ +void buttonHandler() +{ + //Count the time to choose selection + long startTime = millis(); + delay(10); + long endTime = millis() - startTime; + + //As long as the button is pressed + while (extButtonPressed() && (endTime <= 1000)) + endTime = millis() - startTime; + + //Short press - request to save a thermal image + if (endTime < 1000) + { + sendCmd = FRAME_CAPTURE_THERMAL; + } + + //Long press - request to start or stop a video + else + { + sendCmd = FRAME_CAPTURE_VIDEO; + //Wait until button release + while (extButtonPressed()) + ; + } +} + +/* Evaluate touch presses */ +bool touchHandler() +{ + //Count the time to choose selection + long startTime = millis(); + delay(10); + long endTime = millis() - startTime; + + //Wait for touch release, but not longer than a second + if (touch_capacitive) + { + while ((touch_touched()) && (endTime <= 1000)) + endTime = millis() - startTime; + } + else + { + while ((!digitalRead(pin_touch_irq)) && (endTime <= 1000)) + endTime = millis() - startTime; + } + endTime = millis() - startTime; + + //Short press - take visual image + if (endTime < 1000) + { + sendCmd = FRAME_CAPTURE_VISUAL; + return false; + } + + //Long press + return true; +} + +/* Check for serial connection */ +void checkSerial() +{ + //If start command received + if ((Serial.available() > 0) && (Serial.read() == CMD_START)) + { + serialMode = true; + serialConnect(); + serialMode = false; + lepton_startFrame(); + } + + //Another command received, discard it + else if ((Serial.available() > 0)) + Serial.read(); +} + +/* Check for updater requests */ +void checkForUpdater() +{ + //We received something + if (Serial.available() > 0) + { + //Read command from Serial Port + byte recCmd = Serial.read(); + //Decide what to do + switch (recCmd) + { + //Send firmware version + case CMD_GET_FWVERSION: + sendFWVersion(); + break; + //Get hardware version + case CMD_GET_HWVERSION: + sendHardwareVersion(); + break; + //Get diagnostic information + case CMD_GET_DIAGNOSTIC: + sendDiagnostic(); + break; + //Get HQ resolution information + case CMD_GET_HQRESOLUTION: + sendHQResolution(); + break; + //Send the calibration status + case CMD_GET_CALSTATUS: + Serial.write(CMD_INVALID); + break; + //Send battery status + case CMD_GET_BATTERYSTATUS: + sendBatteryStatus(); + break; + + //Start connection, send ACK + case CMD_START: + Serial.write(CMD_START); + break; + } + Serial.flush(); + } +} + +/* Go into video output mode and wait for connected module */ +void serialOutput() +{ + //Send the frames + while (true) + { + + //Abort transmission when touched long or save visual when short + if (touch_touched() && checkDiagnostic(diag_touch)) + if (touchHandler()) + break; + + //Get the temps + if (checkDiagnostic(diag_lep_data)) + { + lepton_startFrame(); + lepton_getFrame(); + } + + //Get the spot temperature + getSpotTemp(); + + //Refresh the temp points + refreshTempPoints(); + + //Find min and max if not in manual mode and limits not locked + if ((autoMode) && (!limitsLocked)) + limitValues(); + + //Check button press if not in terminal mode + if (extButtonPressed()) + buttonHandler(); + + //Check for serial commands + if (Serial.available() > 0) + { + //Check for exit + if (serialHandler()) + break; + } + } +} + +/* Method to init some basic values in case no display is used */ +void serialInit() +{ + //Read all settings from EEPROM + readEEPROM(); + + //Select color scheme + selectColorScheme(); + + //Clear show temp array + clearTempPoints(); + + //Receive and send commands over serial port + while (true) + serialOutput(); +} + +/* Tries to establish a connection to a thermal viewer or video output module*/ +void serialConnect() +{ + //Show message + showFullMessage((char *)"Serial connection detected"); + display_print((char *)"Touch screen long to return", CENTER, 170); + delay(1000); + + //Disable screen backlight + disableScreenLight(); + + //Send ACK for Start + Serial.write(CMD_START); + + //Go to the serial output + serialOutput(); + + //Send ACK for End + Serial.write(CMD_END); + + //Re-Enable display backlight + enableScreenLight(); + + //Show message + showFullMessage((char *)"Connection ended, return.."); + delay(1000); + + //Clear all serial buffers + Serial.clear(); +} diff --git a/Firmware_V3/src/hardware/display/display.cpp b/Firmware_V3/src/hardware/display/display.cpp new file mode 100644 index 0000000..f72840b --- /dev/null +++ b/Firmware_V3/src/hardware/display/display.cpp @@ -0,0 +1,1495 @@ +/* +* +* Display - ILI9341 SPI Display Module +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include + +/*################# DATA TYPES, CONSTANTS & MACRO DEFINITIONS #################*/ + +#define ILI9341_SPICLOCK 30000000 + +#define ILI9341_TFTWIDTH 240 +#define ILI9341_TFTHEIGHT 320 + +#define ILI9341_NOP 0x00 +#define ILI9341_SWRESET 0x01 +#define ILI9341_RDDID 0x04 +#define ILI9341_RDDST 0x09 + +#define ILI9341_SLPIN 0x10 +#define ILI9341_SLPOUT 0x11 +#define ILI9341_PTLON 0x12 +#define ILI9341_NORON 0x13 + +#define ILI9341_RDMODE 0x0A +#define ILI9341_RDMADCTL 0x0B +#define ILI9341_RDPIXFMT 0x0C +#define ILI9341_RDIMGFMT 0x0A +#define ILI9341_RDSELFDIAG 0x0F + +#define ILI9341_INVOFF 0x20 +#define ILI9341_INVON 0x21 +#define ILI9341_GAMMASET 0x26 +#define ILI9341_DISPOFF 0x28 +#define ILI9341_DISPON 0x29 + +#define ILI9341_CASET 0x2A +#define ILI9341_PASET 0x2B +#define ILI9341_RAMWR 0x2C +#define ILI9341_RAMRD 0x2E + +#define ILI9341_PTLAR 0x30 +#define ILI9341_MADCTL 0x36 +#define ILI9341_PIXFMT 0x3A + +#define ILI9341_FRMCTR1 0xB1 +#define ILI9341_FRMCTR2 0xB2 +#define ILI9341_FRMCTR3 0xB3 +#define ILI9341_INVCTR 0xB4 +#define ILI9341_DFUNCTR 0xB6 + +#define ILI9341_PWCTR1 0xC0 +#define ILI9341_PWCTR2 0xC1 +#define ILI9341_PWCTR3 0xC2 +#define ILI9341_PWCTR4 0xC3 +#define ILI9341_PWCTR5 0xC4 +#define ILI9341_VMCTR1 0xC5 +#define ILI9341_VMCTR2 0xC7 + +#define ILI9341_RDID1 0xDA +#define ILI9341_RDID2 0xDB +#define ILI9341_RDID3 0xDC +#define ILI9341_RDID4 0xDD + +#define ILI9341_GMCTRP1 0xE0 +#define ILI9341_GMCTRN1 0xE1 + +#define MADCTL_MY 0x80 +#define MADCTL_MX 0x40 +#define MADCTL_MV 0x20 +#define MADCTL_ML 0x10 +#define MADCTL_RGB 0x00 +#define MADCTL_BGR 0x08 +#define MADCTL_MH 0x04 + +#define TCR_MASK (LPSPI_TCR_PCS(3) | LPSPI_TCR_FRAMESZ(31) | LPSPI_TCR_CONT | LPSPI_TCR_RXMSK) + +#define swap(type, i, j) \ + { \ + type t = i; \ + i = j; \ + j = t; \ + } +#define fontbyte(x) cfont.font[x] + +struct current_font +{ + uint8_t *font; + uint8_t x_size; + uint8_t y_size; + uint8_t offset; + uint8_t numchars; +}; + +/*######################### STATIC DATA DECLARATIONS ##########################*/ + +static uint8_t rotation; +static current_font cfont; +static boolean transparent; +static byte fch, fcl, bch, bcl, orient; +static uint16_t imageX, imageY; + +static const uint8_t init_commands[] = { + 4, 0xEF, 0x03, 0x80, 0x02, + 4, 0xCF, 0x00, 0XC1, 0X30, + 5, 0xED, 0x64, 0x03, 0X12, 0X81, + 4, 0xE8, 0x85, 0x00, 0x78, + 6, 0xCB, 0x39, 0x2C, 0x00, 0x34, 0x02, + 2, 0xF7, 0x20, + 3, 0xEA, 0x00, 0x00, + 2, ILI9341_PWCTR1, 0x23, // Power control + 2, ILI9341_PWCTR2, 0x10, // Power control + 3, ILI9341_VMCTR1, 0x3e, 0x28, // VCM control + 2, ILI9341_VMCTR2, 0x86, // VCM control2 + 2, ILI9341_MADCTL, 0x48, // Memory Access Control + 2, ILI9341_PIXFMT, 0x55, + 3, ILI9341_FRMCTR1, 0x00, 0x18, + 4, ILI9341_DFUNCTR, 0x08, 0x82, 0x27, // Display Function Control + 2, 0xF2, 0x00, // Gamma Function Disable + 2, ILI9341_GAMMASET, 0x01, // Gamma curve selected + 16, ILI9341_GMCTRP1, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, + 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00, // Set Gamma + 16, ILI9341_GMCTRN1, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, + 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F, // Set Gamma + 3, 0xb1, 0x00, 0x10, // FrameRate Control 119Hz + 0}; + +/*############################# PUBLIC VARIABLES ##############################*/ + +boolean display_writeToImage; +uint32_t display_cs_pinmask; +volatile uint32_t *display_cs_port; +uint32_t display_spi_tcr_current; +uint32_t display_dc_pinmask; +volatile uint32_t *display_dc_port; +uint8_t display_pending_rx_count; +uint32_t display_tcr_dc_assert; +uint32_t display_tcr_dc_not_assert; + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +void display_begin_spi_transaction() +{ + SPI.beginTransaction(SPISettings(ILI9341_SPICLOCK, MSBFIRST, SPI_MODE0)); + if (!display_dc_port) + display_spi_tcr_current = IMXRT_LPSPI4_S.TCR; + if (display_cs_port) + t4_direct_write_low(display_cs_port, display_cs_pinmask); +} +void display_end_spi_transaction() +{ + if (display_cs_port) + t4_direct_write_high(display_cs_port, display_cs_pinmask); + SPI.endTransaction(); +} + +void display_waitFifoNotFull() +{ + uint32_t tmp __attribute__((unused)); + do + { + if ((IMXRT_LPSPI4_S.RSR & LPSPI_RSR_RXEMPTY) == 0) + { + tmp = IMXRT_LPSPI4_S.RDR; + if (display_pending_rx_count) + display_pending_rx_count--; + } + } + while ((IMXRT_LPSPI4_S.SR & LPSPI_SR_TDF) == 0); +} + +void display_waitTransmitComplete() +{ + uint32_t tmp __attribute__((unused)); + + while (display_pending_rx_count) + { + if ((IMXRT_LPSPI4_S.RSR & LPSPI_RSR_RXEMPTY) == 0) + { + tmp = IMXRT_LPSPI4_S.RDR; + display_pending_rx_count--; + } + } + IMXRT_LPSPI4_S.CR = LPSPI_CR_MEN | LPSPI_CR_RRF; +} + +void display_maybeUpdateTCR(uint32_t requested_tcr_state) +{ + if ((display_spi_tcr_current & TCR_MASK) != requested_tcr_state) + { + bool dc_state_change = (display_spi_tcr_current & LPSPI_TCR_PCS(3)) != (requested_tcr_state & LPSPI_TCR_PCS(3)); + display_spi_tcr_current = (display_spi_tcr_current & ~TCR_MASK) | requested_tcr_state; + if (!dc_state_change || !display_dc_pinmask) + { + while ((IMXRT_LPSPI4_S.FSR & 0x1f)) + ; + IMXRT_LPSPI4_S.TCR = display_spi_tcr_current; + } + + else + { + display_waitTransmitComplete(); + if (requested_tcr_state & LPSPI_TCR_PCS(3)) + t4_direct_write_high(display_dc_port, display_dc_pinmask); + else + t4_direct_write_low(display_dc_port, display_dc_pinmask); + IMXRT_LPSPI4_S.TCR = display_spi_tcr_current & ~(LPSPI_TCR_PCS(3) | LPSPI_TCR_CONT); + } + } +} + +void display_writecommand_cont(uint8_t c) +{ + display_maybeUpdateTCR(display_tcr_dc_assert | LPSPI_TCR_FRAMESZ(7)); + IMXRT_LPSPI4_S.TDR = c; + display_pending_rx_count++; +} + +void display_writedata8_cont(uint8_t c) +{ + display_maybeUpdateTCR(display_tcr_dc_not_assert | LPSPI_TCR_FRAMESZ(7) | LPSPI_TCR_CONT); + IMXRT_LPSPI4_S.TDR = c; + display_pending_rx_count++; + display_waitFifoNotFull(); +} + +void display_writedata16_cont(uint16_t d) +{ + display_maybeUpdateTCR(display_tcr_dc_not_assert | LPSPI_TCR_FRAMESZ(15) | LPSPI_TCR_CONT); + IMXRT_LPSPI4_S.TDR = d; + display_pending_rx_count++; + display_waitFifoNotFull(); +} + +void display_writecommand_last(uint8_t c) +{ + display_maybeUpdateTCR(display_tcr_dc_assert | LPSPI_TCR_FRAMESZ(7)); + IMXRT_LPSPI4_S.TDR = c; + display_pending_rx_count++; + display_waitTransmitComplete(); +} + +void display_writedata8_last(uint8_t c) +{ + display_maybeUpdateTCR(display_tcr_dc_not_assert | LPSPI_TCR_FRAMESZ(7)); + IMXRT_LPSPI4_S.TDR = c; + display_pending_rx_count++; + display_waitTransmitComplete(); +} + +void display_writedata16_last(uint16_t d) +{ + display_maybeUpdateTCR(display_tcr_dc_not_assert | LPSPI_TCR_FRAMESZ(15)); + IMXRT_LPSPI4_S.TDR = d; + display_pending_rx_count++; + display_waitTransmitComplete(); +} + +void display_setAddr(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) +{ + display_writecommand_cont(ILI9341_CASET); + display_writedata16_cont(x0); + display_writedata16_cont(x1); + display_writecommand_cont(ILI9341_PASET); + display_writedata16_cont(y0); + display_writedata16_cont(y1); +} + +/* Read 8-bit command from the screen */ +uint8_t display_readcommand8(uint8_t c, uint8_t index) +{ + uint8_t r = 0; + + display_begin_spi_transaction(); + + if (display_dc_port) + { + t4_direct_write_low(display_dc_port, display_dc_pinmask); + IMXRT_LPSPI4_S.SR = LPSPI_SR_TCF | LPSPI_SR_FCF | LPSPI_SR_WCF; + IMXRT_LPSPI4_S.TCR = LPSPI_TCR_FRAMESZ(7) | LPSPI_TCR_RXMSK | LPSPI_TCR_CONT; + IMXRT_LPSPI4_S.TDR = 0xD9; + while (!(IMXRT_LPSPI4_S.SR & LPSPI_SR_WCF)) + ; + + t4_direct_write_high(display_dc_port, display_dc_pinmask); + IMXRT_LPSPI4_S.SR = LPSPI_SR_TCF | LPSPI_SR_FCF | LPSPI_SR_WCF; + IMXRT_LPSPI4_S.TDR = 0x10 + index; + while (!(IMXRT_LPSPI4_S.SR & LPSPI_SR_WCF)) + ; + + t4_direct_write_low(display_dc_port, display_dc_pinmask); + IMXRT_LPSPI4_S.SR = LPSPI_SR_TCF | LPSPI_SR_FCF | LPSPI_SR_WCF; + IMXRT_LPSPI4_S.TDR = c; + while (!(IMXRT_LPSPI4_S.SR & LPSPI_SR_WCF)) + ; + + t4_direct_write_high(display_dc_port, display_dc_pinmask); + IMXRT_LPSPI4_S.SR = LPSPI_SR_TCF | LPSPI_SR_FCF | LPSPI_SR_WCF; + IMXRT_LPSPI4_S.TCR = LPSPI_TCR_FRAMESZ(7); + IMXRT_LPSPI4_S.TDR = 0x10 + index; + while (!(IMXRT_LPSPI4_S.SR & LPSPI_SR_WCF)) + while (((IMXRT_LPSPI4_S.FSR >> 16) & 0x1F) == 0) + + r = IMXRT_LPSPI4_S.RDR; + } + + display_end_spi_transaction(); + + return r; +} + +/* Set display rotation */ +void display_setRotation(uint8_t m) +{ + display_begin_spi_transaction(); + display_writecommand_cont(ILI9341_MADCTL); + rotation = m % 4; + switch (rotation) + { + case 0: + display_writedata8_last(MADCTL_MX | MADCTL_BGR); + orient = LANDSCAPE; + break; + case 1: + display_writedata8_last(MADCTL_MV | MADCTL_BGR); + orient = PORTRAIT; + break; + case 2: + display_writedata8_last(MADCTL_MY | MADCTL_BGR); + orient = LANDSCAPE; + break; + case 3: + display_writedata8_last(MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR); + orient = PORTRAIT; + break; + } + display_end_spi_transaction(); +} + +/* Init the hardware LCD */ +byte display_InitLCD() +{ + display_pending_rx_count = 0; + display_cs_port = portOutputRegister(pin_lcd_cs); + display_cs_pinmask = digitalPinToBitMask(pin_lcd_cs); + display_spi_tcr_current = IMXRT_LPSPI4_S.TCR; + + uint8_t dc_cs_index = SPI.setCS(pin_lcd_dc); + display_dc_port = 0; + display_dc_pinmask = 0; + dc_cs_index--; + display_tcr_dc_assert = LPSPI_TCR_PCS(dc_cs_index); + display_tcr_dc_not_assert = LPSPI_TCR_PCS(3); + + display_maybeUpdateTCR(display_tcr_dc_not_assert | LPSPI_TCR_FRAMESZ(7)); + + //Read the self-diagnostic flag + byte diag = display_readcommand8(ILI9341_RDSELFDIAG); + + //Send the init commands + display_begin_spi_transaction(); + const uint8_t *addr = init_commands; + while (1) + { + uint8_t count = *addr++; + if (count-- == 0) + break; + display_writecommand_cont(*addr++); + while (count-- > 0) + { + display_writedata8_cont(*addr++); + } + } + display_writecommand_last(ILI9341_SLPOUT); + display_end_spi_transaction(); + + //Wait a short time + delay(120); + + //Turn the display on + display_begin_spi_transaction(); + display_writecommand_last(ILI9341_DISPON); + display_end_spi_transaction(); + + //Init font & transparency + cfont.font = 0; + transparent = 0; + + //Set the display rotation + display_setRotation(45); + + //Disable write to image + display_writeToImage = 0; + + //Return the diagnostic info + return diag; +} + +/* Init the display module */ +void display_init() +{ + byte count = 0; + //Init the display + byte check = display_InitLCD(); + + //Status not okay, try again 10 times + while ((check != 0x00) && (count < 10)) + { + delay(10); + check = display_InitLCD(); + count++; + } + //If it failed after 10 attemps, show diag + if (check != 0x00) + setDiagnostic(diag_display); + + //Read 180° rotation + byte read = EEPROM.read(eeprom_rotationVert); + if ((read == 0) || (read == 1)) + rotationVert = read; + else + rotationVert = 0; +} + +/* Set the xy coordinates */ +void display_setXY(word x1, word y1, word x2, word y2) +{ + if (orient == LANDSCAPE) + { + swap(word, x1, y1); + swap(word, x2, y2); + y1 = 239 - y1; + y2 = 239 - y2; + swap(word, y1, y2); + } + + //Write to the display + if (!display_writeToImage) + { + display_begin_spi_transaction(); + display_setAddr(x1, y1, x2, y2); + display_writecommand_last(ILI9341_RAMWR); // write to RAM + display_end_spi_transaction(); + } + //Write to the image buffer + else + { + imageX = x1; + imageY = y1; + } +} + +/* Clear the xy coordinates */ +void display_clrXY() +{ + if (orient == PORTRAIT) + display_setXY(0, 0, 319, 239); + else + display_setXY(0, 0, 239, 319); +} + +/* Clear the screen */ +void display_clrScr() +{ + display_setXY(0, 0, 239, 319); +} + +/* Draw a pixel */ +void display_drawPixel(int x, int y) +{ + //Out of borders, return + if ((x < 0) || (x >= 320) || (y < 0) || (y >= 240)) + return; + //Send pixel coordinates and color to screen + display_begin_spi_transaction(); + display_setAddr(x, y, x, y); + display_writecommand_cont(ILI9341_RAMWR); + display_writedata16_last(fch << 8 | fcl); + display_end_spi_transaction(); +} + +/* Draw a horizontal line */ +void display_drawHLine(int x, int y, int l) +{ + //Clipping + if ((x >= 320) || (y >= 240)) + return; + if ((x + l - 1) >= 320) + l = 320 - x; + + display_begin_spi_transaction(); + display_setAddr(x, y, x + l - 1, y); + display_writecommand_cont(ILI9341_RAMWR); + word color = (fch << 8 | fcl); + while (l-- > 1) + { + display_writedata16_cont(color); + } + display_writedata16_last(color); + display_end_spi_transaction(); +} + +/* Draw a vertical line */ +void display_drawVLine(int x, int y, int l) +{ + //Clipping + if ((x >= 320) || (y >= 240)) + return; + if ((y + l - 1) >= 240) + l = 240 - y; + + display_begin_spi_transaction(); + display_setAddr(x, y, x, y + l - 1); + display_writecommand_cont(ILI9341_RAMWR); + word color = (fch << 8 | fcl); + while (l-- > 1) + { + display_writedata16_cont(color); + } + display_writedata16_last(color); + display_end_spi_transaction(); +} + +/* Set a specific pixel in that color */ +void display_setPixel(word color) +{ + uint32_t pos; + + //Write to display + if (!display_writeToImage) + { + display_begin_spi_transaction(); + display_writedata16_last(color); + display_end_spi_transaction(); + } + //Write to buffer directly + else + { + pos = ((imageY)*320) + imageX; + if (pos < 76800) + bigBuffer[pos] = color; + } +} + +/* Write the data to the LCD */ +void display_LCD_Write_DATA(char VH, char VL) +{ + display_setPixel((VH << 8) | VL); +} + +/* Draw a line */ +void display_drawLine(int x1, int y1, int x2, int y2) +{ + //Write to screen + if ((y1 == y2) && (!display_writeToImage)) + display_drawHLine(x1, y1, x2 - x1); + else if ((x1 == x2) && (!display_writeToImage)) + display_drawVLine(x1, y1, y2 - y1); + //Write to image buffer directly + else + { + unsigned int dx = (x2 > x1 ? x2 - x1 : x1 - x2); + short xstep = x2 > x1 ? 1 : -1; + unsigned int dy = (y2 > y1 ? y2 - y1 : y1 - y2); + short ystep = y2 > y1 ? 1 : -1; + int col = x1, row = y1; + + if (dx < dy) + { + int t = -(dy >> 1); + while (1) + { + display_setXY(col, row, col, row); + display_LCD_Write_DATA(fch, fcl); + if (row == y2) + return; + row += ystep; + t += dx; + if (t >= 0) + { + col += xstep; + t -= dy; + } + } + } + else + { + int t = -(dx >> 1); + while (1) + { + display_setXY(col, row, col, row); + display_LCD_Write_DATA(fch, fcl); + if (col == x2) + return; + col += xstep; + t += dy; + if (t >= 0) + { + row += ystep; + t -= dx; + } + } + } + } + display_clrXY(); +} + +/* Fill the screen by RGB565 color */ +void display_fillScr(word color) +{ + int x = 0; + int y = 0; + display_begin_spi_transaction(); + display_setAddr(x, y, x + 319, y + 239); + display_writecommand_cont(ILI9341_RAMWR); + for (y = 240; y > 0; y--) + { + for (x = 320; x > 1; x--) + { + display_writedata16_cont(color); + } + display_writedata16_last(color); + } + display_end_spi_transaction(); +} + +/* Fill the screen by separate RGB value */ +void display_fillScr(byte r, byte g, byte b) +{ + word color = ((r & 248) << 8 | (g & 252) << 3 | (b & 248) >> 3); + display_fillScr(color); +} + +/* Draw an empty rectangle */ +void display_drawRect(int x1, int y1, int x2, int y2) +{ + if (x1 > x2) + { + swap(int, x1, x2); + } + if (y1 > y2) + { + swap(int, y1, y2); + } + + display_drawHLine(x1, y1, x2 - x1); + display_drawHLine(x1, y2, x2 - x1); + display_drawVLine(x1, y1, y2 - y1); + display_drawVLine(x2, y1, y2 - y1); +} + +/* Fill a rectangle */ +void display_fillRect(int x1, int y1, int x2, int y2) +{ + if (x1 > x2) + { + swap(int, x1, x2); + } + if (y1 > y2) + { + swap(int, y1, y2); + } + + int w = x2 - x1; + int h = y2 - y1; + + //Clipping + if ((x1 >= 320) || (y1 >= 240)) + return; + if ((x1 + w - 1) >= 320) + w = 320 - x1; + if ((y1 + h - 1) >= 240) + h = 240 - y1; + + //Send to display + display_begin_spi_transaction(); + display_setAddr(x1, y1, x1 + w - 1, y1 + h - 1); + display_writecommand_cont(ILI9341_RAMWR); + word color = (fch << 8 | fcl); + for (int y = h; y > 0; y--) + { + for (int x = w; x > 1; x--) + { + display_writedata16_cont(color); + } + display_writedata16_last(color); + } + display_end_spi_transaction(); +} + +/* Draw an empty round rectangle */ +void display_drawRoundRect(int x1, int y1, int x2, int y2) +{ + if (x1 > x2) + { + swap(int, x1, x2); + } + if (y1 > y2) + { + swap(int, y1, y2); + } + if ((x2 - x1) > 4 && (y2 - y1) > 4) + { + display_drawPixel(x1 + 1, y1 + 1); + display_drawPixel(x2 - 1, y1 + 1); + display_drawPixel(x1 + 1, y2 - 1); + display_drawPixel(x2 - 1, y2 - 1); + display_drawHLine(x1 + 2, y1, x2 - x1 - 4); + display_drawHLine(x1 + 2, y2, x2 - x1 - 4); + display_drawVLine(x1, y1 + 2, y2 - y1 - 4); + display_drawVLine(x2, y1 + 2, y2 - y1 - 4); + } +} + +/* Fill a round rectangle */ +void display_fillRoundRect(int x1, int y1, int x2, int y2) +{ + if (x1 > x2) + { + swap(int, x1, x2); + } + if (y1 > y2) + { + swap(int, y1, y2); + } + + if ((x2 - x1) > 4 && (y2 - y1) > 4) + { + for (int i = 0; i < ((y2 - y1) / 2) + 1; i++) + { + switch (i) + { + case 0: + display_drawHLine(x1 + 2, y1 + i, x2 - x1 - 4); + display_drawHLine(x1 + 2, y2 - i, x2 - x1 - 4); + break; + case 1: + display_drawHLine(x1 + 1, y1 + i, x2 - x1 - 2); + display_drawHLine(x1 + 1, y2 - i, x2 - x1 - 2); + break; + default: + display_drawHLine(x1, y1 + i, x2 - x1); + display_drawHLine(x1, y2 - i, x2 - x1); + } + } + } +} + +/* Draw an empty circle */ +void display_drawCircle(int x, int y, int radius) +{ + int f = 1 - radius; + int ddF_x = 1; + int ddF_y = -2 * radius; + int x1 = 0; + int y1 = radius; + + display_setXY(x, y + radius, x, y + radius); + display_LCD_Write_DATA(fch, fcl); + display_setXY(x, y - radius, x, y - radius); + display_LCD_Write_DATA(fch, fcl); + display_setXY(x + radius, y, x + radius, y); + display_LCD_Write_DATA(fch, fcl); + display_setXY(x - radius, y, x - radius, y); + display_LCD_Write_DATA(fch, fcl); + + while (x1 < y1) + { + if (f >= 0) + { + y1--; + ddF_y += 2; + f += ddF_y; + } + x1++; + ddF_x += 2; + f += ddF_x; + display_setXY(x + x1, y + y1, x + x1, y + y1); + display_LCD_Write_DATA(fch, fcl); + display_setXY(x - x1, y + y1, x - x1, y + y1); + display_LCD_Write_DATA(fch, fcl); + display_setXY(x + x1, y - y1, x + x1, y - y1); + display_LCD_Write_DATA(fch, fcl); + display_setXY(x - x1, y - y1, x - x1, y - y1); + display_LCD_Write_DATA(fch, fcl); + display_setXY(x + y1, y + x1, x + y1, y + x1); + display_LCD_Write_DATA(fch, fcl); + display_setXY(x - y1, y + x1, x - y1, y + x1); + display_LCD_Write_DATA(fch, fcl); + display_setXY(x + y1, y - x1, x + y1, y - x1); + display_LCD_Write_DATA(fch, fcl); + display_setXY(x - y1, y - x1, x - y1, y - x1); + display_LCD_Write_DATA(fch, fcl); + } + display_clrXY(); +} + +/* Fill a circle */ +void display_fillCircle(int x, int y, int radius) +{ + for (int y1 = -radius; y1 <= 0; y1++) + { + for (int x1 = -radius; x1 <= 0; x1++) + { + if (x1 * x1 + y1 * y1 <= radius * radius) + { + display_drawHLine(x + x1, y + y1, 2 * (-x1)); + display_drawHLine(x + x1, y - y1, 2 * (-x1)); + break; + } + } + } +} + +/* Set color to separate RGB values */ +void display_setColor(byte r, byte g, byte b) +{ + fch = ((r & 248) | g >> 5); + fcl = ((g & 28) << 3 | b >> 3); +} + +/* Set color to RGB565 color */ +void display_setColor(word color) +{ + fch = byte(color >> 8); + fcl = byte(color & 0xFF); +} + +/* Get current RGB565 color */ +word display_getColor() +{ + return (fch << 8) | fcl; +} + +/* Set back color to separate RGB value */ +void display_setBackColor(byte r, byte g, byte b) +{ + bch = ((r & 248) | g >> 5); + bcl = ((g & 28) << 3 | b >> 3); + transparent = 0; +} + +/* Set back color to RGB565 value */ +void display_setBackColor(uint32_t color) +{ + if (color == VGA_TRANSPARENT) + transparent = 1; + else + { + bch = byte(color >> 8); + bcl = byte(color & 0xFF); + transparent = 0; + } +} + +/* Get back color as RGB565 value */ +word display_getBackColor() +{ + return (bch << 8) | bcl; +} + +/* Print a specific char */ +void display_printChar(byte c, int x, int y) +{ + byte i, ch; + word j; + word temp; + + //Not transparent + if (!transparent) + { + if (orient == PORTRAIT) + { + display_setXY(x, y, x + cfont.x_size - 1, y + cfont.y_size - 1); + + temp = ((c - cfont.offset) * ((cfont.x_size / 8) * cfont.y_size)) + 4; + for (j = 0; j < ((cfont.x_size / 8) * cfont.y_size); j++) + { + ch = pgm_read_byte(&cfont.font[temp]); + for (i = 0; i < 8; i++) + { + if ((ch & (1 << (7 - i))) != 0) + { + display_setPixel((fch << 8) | fcl); + } + else + { + display_setPixel((bch << 8) | bcl); + } + } + temp++; + } + } + else + { + temp = ((c - cfont.offset) * ((cfont.x_size / 8) * cfont.y_size)) + 4; + + for (j = 0; j < ((cfont.x_size / 8) * cfont.y_size); + j += (cfont.x_size / 8)) + { + display_setXY(x, y + (j / (cfont.x_size / 8)), x + cfont.x_size - 1, + y + (j / (cfont.x_size / 8))); + for (int zz = (cfont.x_size / 8) - 1; zz >= 0; zz--) + { + ch = pgm_read_byte(&cfont.font[temp + zz]); + for (i = 0; i < 8; i++) + { + if ((ch & (1 << i)) != 0) + { + display_setPixel((fch << 8) | fcl); + } + else + { + display_setPixel((bch << 8) | bcl); + } + } + } + temp += (cfont.x_size / 8); + } + } + } + + //Transparent + else + { + temp = ((c - cfont.offset) * ((cfont.x_size / 8) * cfont.y_size)) + 4; + for (j = 0; j < cfont.y_size; j++) + { + for (int zz = 0; zz < (cfont.x_size / 8); zz++) + { + ch = pgm_read_byte(&cfont.font[temp + zz]); + for (i = 0; i < 8; i++) + { + display_setXY(x + i + (zz * 8), y + j, x + i + (zz * 8) + 1, + y + j + 1); + + if ((ch & (1 << (7 - i))) != 0) + { + display_setPixel((fch << 8) | fcl); + } + } + } + temp += (cfont.x_size / 8); + } + } + + display_clrXY(); +} + +/* Get the font height */ +int display_getFontHeight() +{ + return (cfont.y_size); +} + +/* Return the Glyph data for an individual character in the font*/ +boolean display_getCharPtr(byte c, propFont &fontChar) +{ + byte *tempPtr = cfont.font + 4; // point at data + + do + { + fontChar.charCode = pgm_read_byte(tempPtr++); + fontChar.adjYOffset = pgm_read_byte(tempPtr++); + fontChar.width = pgm_read_byte(tempPtr++); + fontChar.height = pgm_read_byte(tempPtr++); + fontChar.xOffset = pgm_read_byte(tempPtr++); + fontChar.xOffset = fontChar.xOffset < 0x80 ? fontChar.xOffset : (0x100 - fontChar.xOffset); + fontChar.xDelta = pgm_read_byte(tempPtr++); + if (c != fontChar.charCode && fontChar.charCode != 0xFF) + { + if (fontChar.width != 0) + { + // packed bits + tempPtr += (((fontChar.width * fontChar.height) - 1) / 8) + 1; + } + } + } while (c != fontChar.charCode && fontChar.charCode != 0xFF); + + fontChar.dataPtr = tempPtr; + + return (fontChar.charCode != 0xFF); +} + +/* Print a proportional char */ +int display_printProportionalChar(byte c, int x, int y) +{ + byte i, j; + byte ch = 0; + byte *tempPtr; + + propFont fontChar; + if (!display_getCharPtr(c, fontChar)) + { + return 0; + } + + word fcolor = display_getColor(); + if (!transparent) + { + int fontHeight = display_getFontHeight(); + display_setColor(display_getBackColor()); + display_fillRect(x, y, x + fontChar.xDelta + 1, y + fontHeight); + display_setColor(fcolor); + } + + tempPtr = fontChar.dataPtr; + + if (fontChar.width != 0) + { + byte mask = 0x80; + for (j = 0; j < fontChar.height; j++) + { + for (i = 0; i < fontChar.width; i++) + { + if (((i + (j * fontChar.width)) % 8) == 0) + { + mask = 0x80; + ch = pgm_read_byte(tempPtr++); + } + + if ((ch & mask) != 0) + { + display_setXY(x + fontChar.xOffset + i, y + j + fontChar.adjYOffset, + x + fontChar.xOffset + i, y + j + fontChar.adjYOffset); + display_setPixel(fcolor); + } + + mask >>= 1; + } + } + } + return fontChar.xDelta; +} + +/* Rotate a proportional char */ +int display_rotatePropChar(byte c, int x, int y, int offset, int deg) +{ + propFont fontChar; + + if (!display_getCharPtr(c, fontChar)) + { + return 0; + } + + byte ch = 0; + byte *tempPtr = fontChar.dataPtr; + double radian = deg * 0.0175; + + word fcolor = display_getColor(); + + if (fontChar.width != 0) + { + byte mask = 0x80; + float cos_radian = cos(radian); + float sin_radian = sin(radian); + for (int j = 0; j < fontChar.height; j++) + { + for (int i = 0; i < fontChar.width; i++) + { + if (((i + (j * fontChar.width)) % 8) == 0) + { + mask = 0x80; + ch = pgm_read_byte(tempPtr++); + } + + int newX = x + ((offset + i) * cos_radian - (j + fontChar.adjYOffset) * sin_radian); + int newY = y + ((j + fontChar.adjYOffset) * cos_radian + (offset + i) * sin_radian); + if ((ch & mask) != 0) + { + display_setXY(newX, newY, newX, newY); + display_setPixel(fcolor); + } + else + { + if (!transparent) + { + display_setXY(newX, newY, newX, newY); + display_setPixel(display_getBackColor()); + } + } + mask >>= 1; + } + } + } + + display_clrXY(); + + return fontChar.xDelta; +} + +/* Rotate a char on the display */ +void display_rotateChar(byte c, int x, int y, int pos, int deg) +{ + byte i, j, ch; + word temp; + int newx, newy; + double radian; + radian = deg * 0.0175; + + temp = ((c - cfont.offset) * ((cfont.x_size / 8) * cfont.y_size)) + 4; + for (j = 0; j < cfont.y_size; j++) + { + for (int zz = 0; zz < (cfont.x_size / 8); zz++) + { + ch = pgm_read_byte(&cfont.font[temp + zz]); + for (i = 0; i < 8; i++) + { + newx = x + (((i + (zz * 8) + (pos * cfont.x_size)) * cos(radian)) - ((j)*sin(radian))); + newy = y + (((j)*cos(radian)) + ((i + (zz * 8) + (pos * cfont.x_size)) * sin(radian))); + + display_setXY(newx, newy, newx + 1, newy + 1); + + if ((ch & (1 << (7 - i))) != 0) + { + display_setPixel((fch << 8) | fcl); + } + else + { + if (!transparent) + display_setPixel((bch << 8) | bcl); + } + } + } + temp += (cfont.x_size / 8); + } + display_clrXY(); +} + +/* Print char array on the display */ +void display_print(char *st, int x, int y, int deg) +{ + int stl, i; + stl = strlen(st); + + if (orient == PORTRAIT) + { + if (x == RIGHT) + x = 320 - (stl * cfont.x_size); + if (x == CENTER) + x = (320 - (stl * cfont.x_size)) / 2; + } + else + { + if (x == RIGHT) + x = 240 - (stl * cfont.x_size); + if (x == CENTER) + x = (240 - (stl * cfont.x_size)) / 2; + } + + int offset = 0; + for (i = 0; i < stl; i++) + { + if (deg == 0) + { + if (cfont.x_size == 0) + x += display_printProportionalChar(*st++, x, y) + 1; + else + { + display_printChar(*st++, x, y); + x += cfont.x_size; + } + } + else + { + if (cfont.x_size == 0) + offset += display_rotatePropChar(*st++, x, y, offset, deg); + else + display_rotateChar(*st++, x, y, i, deg); + } + } +} + +/* Print a rotated string on the display */ +void display_print(String st, int x, int y, int deg) +{ + char buf[st.length() + 1]; + st.toCharArray(buf, st.length() + 1); + display_print(buf, x, y, deg); +} + +/* Print string on the display */ +void display_printC(String st, int x, int y, uint32_t color) +{ + char buf[st.length() + 1]; + display_setColor(color); + st.toCharArray(buf, st.length() + 1); + display_print(buf, x, y, 0); +} + +/* Print an integer */ +void display_printNumI(long num, int x, int y, int length, char filler) +{ + char buf[25]; + char st[27]; + boolean neg = 0; + int c = 0, f = 0; + + if (num == 0) + { + if (length != 0) + { + for (c = 0; c < (length - 1); c++) + st[c] = filler; + st[c] = 48; + st[c + 1] = 0; + } + else + { + st[0] = 48; + st[1] = 0; + } + } + else + { + if (num < 0) + { + neg = 1; + num = -num; + } + + while (num > 0) + { + buf[c] = 48 + (num % 10); + c++; + num = (num - (num % 10)) / 10; + } + buf[c] = 0; + + if (neg) + { + st[0] = 45; + } + + if (length > (c + neg)) + { + for (int i = 0; i < (length - c - neg); i++) + { + st[i + neg] = filler; + f++; + } + } + + for (int i = 0; i < c; i++) + { + st[i + neg + f] = buf[c - i - 1]; + } + st[c + neg + f] = 0; + } + + display_print(st, x, y); +} + +/* Helper method to convert a float*/ +void display_convertFloat(char *buf, double num, int width, byte prec) +{ + dtostrf(num, width, prec, buf); +} + +/* Print a float */ +void display_printNumF(double num, byte dec, int x, int y, char divider, int length, char filler) +{ + char st[27]; + boolean neg = 0; + + if (dec < 1) + dec = 1; + else if (dec > 5) + dec = 5; + + if (num < 0) + neg = 1; + display_convertFloat(st, num, length, dec); + if (divider != '.') + { + for (uint16_t i = 0; i < sizeof(st); i++) + if (st[i] == '.') + st[i] = divider; + } + + if (filler != ' ') + { + if (neg) + { + st[0] = '-'; + for (uint16_t i = 1; i < sizeof(st); i++) + if ((st[i] == ' ') || (st[i] == '-')) + st[i] = filler; + } + else + { + for (uint16_t i = 0; i < sizeof(st); i++) + if (st[i] == ' ') + st[i] = filler; + } + } + + display_print(st, x, y); +} + +/* Set a specific font */ +void display_setFont(const uint8_t *font) +{ + cfont.font = (uint8_t *)font; + cfont.x_size = fontbyte(0); + cfont.y_size = fontbyte(1); + cfont.offset = fontbyte(2); + cfont.numchars = fontbyte(3); +} + +/* Get the current font */ +uint8_t *display_getFont() +{ + return cfont.font; +} + +/* Get the x size of the current font */ +uint8_t display_getFontXsize() +{ + return cfont.x_size; +} + +/* Get the y size of the current font */ +uint8_t display_getFontYsize() +{ + return cfont.y_size; +} + +/* Draw a bitmap on the screen */ +void display_drawBitmap(int x, int y, int w, int h, unsigned short *data) +{ + display_begin_spi_transaction(); + display_setAddr(x, y, x + w - 1, y + h - 1); + display_writecommand_cont(ILI9341_RAMWR); + + for (y = h; y > 0; y--) + { + for (x = w; x > 1; x--) + { + display_writedata16_cont(*data++); + } + display_writedata16_last(*data++); + } + + display_end_spi_transaction(); +} + +/* Write a paletted bitmap with 2BPP */ +void display_writeRect2BPP(int16_t x, int16_t y, int16_t w, int16_t h, const uint8_t *pixels, const uint16_t *palette) +{ + display_begin_spi_transaction(); + display_setAddr(x, y, x + w - 1, y + h - 1); + display_writecommand_cont(ILI9341_RAMWR); + for (y = h; y > 0; y--) + { + for (x = w; x > 4; x -= 4) + { + display_writedata16_cont(palette[((*pixels) >> 6) & 0x3]); + display_writedata16_cont(palette[((*pixels) >> 4) & 0x3]); + display_writedata16_cont(palette[((*pixels) >> 2) & 0x3]); + display_writedata16_cont(palette[(*pixels++) & 0x3]); + } + display_writedata16_cont(palette[((*pixels) >> 6) & 0x3]); + display_writedata16_cont(palette[((*pixels) >> 4) & 0x3]); + display_writedata16_cont(palette[((*pixels) >> 2) & 0x3]); + display_writedata16_last(palette[(*pixels++) & 0x3]); + } + display_end_spi_transaction(); +} + +/* Write a paletted bitmap with 4BPP */ +void display_writeRect4BPP(int16_t x, int16_t y, int16_t w, int16_t h, const uint8_t *pixels, const uint16_t *palette) +{ + display_begin_spi_transaction(); + display_setAddr(x, y, x + w - 1, y + h - 1); + display_writecommand_cont(ILI9341_RAMWR); + for (y = h; y > 0; y--) + { + for (x = w; x > 2; x -= 2) + { + display_writedata16_cont(palette[((*pixels) >> 4) & 0xF]); + display_writedata16_cont(palette[(*pixels++) & 0xF]); + } + display_writedata16_cont(palette[((*pixels) >> 4) & 0xF]); + display_writedata16_last(palette[(*pixels++) & 0xF]); + } + display_end_spi_transaction(); +} + +/* Returns the string width in pixels */ +int display_getStringWidth(char *str) +{ + //Fixed font width + if (cfont.x_size != 0) + { + return (strlen(str) * cfont.x_size); + } + + //Calculate the string width + int strWidth = 0; + while (*str != 0) + { + propFont fontChar; + boolean found = display_getCharPtr(*str, fontChar); + + if (found && *str == fontChar.charCode) + { + strWidth += fontChar.xDelta + 1; + } + + str++; + } + + return strWidth; +} + +/* Enter sleep mode */ +void display_enterSleepMode() +{ + display_begin_spi_transaction(); + display_writecommand_last(ILI9341_SLPIN); + display_end_spi_transaction(); +} + +/* Exit sleep mode */ +void display_exitSleepMode() +{ + display_begin_spi_transaction(); + display_writecommand_last(ILI9341_SLPOUT); + display_end_spi_transaction(); +} + +/* Write RGB565 data to the screen */ +void display_writeScreen(unsigned short *pcolors, boolean small) +{ + display_begin_spi_transaction(); + display_setAddr(0, 0, 319, 239); + display_writecommand_cont(ILI9341_RAMWR); + + //160x120 array, doubled + if (small) + { + for (byte y = 120; y > 0; y--) + { + for (byte x = 160; x > 1; x--) + { + display_writedata16_cont(*pcolors); + display_writedata16_cont(*pcolors++); + } + display_writedata16_cont(*pcolors); + display_writedata16_last(*pcolors++); + pcolors = pcolors - 160; + for (byte x = 160; x > 1; x--) + { + display_writedata16_cont(*pcolors); + display_writedata16_cont(*pcolors++); + } + display_writedata16_cont(*pcolors); + display_writedata16_last(*pcolors++); + } + } + //320x240 array + else + { + uint16_t *colors_end = &pcolors[76799]; + uint16_t *colors_curr = pcolors; + + // Quick write out the data; + while (colors_curr < colors_end) + { + display_writedata16_cont(*colors_curr++); + } + display_writedata16_last(*colors_curr); + } + + display_end_spi_transaction(); +} \ No newline at end of file diff --git a/Firmware_V3/src/hardware/display/fonts.cpp b/Firmware_V3/src/hardware/display/fonts.cpp new file mode 100644 index 0000000..09af272 --- /dev/null +++ b/Firmware_V3/src/hardware/display/fonts.cpp @@ -0,0 +1,226 @@ +/* +* +* Fonts +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include + +/*############################# PUBLIC VARIABLES ##############################*/ + +/* Small Font */ +const uint8_t smallFont[] = { + 0x08,0x0C,0x20,0x5F, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // + 0x00,0x00,0x20,0x20,0x20,0x20,0x20,0x20,0x00,0x20,0x00,0x00, // ! + 0x00,0x28,0x50,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // " + 0x00,0x00,0x28,0x28,0xFC,0x28,0x50,0xFC,0x50,0x50,0x00,0x00, // # + 0x00,0x20,0x78,0xA8,0xA0,0x60,0x30,0x28,0xA8,0xF0,0x20,0x00, // $ + 0x00,0x00,0x48,0xA8,0xB0,0x50,0x28,0x34,0x54,0x48,0x00,0x00, // % + 0x00,0x00,0x20,0x50,0x50,0x78,0xA8,0xA8,0x90,0x6C,0x00,0x00, // & + 0x00,0x40,0x40,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ' + 0x00,0x04,0x08,0x10,0x10,0x10,0x10,0x10,0x10,0x08,0x04,0x00, // ( + 0x00,0x40,0x20,0x10,0x10,0x10,0x10,0x10,0x10,0x20,0x40,0x00, // ) + 0x00,0x00,0x00,0x20,0xA8,0x70,0x70,0xA8,0x20,0x00,0x00,0x00, // * + 0x00,0x00,0x20,0x20,0x20,0xF8,0x20,0x20,0x20,0x00,0x00,0x00, // + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x40,0x80, // , + 0x00,0x00,0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00, // - + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00, // . + 0x00,0x08,0x10,0x10,0x10,0x20,0x20,0x40,0x40,0x40,0x80,0x00, // / + 0x00,0x00,0x70,0x88,0x88,0x88,0x88,0x88,0x88,0x70,0x00,0x00, // 0 + 0x00,0x00,0x20,0x60,0x20,0x20,0x20,0x20,0x20,0x70,0x00,0x00, // 1 + 0x00,0x00,0x70,0x88,0x88,0x10,0x20,0x40,0x80,0xF8,0x00,0x00, // 2 + 0x00,0x00,0x70,0x88,0x08,0x30,0x08,0x08,0x88,0x70,0x00,0x00, // 3 + 0x00,0x00,0x10,0x30,0x50,0x50,0x90,0x78,0x10,0x18,0x00,0x00, // 4 + 0x00,0x00,0xF8,0x80,0x80,0xF0,0x08,0x08,0x88,0x70,0x00,0x00, // 5 + 0x00,0x00,0x70,0x90,0x80,0xF0,0x88,0x88,0x88,0x70,0x00,0x00, // 6 + 0x00,0x00,0xF8,0x90,0x10,0x20,0x20,0x20,0x20,0x20,0x00,0x00, // 7 + 0x00,0x00,0x70,0x88,0x88,0x70,0x88,0x88,0x88,0x70,0x00,0x00, // 8 + 0x00,0x00,0x70,0x88,0x88,0x88,0x78,0x08,0x48,0x70,0x00,0x00, // 9 + 0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x20,0x00,0x00, // : + 0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x20,0x20,0x00, // ; + 0x00,0x04,0x08,0x10,0x20,0x40,0x20,0x10,0x08,0x04,0x00,0x00, // < + 0x00,0x00,0x00,0x00,0xF8,0x00,0x00,0xF8,0x00,0x00,0x00,0x00, // = + 0x00,0x40,0x20,0x10,0x08,0x04,0x08,0x10,0x20,0x40,0x00,0x00, // > + 0x00,0x00,0x70,0x88,0x88,0x10,0x20,0x20,0x00,0x20,0x00,0x00, // ? + 0x00,0x00,0x70,0x88,0x98,0xA8,0xA8,0xB8,0x80,0x78,0x00,0x00, // @ + 0x00,0x00,0x20,0x20,0x30,0x50,0x50,0x78,0x48,0xCC,0x00,0x00, // A + 0x00,0x00,0xF0,0x48,0x48,0x70,0x48,0x48,0x48,0xF0,0x00,0x00, // B + 0x00,0x00,0x78,0x88,0x80,0x80,0x80,0x80,0x88,0x70,0x00,0x00, // C + 0x00,0x00,0xF0,0x48,0x48,0x48,0x48,0x48,0x48,0xF0,0x00,0x00, // D + 0x00,0x00,0xF8,0x48,0x50,0x70,0x50,0x40,0x48,0xF8,0x00,0x00, // E + 0x00,0x00,0xF8,0x48,0x50,0x70,0x50,0x40,0x40,0xE0,0x00,0x00, // F + 0x00,0x00,0x38,0x48,0x80,0x80,0x9C,0x88,0x48,0x30,0x00,0x00, // G + 0x00,0x00,0xCC,0x48,0x48,0x78,0x48,0x48,0x48,0xCC,0x00,0x00, // H + 0x00,0x00,0xF8,0x20,0x20,0x20,0x20,0x20,0x20,0xF8,0x00,0x00, // I + 0x00,0x00,0x7C,0x10,0x10,0x10,0x10,0x10,0x10,0x90,0xE0,0x00, // J + 0x00,0x00,0xEC,0x48,0x50,0x60,0x50,0x50,0x48,0xEC,0x00,0x00, // K + 0x00,0x00,0xE0,0x40,0x40,0x40,0x40,0x40,0x44,0xFC,0x00,0x00, // L + 0x00,0x00,0xD8,0xD8,0xD8,0xD8,0xA8,0xA8,0xA8,0xA8,0x00,0x00, // M + 0x00,0x00,0xDC,0x48,0x68,0x68,0x58,0x58,0x48,0xE8,0x00,0x00, // N + 0x00,0x00,0x70,0x88,0x88,0x88,0x88,0x88,0x88,0x70,0x00,0x00, // O + 0x00,0x00,0xF0,0x48,0x48,0x70,0x40,0x40,0x40,0xE0,0x00,0x00, // P + 0x00,0x00,0x70,0x88,0x88,0x88,0x88,0xE8,0x98,0x70,0x18,0x00, // Q + 0x00,0x00,0xF0,0x48,0x48,0x70,0x50,0x48,0x48,0xEC,0x00,0x00, // R + 0x00,0x00,0x78,0x88,0x80,0x60,0x10,0x08,0x88,0xF0,0x00,0x00, // S + 0x00,0x00,0xF8,0xA8,0x20,0x20,0x20,0x20,0x20,0x70,0x00,0x00, // T + 0x00,0x00,0xCC,0x48,0x48,0x48,0x48,0x48,0x48,0x30,0x00,0x00, // U + 0x00,0x00,0xCC,0x48,0x48,0x50,0x50,0x30,0x20,0x20,0x00,0x00, // V + 0x00,0x00,0xA8,0xA8,0xA8,0x70,0x50,0x50,0x50,0x50,0x00,0x00, // W + 0x00,0x00,0xD8,0x50,0x50,0x20,0x20,0x50,0x50,0xD8,0x00,0x00, // X + 0x00,0x00,0xD8,0x50,0x50,0x20,0x20,0x20,0x20,0x70,0x00,0x00, // Y + 0x00,0x00,0xF8,0x90,0x10,0x20,0x20,0x40,0x48,0xF8,0x00,0x00, // Z + 0x00,0x38,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x38,0x00, // [ + 0x00,0x40,0x40,0x40,0x20,0x20,0x10,0x10,0x10,0x08,0x00,0x00, // + 0x00,0x70,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x70,0x00, // ] + 0x00,0x20,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ^ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFC, // _ + 0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ' + 0x00,0x00,0x00,0x00,0x00,0x30,0x48,0x38,0x48,0x3C,0x00,0x00, // a + 0x00,0x00,0xC0,0x40,0x40,0x70,0x48,0x48,0x48,0x70,0x00,0x00, // b + 0x00,0x00,0x00,0x00,0x00,0x38,0x48,0x40,0x40,0x38,0x00,0x00, // c + 0x00,0x00,0x18,0x08,0x08,0x38,0x48,0x48,0x48,0x3C,0x00,0x00, // d + 0x00,0x00,0x00,0x00,0x00,0x30,0x48,0x78,0x40,0x38,0x00,0x00, // e + 0x00,0x00,0x1C,0x20,0x20,0x78,0x20,0x20,0x20,0x78,0x00,0x00, // f + 0x00,0x00,0x00,0x00,0x00,0x3C,0x48,0x30,0x40,0x78,0x44,0x38, // g + 0x00,0x00,0xC0,0x40,0x40,0x70,0x48,0x48,0x48,0xEC,0x00,0x00, // h + 0x00,0x00,0x20,0x00,0x00,0x60,0x20,0x20,0x20,0x70,0x00,0x00, // i + 0x00,0x00,0x10,0x00,0x00,0x30,0x10,0x10,0x10,0x10,0x10,0xE0, // j + 0x00,0x00,0xC0,0x40,0x40,0x5C,0x50,0x70,0x48,0xEC,0x00,0x00, // k + 0x00,0x00,0xE0,0x20,0x20,0x20,0x20,0x20,0x20,0xF8,0x00,0x00, // l + 0x00,0x00,0x00,0x00,0x00,0xF0,0xA8,0xA8,0xA8,0xA8,0x00,0x00, // m + 0x00,0x00,0x00,0x00,0x00,0xF0,0x48,0x48,0x48,0xEC,0x00,0x00, // n + 0x00,0x00,0x00,0x00,0x00,0x30,0x48,0x48,0x48,0x30,0x00,0x00, // o + 0x00,0x00,0x00,0x00,0x00,0xF0,0x48,0x48,0x48,0x70,0x40,0xE0, // p + 0x00,0x00,0x00,0x00,0x00,0x38,0x48,0x48,0x48,0x38,0x08,0x1C, // q + 0x00,0x00,0x00,0x00,0x00,0xD8,0x60,0x40,0x40,0xE0,0x00,0x00, // r + 0x00,0x00,0x00,0x00,0x00,0x78,0x40,0x30,0x08,0x78,0x00,0x00, // s + 0x00,0x00,0x00,0x20,0x20,0x70,0x20,0x20,0x20,0x18,0x00,0x00, // t + 0x00,0x00,0x00,0x00,0x00,0xD8,0x48,0x48,0x48,0x3C,0x00,0x00, // u + 0x00,0x00,0x00,0x00,0x00,0xEC,0x48,0x50,0x30,0x20,0x00,0x00, // v + 0x00,0x00,0x00,0x00,0x00,0xA8,0xA8,0x70,0x50,0x50,0x00,0x00, // w + 0x00,0x00,0x00,0x00,0x00,0xD8,0x50,0x20,0x50,0xD8,0x00,0x00, // x + 0x00,0x00,0x00,0x00,0x00,0xEC,0x48,0x50,0x30,0x20,0x20,0xC0, // y + 0x00,0x00,0x00,0x00,0x00,0x78,0x10,0x20,0x20,0x78,0x00,0x00, // z + 0x00,0x18,0x10,0x10,0x10,0x20,0x10,0x10,0x10,0x10,0x18,0x00, // { + 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, // | + 0x00,0x60,0x20,0x20,0x20,0x10,0x20,0x20,0x20,0x20,0x60,0x00, // } + 0x40,0xA4,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ~ +}; + +/* Big Font */ +const uint8_t bigFont[] = { + 0x10,0x10,0x20,0x5F, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // + 0x00,0x00,0x00,0x00,0x07,0x00,0x0F,0x80,0x0F,0x80,0x0F,0x80,0x0F,0x80,0x0F,0x80,0x07,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x00,0x00, // ! + 0x00,0x00,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x06,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // " + 0x00,0x00,0x0C,0x30,0x0C,0x30,0x0C,0x30,0x7F,0xFE,0x7F,0xFE,0x0C,0x30,0x0C,0x30,0x0C,0x30,0x0C,0x30,0x7F,0xFE,0x7F,0xFE,0x0C,0x30,0x0C,0x30,0x0C,0x30,0x00,0x00, // # + 0x00,0x00,0x02,0x40,0x02,0x40,0x0F,0xF8,0x1F,0xF8,0x1A,0x40,0x1A,0x40,0x1F,0xF0,0x0F,0xF8,0x02,0x58,0x02,0x58,0x1F,0xF8,0x1F,0xF0,0x02,0x40,0x02,0x40,0x00,0x00, // $ + 0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x10,0x0E,0x30,0x0E,0x70,0x00,0xE0,0x01,0xC0,0x03,0x80,0x07,0x00,0x0E,0x70,0x0C,0x70,0x08,0x70,0x00,0x00,0x00,0x00,0x00,0x00, // % + 0x00,0x00,0x00,0x00,0x0F,0x00,0x19,0x80,0x19,0x80,0x19,0x80,0x0F,0x00,0x0F,0x08,0x0F,0x98,0x19,0xF8,0x18,0xF0,0x18,0xE0,0x19,0xF0,0x0F,0x98,0x00,0x00,0x00,0x00, // & + 0x00,0x00,0x00,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ' + 0x00,0x00,0x00,0x00,0x00,0xF0,0x01,0xC0,0x03,0x80,0x07,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x07,0x00,0x03,0x80,0x01,0xC0,0x00,0xF0,0x00,0x00,0x00,0x00, // ( + 0x00,0x00,0x00,0x00,0x0F,0x00,0x03,0x80,0x01,0xC0,0x00,0xE0,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0xE0,0x01,0xC0,0x03,0x80,0x0F,0x00,0x00,0x00,0x00,0x00, // ) + 0x00,0x00,0x00,0x00,0x01,0x80,0x11,0x88,0x09,0x90,0x07,0xE0,0x07,0xE0,0x3F,0xFC,0x3F,0xFC,0x07,0xE0,0x07,0xE0,0x09,0x90,0x11,0x88,0x01,0x80,0x00,0x00,0x00,0x00, // * + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x80,0x01,0x80,0x01,0x80,0x0F,0xF0,0x0F,0xF0,0x01,0x80,0x01,0x80,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x0E,0x00,0x00,0x00, // , + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xF8,0x1F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // - + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x00,0x00,0x00,0x00, // , + 0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x06,0x00,0x0E,0x00,0x1C,0x00,0x38,0x00,0x70,0x00,0xE0,0x01,0xC0,0x03,0x80,0x07,0x00,0x0E,0x00,0x1C,0x00,0x00,0x00,0x00,0x00, // / + + 0x00,0x00,0x00,0x00,0x0F,0xF0,0x1C,0x38,0x1C,0x78,0x1C,0xF8,0x1C,0xF8,0x1D,0xB8,0x1D,0xB8,0x1F,0x38,0x1F,0x38,0x1E,0x38,0x1C,0x38,0x0F,0xF0,0x00,0x00,0x00,0x00, // 0 + 0x00,0x00,0x00,0x00,0x01,0x80,0x01,0x80,0x03,0x80,0x1F,0x80,0x1F,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x1F,0xF0,0x00,0x00,0x00,0x00, // 1 + 0x00,0x00,0x00,0x00,0x0F,0xE0,0x1C,0x70,0x1C,0x38,0x00,0x38,0x00,0x70,0x00,0xE0,0x01,0xC0,0x03,0x80,0x07,0x00,0x0E,0x38,0x1C,0x38,0x1F,0xF8,0x00,0x00,0x00,0x00, // 2 + 0x00,0x00,0x00,0x00,0x0F,0xE0,0x1C,0x70,0x1C,0x38,0x00,0x38,0x00,0x70,0x03,0xC0,0x03,0xC0,0x00,0x70,0x00,0x38,0x1C,0x38,0x1C,0x70,0x0F,0xE0,0x00,0x00,0x00,0x00, // 3 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x01,0xE0,0x03,0xE0,0x06,0xE0,0x0C,0xE0,0x18,0xE0,0x1F,0xF8,0x1F,0xF8,0x00,0xE0,0x00,0xE0,0x00,0xE0,0x03,0xF8,0x00,0x00,0x00,0x00, // 4 + 0x00,0x00,0x00,0x00,0x1F,0xF8,0x1C,0x00,0x1C,0x00,0x1C,0x00,0x1C,0x00,0x1F,0xE0,0x1F,0xF0,0x00,0x78,0x00,0x38,0x1C,0x38,0x1C,0x70,0x0F,0xE0,0x00,0x00,0x00,0x00, // 5 + 0x00,0x00,0x00,0x00,0x03,0xE0,0x07,0x00,0x0E,0x00,0x1C,0x00,0x1C,0x00,0x1F,0xF0,0x1F,0xF8,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x0F,0xF0,0x00,0x00,0x00,0x00, // 6 + 0x00,0x00,0x00,0x00,0x1F,0xFC,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x00,0x1C,0x00,0x38,0x00,0x70,0x00,0xE0,0x01,0xC0,0x03,0x80,0x03,0x80,0x03,0x80,0x00,0x00,0x00,0x00, // 7 + 0x00,0x00,0x00,0x00,0x0F,0xF0,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x1F,0x38,0x07,0xE0,0x07,0xE0,0x1C,0xF8,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x0F,0xF0,0x00,0x00,0x00,0x00, // 8 + 0x00,0x00,0x00,0x00,0x0F,0xF0,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x1F,0xF8,0x0F,0xF8,0x00,0x38,0x00,0x38,0x00,0x70,0x00,0xE0,0x07,0xC0,0x00,0x00,0x00,0x00, // 9 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x80,0x03,0x80,0x03,0x80,0x00,0x00,0x00,0x00,0x03,0x80,0x03,0x80,0x03,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // : + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x80,0x03,0x80,0x03,0x80,0x00,0x00,0x00,0x00,0x03,0x80,0x03,0x80,0x03,0x80,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ; + 0x00,0x00,0x00,0x70,0x00,0xE0,0x01,0xC0,0x03,0x80,0x07,0x00,0x0E,0x00,0x1C,0x00,0x1C,0x00,0x0E,0x00,0x07,0x00,0x03,0x80,0x01,0xC0,0x00,0xE0,0x00,0x70,0x00,0x00, // < + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0xFC,0x3F,0xFC,0x00,0x00,0x00,0x00,0x3F,0xFC,0x3F,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // = + 0x00,0x00,0x1C,0x00,0x0E,0x00,0x07,0x00,0x03,0x80,0x01,0xC0,0x00,0xE0,0x00,0x70,0x00,0x70,0x00,0xE0,0x01,0xC0,0x03,0x80,0x07,0x00,0x0E,0x00,0x1C,0x00,0x00,0x00, // > + 0x00,0x00,0x03,0xC0,0x0F,0xF0,0x1E,0x78,0x18,0x38,0x00,0x38,0x00,0x70,0x00,0xE0,0x01,0xC0,0x01,0xC0,0x00,0x00,0x00,0x00,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x00,0x00, // ? + + 0x00,0x00,0x0F,0xF8,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0xFC,0x1C,0xFC,0x1C,0xFC,0x1C,0xFC,0x1C,0x00,0x1C,0x00,0x1C,0x00,0x1F,0xF0,0x07,0xF8,0x00,0x00, // @ + 0x00,0x00,0x00,0x00,0x03,0xC0,0x07,0xE0,0x0E,0x70,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x1F,0xF8,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x00,0x00,0x00,0x00, // A + 0x00,0x00,0x00,0x00,0x1F,0xF0,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0F,0xF0,0x0F,0xF0,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x1F,0xF0,0x00,0x00,0x00,0x00, // B + 0x00,0x00,0x00,0x00,0x07,0xF0,0x0E,0x38,0x1C,0x38,0x1C,0x00,0x1C,0x00,0x1C,0x00,0x1C,0x00,0x1C,0x00,0x1C,0x00,0x1C,0x38,0x0E,0x38,0x07,0xF0,0x00,0x00,0x00,0x00, // C + 0x00,0x00,0x00,0x00,0x1F,0xE0,0x0E,0x70,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x70,0x1F,0xE0,0x00,0x00,0x00,0x00, // D + 0x00,0x00,0x00,0x00,0x1F,0xF8,0x0E,0x18,0x0E,0x08,0x0E,0x00,0x0E,0x30,0x0F,0xF0,0x0F,0xF0,0x0E,0x30,0x0E,0x00,0x0E,0x08,0x0E,0x18,0x1F,0xF8,0x00,0x00,0x00,0x00, // E + 0x00,0x00,0x00,0x00,0x1F,0xF8,0x0E,0x18,0x0E,0x08,0x0E,0x00,0x0E,0x30,0x0F,0xF0,0x0F,0xF0,0x0E,0x30,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x1F,0x00,0x00,0x00,0x00,0x00, // F + 0x00,0x00,0x00,0x00,0x07,0xF0,0x0E,0x38,0x1C,0x38,0x1C,0x38,0x1C,0x00,0x1C,0x00,0x1C,0x00,0x1C,0xF8,0x1C,0x38,0x1C,0x38,0x0E,0x38,0x07,0xF8,0x00,0x00,0x00,0x00, // G + 0x00,0x00,0x00,0x00,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1F,0xF0,0x1F,0xF0,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x00,0x00,0x00,0x00, // H + 0x00,0x00,0x00,0x00,0x0F,0xE0,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x0F,0xE0,0x00,0x00,0x00,0x00, // I + 0x00,0x00,0x00,0x00,0x01,0xFC,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x38,0x70,0x38,0x70,0x38,0x70,0x38,0x70,0x0F,0xE0,0x00,0x00,0x00,0x00, // J + 0x00,0x00,0x00,0x00,0x1E,0x38,0x0E,0x38,0x0E,0x70,0x0E,0xE0,0x0F,0xC0,0x0F,0x80,0x0F,0x80,0x0F,0xC0,0x0E,0xE0,0x0E,0x70,0x0E,0x38,0x1E,0x38,0x00,0x00,0x00,0x00, // K + 0x00,0x00,0x00,0x00,0x1F,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x08,0x0E,0x18,0x0E,0x38,0x1F,0xF8,0x00,0x00,0x00,0x00, // L + 0x00,0x00,0x00,0x00,0x1C,0x1C,0x1E,0x3C,0x1F,0x7C,0x1F,0xFC,0x1F,0xFC,0x1D,0xDC,0x1C,0x9C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x00,0x00,0x00,0x00, // M + 0x00,0x00,0x00,0x00,0x1C,0x1C,0x1C,0x1C,0x1E,0x1C,0x1F,0x1C,0x1F,0x9C,0x1D,0xDC,0x1C,0xFC,0x1C,0x7C,0x1C,0x3C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x00,0x00,0x00,0x00, // N + 0x00,0x00,0x00,0x00,0x03,0xE0,0x07,0xF0,0x0E,0x38,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x0E,0x38,0x07,0xF0,0x03,0xE0,0x00,0x00,0x00,0x00, // O + + 0x00,0x00,0x00,0x00,0x1F,0xF0,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0F,0xF0,0x0F,0xF0,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x1F,0x00,0x00,0x00,0x00,0x00, // P + 0x00,0x00,0x00,0x00,0x03,0xE0,0x0F,0x78,0x0E,0x38,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x7C,0x1C,0xFC,0x0F,0xF8,0x0F,0xF8,0x00,0x38,0x00,0xFC,0x00,0x00, // Q + 0x00,0x00,0x00,0x00,0x1F,0xF0,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0F,0xF0,0x0F,0xF0,0x0E,0x70,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x1E,0x38,0x00,0x00,0x00,0x00, // R + 0x00,0x00,0x00,0x00,0x0F,0xF0,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x1C,0x00,0x0F,0xE0,0x07,0xF0,0x00,0x38,0x1C,0x38,0x1C,0x38,0x1C,0x38,0x0F,0xF0,0x00,0x00,0x00,0x00, // S + 0x00,0x00,0x00,0x00,0x1F,0xFC,0x19,0xCC,0x11,0xC4,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x07,0xF0,0x00,0x00,0x00,0x00, // T + 0x00,0x00,0x00,0x00,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x0F,0xE0,0x00,0x00,0x00,0x00, // U + 0x00,0x00,0x00,0x00,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x0E,0xE0,0x07,0xC0,0x03,0x80,0x00,0x00,0x00,0x00, // V + 0x00,0x00,0x00,0x00,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x9C,0x1C,0x9C,0x1C,0x9C,0x0F,0xF8,0x0F,0xF8,0x07,0x70,0x07,0x70,0x00,0x00,0x00,0x00, // W + 0x00,0x00,0x00,0x00,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x0E,0xE0,0x07,0xC0,0x03,0x80,0x03,0x80,0x07,0xC0,0x0E,0xE0,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x00,0x00,0x00,0x00, // X + 0x00,0x00,0x00,0x00,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x0E,0xE0,0x07,0xC0,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x0F,0xE0,0x00,0x00,0x00,0x00, // Y + 0x00,0x00,0x00,0x00,0x1F,0xF8,0x1C,0x38,0x18,0x38,0x10,0x70,0x00,0xE0,0x01,0xC0,0x03,0x80,0x07,0x00,0x0E,0x08,0x1C,0x18,0x1C,0x38,0x1F,0xF8,0x00,0x00,0x00,0x00, // Z + 0x00,0x00,0x00,0x00,0x07,0xF0,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0xF0,0x00,0x00,0x00,0x00, // [ + 0x00,0x00,0x00,0x00,0x10,0x00,0x18,0x00,0x1C,0x00,0x0E,0x00,0x07,0x00,0x03,0x80,0x01,0xC0,0x00,0xE0,0x00,0x70,0x00,0x38,0x00,0x1C,0x00,0x07,0x00,0x00,0x00,0x00, // + 0x00,0x00,0x00,0x00,0x07,0xF0,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x07,0xF0,0x00,0x00,0x00,0x00, // ] + 0x00,0x00,0x01,0x80,0x03,0xC0,0x07,0xE0,0x0E,0x70,0x1C,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ^ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0xFF,0x7F,0xFF, // _ + + 0x00,0x00,0x00,0x00,0x1C,0x00,0x1C,0x00,0x07,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ' + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0xE0,0x00,0x70,0x00,0x70,0x0F,0xF0,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x0F,0xD8,0x00,0x00,0x00,0x00, // a + 0x00,0x00,0x00,0x00,0x1E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x0F,0xF0,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x1B,0xF0,0x00,0x00,0x00,0x00, // b + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0xE0,0x1C,0x70,0x1C,0x70,0x1C,0x00,0x1C,0x00,0x1C,0x70,0x1C,0x70,0x0F,0xE0,0x00,0x00,0x00,0x00, // c + 0x00,0x00,0x00,0x00,0x00,0xF8,0x00,0x70,0x00,0x70,0x00,0x70,0x0F,0xF0,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x0F,0xD8,0x00,0x00,0x00,0x00, // d + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0xE0,0x1C,0x70,0x1C,0x70,0x1F,0xF0,0x1C,0x00,0x1C,0x70,0x1C,0x70,0x0F,0xE0,0x00,0x00,0x00,0x00, // e + 0x00,0x00,0x00,0x00,0x03,0xE0,0x07,0x70,0x07,0x70,0x07,0x00,0x07,0x00,0x1F,0xE0,0x1F,0xE0,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x1F,0xC0,0x00,0x00,0x00,0x00, // f + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0xD8,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x0F,0xF0,0x07,0xF0,0x00,0x70,0x1C,0x70,0x0F,0xE0, // g + 0x00,0x00,0x00,0x00,0x1E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0xF0,0x0F,0x38,0x0F,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x1E,0x38,0x00,0x00,0x00,0x00, // h + 0x00,0x00,0x00,0x00,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x00,0x00,0x0F,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x0F,0xF8,0x00,0x00,0x00,0x00, // i + 0x00,0x00,0x00,0x00,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x00,0x03,0xF0,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x70,0x1C,0x70,0x0C,0xF0,0x07,0xE0, // j + 0x00,0x00,0x00,0x00,0x1E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x38,0x0E,0x70,0x0E,0xE0,0x0F,0xC0,0x0E,0xE0,0x0E,0x70,0x0E,0x38,0x1E,0x38,0x00,0x00,0x00,0x00, // k + 0x00,0x00,0x00,0x00,0x0F,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x0F,0xF8,0x00,0x00,0x00,0x00, // l + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xF8,0x1C,0x9C,0x1C,0x9C,0x1C,0x9C,0x1C,0x9C,0x1C,0x9C,0x1C,0x9C,0x1C,0x9C,0x00,0x00,0x00,0x00, // m + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xE0,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x00,0x00,0x00,0x00, // n + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0xE0,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x0F,0xE0,0x00,0x00,0x00,0x00, // o + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1B,0xF0,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0F,0xF0,0x0E,0x00,0x0E,0x00,0x1F,0x00, // p + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xB0,0x38,0xE0,0x38,0xE0,0x38,0xE0,0x38,0xE0,0x38,0xE0,0x1F,0xE0,0x00,0xE0,0x00,0xE0,0x01,0xF0, // q + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1E,0xF0,0x0F,0xF8,0x0F,0x38,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x0E,0x00,0x1F,0x00,0x00,0x00,0x00,0x00, // r + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0xE0,0x1C,0x30,0x1C,0x30,0x0F,0x80,0x03,0xE0,0x18,0x70,0x18,0x70,0x0F,0xE0,0x00,0x00,0x00,0x00, // s + 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x00,0x07,0x00,0x1F,0xF0,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x70,0x07,0x70,0x03,0xE0,0x00,0x00,0x00,0x00, // t + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x0F,0xD8,0x00,0x00,0x00,0x00, // u + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x1C,0x70,0x0E,0xE0,0x07,0xC0,0x03,0x80,0x00,0x00,0x00,0x00, // v + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x9C,0x1C,0x9C,0x0F,0xF8,0x07,0x70,0x07,0x70,0x00,0x00,0x00,0x00, // w + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1C,0xE0,0x1C,0xE0,0x0F,0xC0,0x07,0x80,0x07,0x80,0x0F,0xC0,0x1C,0xE0,0x1C,0xE0,0x00,0x00,0x00,0x00, // x + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x0E,0x38,0x07,0xF0,0x03,0xE0,0x00,0xE0,0x01,0xC0,0x1F,0x80, // y + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xE0,0x18,0xE0,0x11,0xC0,0x03,0x80,0x07,0x00,0x0E,0x20,0x1C,0x60,0x1F,0xE0,0x00,0x00,0x00,0x00, // z + 0x00,0x00,0x00,0x00,0x01,0xF8,0x03,0x80,0x03,0x80,0x03,0x80,0x07,0x00,0x1C,0x00,0x1C,0x00,0x07,0x00,0x03,0x80,0x03,0x80,0x03,0x80,0x01,0xF8,0x00,0x00,0x00,0x00, // { + 0x00,0x00,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x00,0x00, // | + 0x00,0x00,0x00,0x00,0x1F,0x80,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x00,0xE0,0x00,0x38,0x00,0x38,0x00,0xE0,0x01,0xC0,0x01,0xC0,0x01,0xC0,0x1F,0x80,0x00,0x00,0x00,0x00, // } + 0x00,0x00,0x00,0x00,0x1F,0x1C,0x3B,0x9C,0x39,0xDC,0x38,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 // ~ +}; diff --git a/Firmware_V3/src/hardware/hardware.cpp b/Firmware_V3/src/hardware/hardware.cpp new file mode 100644 index 0000000..060d5be --- /dev/null +++ b/Firmware_V3/src/hardware/hardware.cpp @@ -0,0 +1,581 @@ +/* + * + * HARDWARE - Main hardware functions + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +void t4_direct_write_low(volatile uint32_t *base, uint32_t mask) +{ + *(base + 34) = mask; +} + +void t4_direct_write_high(volatile uint32_t *base, uint32_t mask) +{ + *(base + 33) = mask; +} + +bool isUSBConnected() +{ + return (analogRead(pin_usb_measure) > 50); +} + +/* Converts a float to four bytes */ +void floatToBytes(uint8_t *farray, float val) +{ + union + { + float f; + unsigned long ul; + } u; + u.f = val; + farray[0] = u.ul & 0x00FF; + farray[1] = (u.ul & 0xFF00) >> 8; + farray[2] = (u.ul & 0xFF0000) >> 16; + farray[3] = (u.ul & 0xFF000000) >> 24; +} + +/* Converts four bytes back to float */ +float bytesToFloat(uint8_t *farray) +{ + union + { + float f; + unsigned long ul; + } u; + u.ul = (farray[3] << 24) | (farray[2] << 16) | (farray[1] << 8) | (farray[0]); + return u.f; +} + +/* Clears the whole EEPROM */ +void clearEEPROM() +{ + for (unsigned int i = 100; i < 250; i++) + EEPROM.write(i, 0); +} + +/* Checks if a FW upgrade has been done */ +void checkFWUpgrade() +{ + //If the first start setup has not been completed, skip + if (checkFirstStart()) + return; + + //Read current FW version from EEPROM + uint16_t eepromVersion = ((EEPROM.read(eeprom_fwVersionHigh) << 8) + EEPROM.read(eeprom_fwVersionLow)); + + //Show message after firmware upgrade + if (eepromVersion != fwVersion) + { + //Set EEPROM firmware version to current one + EEPROM.write(eeprom_fwVersionHigh, (fwVersion & 0xFF00) >> 8); + EEPROM.write(eeprom_fwVersionLow, fwVersion & 0x00FF); + + //Show downgrade completed message + showFullMessage((char *)"Firmware update completed!"); + delay(1000); + } +} + +/* Reads the old settings from EEPROM */ +void readEEPROM() +{ + byte read; + //Temperature format + read = EEPROM.read(eeprom_tempFormat); + if ((read == tempFormat_celcius) || (read == tempFormat_fahrenheit)) + tempFormat = read; + else + tempFormat = tempFormat_celcius; + + //Color scheme + read = EEPROM.read(eeprom_colorScheme); + if ((read >= 0) && (read <= (colorSchemeTotal - 1))) + colorScheme = read; + else + colorScheme = colorScheme_rainbow; + + //Convert Enabled + read = EEPROM.read(eeprom_convertEnabled); + if ((read == false) || (read == true)) + convertEnabled = read; + else + convertEnabled = false; + + //Battery Enabled + read = EEPROM.read(eeprom_batteryEnabled); + if ((read == false) || (read == true)) + batteryEnabled = read; + else + batteryEnabled = false; + + //Time Enabled + read = EEPROM.read(eeprom_timeEnabled); + if ((read == false) || (read == true)) + timeEnabled = read; + else + timeEnabled = false; + + //Date Enabled + read = EEPROM.read(eeprom_dateEnabled); + if ((read == false) || (read == true)) + dateEnabled = read; + else + dateEnabled = false; + + //Storage Enabled + read = EEPROM.read(eeprom_storageEnabled); + if ((read == false) || (read == true)) + storageEnabled = read; + else + storageEnabled = false; + + //Spot Enabled, only load when spot sensor is working + read = EEPROM.read(eeprom_spotEnabled); + if ((read == false) || (read == true)) + spotEnabled = read; + else + spotEnabled = false; + + //Filter Type + read = EEPROM.read(eeprom_filterType); + if ((read == filterType_none) || (read == filterType_box) || (read == filterType_gaussian)) + filterType = read; + else + filterType = filterType_gaussian; + + //Colorbar Enabled + read = EEPROM.read(eeprom_colorbarEnabled); + if ((read == false) || (read == true)) + colorbarEnabled = read; + else + colorbarEnabled = true; + + //Text color + read = EEPROM.read(eeprom_textColor); + if ((read >= textColor_white) && (read <= textColor_blue)) + textColor = read; + else + textColor = textColor_white; + + //Horizontal mirroring + read = EEPROM.read(eeprom_rotationHorizont); + if ((read == false) || (read == true)) + rotationHorizont = read; + else + rotationHorizont = false; + + //Hot / cold mode + read = EEPROM.read(eeprom_hotColdMode); + if ((read >= hotColdMode_disabled) && (read <= hotColdMode_hot)) + hotColdMode = read; + else + hotColdMode = hotColdMode_disabled; + + //Hot / cold level and color + if (hotColdMode != hotColdMode_disabled) + { + hotColdLevel = ((EEPROM.read(eeprom_hotColdLevelHigh) << 8) + EEPROM.read(eeprom_hotColdLevelLow)); + hotColdColor = EEPROM.read(eeprom_hotColdColor); + } + + //Min/Max Points + read = EEPROM.read(eeprom_minMaxPoints); + if ((read == minMaxPoints_disabled) || (read == minMaxPoints_min) || (read == minMaxPoints_max) || (read == minMaxPoints_both)) + minMaxPoints = read; + else + minMaxPoints = minMaxPoints_disabled; + + //Gain Mode + read = EEPROM.read(eeprom_lepton_gain); + if (read == lepton_gain_high) + { + lepton_setHighGain(); + } + else if (read == lepton_gain_low) + { + lepton_setLowGain(); + } + else + { + lepton_setHighGain(); + } +} + +/* Checks the specific device from the diagnostic variable */ +bool checkDiagnostic(byte device) +{ + //Returns false if the device does not work + return (diagnostic >> device) & 1; +} + +/* Sets the status of a specific device from the diagnostic variable */ +void setDiagnostic(byte device) +{ + diagnostic &= ~(1 << device); +} + +/* Checks for hardware issues */ +void checkHardware() +{ + //If the diagnostic is not okay show info + if (diagnostic != diag_ok) + showDiagnostic(); +} + +/* A method to check if the touch screen is pressed */ +boolean touchScreenPressed() +{ + //Check button status with debounce + touchDebouncer.update(); + return touchDebouncer.read(); +} + +/* A method to check if the external button is pressed */ +boolean extButtonPressed() +{ + //Check button status with debounce + buttonDebouncer.update(); + return buttonDebouncer.read(); +} + +/* Initialize the GPIO pins */ +void initGPIO() +{ + pinMode(pin_touch_irq, INPUT); + pinMode(pin_button, INPUT_PULLDOWN); + pinMode(pin_lcd_backlight, OUTPUT); + digitalWrite(pin_lcd_backlight, HIGH); +} + +/* Disables all Chip-Select lines on the SPI bus */ +void initSPI() +{ + pinMode(pin_lcd_dc, OUTPUT); + pinMode(pin_touch_cs, OUTPUT); + pinMode(pin_lepton_cs, OUTPUT); + pinMode(pin_lcd_cs, OUTPUT); + digitalWrite(pin_lcd_dc, HIGH); + digitalWrite(pin_touch_cs, HIGH); + digitalWrite(pin_lepton_cs, HIGH); + digitalWrite(pin_lcd_cs, HIGH); + + SPI.setMOSI(pin_mosi); + SPI.setMISO(pin_miso); + SPI.setSCK(pin_sck); + SPI.setCS(pin_lcd_dc); + SPI.usingInterrupt(pin_lepton_vsync); + SPI.begin(); + + SPI1.setMOSI(pin_mosi1); + SPI1.setMISO(pin_miso1); + SPI1.setSCK(pin_sck1); + SPI1.setCS(pin_lepton_cs); + SPI1.begin(); +} + +/* Inits the I2C Bus */ +void initI2C() +{ + Wire.begin(); +} + +/* Init the Analog-Digital-Converter for the battery measure */ +void initADC() +{ + //Init ADC + batMeasure = new ADC(); + //set number of averages + batMeasure->adc0->setAveraging(4); + //set bits of resolution + batMeasure->adc0->setResolution(12); + //change the conversion speed + batMeasure->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::MED_SPEED); + //change the sampling speed + batMeasure->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::MED_SPEED); + //set battery pin as input + pinMode(pin_bat_measure, INPUT); +} + +/* Init the buffer(s) */ +void initBuffer() +{ + //Init 320x240 buffer + bigBuffer = (uint16_t *)malloc(153600); + + //Init 160x120 buffer + smallBuffer = (uint16_t *)malloc(38400); +} + +/* Display the content of the small/big buffer on the screen */ +void displayBuffer() +{ + display_writeScreen(bigBuffer, 0); +} + +/* Sets the display rotation depending on the setting */ +void setDisplayRotation() +{ + if (rotationVert) + { + display_setRotation(135); + touch_setRotation(true); + } + else + { + display_setRotation(45); + touch_setRotation(false); + } +} + +/* Reads the temperature limits from EEPROM */ +void readTempLimits() +{ + //Some variables to get started + byte minValueHigh, minValueLow, maxValueHigh, maxValueLow, minMaxComp; + bool found = false; + + //Min / max selection + byte minMaxPreset; + byte read = EEPROM.read(eeprom_minMaxPreset); + if ((read >= minMax_preset1) && (read <= minMax_preset3)) + minMaxPreset = read; + else + minMaxPreset = minMax_temporary; + + //Min / max preset 1 + if ((minMaxPreset == minMax_preset1) && (EEPROM.read(eeprom_minMax1Set) == eeprom_setValue)) + { + minValueHigh = eeprom_minValue1High; + minValueLow = eeprom_minValue1Low; + maxValueHigh = eeprom_maxValue1High; + maxValueLow = eeprom_maxValue1Low; + minMaxComp = eeprom_minMax1Comp; + found = true; + } + //Min / max preset 2 + else if ((minMaxPreset == minMax_preset2) && (EEPROM.read(eeprom_minMax2Set) == eeprom_setValue)) + { + minValueHigh = eeprom_minValue2High; + minValueLow = eeprom_minValue2Low; + maxValueHigh = eeprom_maxValue2High; + maxValueLow = eeprom_maxValue2Low; + minMaxComp = eeprom_minMax2Comp; + found = true; + } + //Min / max preset 3 + else if ((minMaxPreset == minMax_preset3) && (EEPROM.read(eeprom_minMax3Set) == eeprom_setValue)) + { + minValueHigh = eeprom_minValue3High; + minValueLow = eeprom_minValue3Low; + maxValueHigh = eeprom_maxValue3High; + maxValueLow = eeprom_maxValue3Low; + minMaxComp = eeprom_minMax3Comp; + found = true; + } + + //Apply settings + if (found) + { + minValue = + ((EEPROM.read(minValueHigh) << 8) + EEPROM.read(minValueLow)); + maxValue = + ((EEPROM.read(maxValueHigh) << 8) + EEPROM.read(maxValueLow)); + for (int i = 0; i < 4; i++) + EEPROM.read(minMaxComp + i); + autoMode = false; + } +} + +/* Init the screen off timer */ +void initScreenOffTimer() +{ + byte read = EEPROM.read(eeprom_screenOffTime); + //Try to read from EEPROM + if ((read == screenOffTime_disabled) || (read == screenOffTime_5min) || read == screenOffTime_20min) + { + screenOffTime = read; + //10 Minutes + if (screenOffTime == screenOffTime_5min) + screenOff.begin(300000, false); + //30 Minutes + else if (screenOffTime == screenOffTime_20min) + screenOff.begin(1200000, false); + //Disable marker + screenPressed = false; + } + else + screenOffTime = screenOffTime_disabled; +} + +/* Get time from the RTC */ +time_t getTeensy3Time() +{ + return Teensy3Clock.get(); +} + +/* Init the time and correct it if required */ +void initRTC() +{ + //Get the time from the Teensy + setSyncProvider(getTeensy3Time); + + //Check if year is lower than 2021 + if ((year() < 2021) && (EEPROM.read(eeprom_firstStart) == eeprom_setValue)) + { + showFullMessage((char *)"Empty coin cell battery"); + delay(1000); + setTime(0, 0, 0, 1, 1, 2021); + Teensy3Clock.set(now()); + } +} + +/* Disable the screen backlight */ +void disableScreenLight() +{ + digitalWrite(pin_lcd_backlight, LOW); +} + +/* Enables the screen backlight */ +void enableScreenLight() +{ + digitalWrite(pin_lcd_backlight, HIGH); +} + +/* Checks if the screen backlight is on or off*/ +bool checkScreenLight() +{ + return digitalRead(pin_lcd_backlight); +} + +//Get the spot temperature from Lepton or MLX90614 +void getSpotTemp() +{ + //Get spot value from radiometric Lepton + if ((leptonVersion == leptonVersion_2_5_shutter) || (leptonVersion == leptonVersion_3_5_shutter)) + spotTemp = lepton_spotTemp(); + + //Convert to Fahrenheit if required + if (tempFormat == tempFormat_fahrenheit) + spotTemp = celciusToFahrenheit(spotTemp); +} + +/* Toggle the display*/ +void toggleDisplay() +{ + showFullMessage((char *)"Screen goes off, touch to continue", true); + delay(1000); + disableScreenLight(); + //Wait for touch press + while (!touch_touched()) + ; + //Turning screen on + drawMainMenuBorder(); + showFullMessage((char *)"Turning screen on..", true); + enableScreenLight(); + delay(1000); +} + +/* Check if the screen was pressed in the time period */ +bool screenOffCheck() +{ + //Timer exceeded + if ((screenOff.check()) && (screenOffTime != screenOffTime_disabled)) + { + //No touch press in the last interval + if (screenPressed == false) + { + toggleDisplay(); + screenOff.reset(); + return true; + } + //Touch pressed, restart timer + screenPressed = false; + screenOff.reset(); + return false; + } + return false; +} + +/* Init the hardware */ +void initHardware() +{ + //Init UART + Serial.begin(115200); + + //Init GPIO + initGPIO(); + + //Init SPI + initSPI(); + + //Init I2C + initI2C(); + + //Init ADC + initADC(); + + //Init display + display_init(); + + //Show bootscreen + bootScreen(); + + //Init touch + touch_init(); + + //Eventually enter MTP mode + enterMassStorage(); + + //Init lepton + lepton_init(); + + //Init SD card + initSD(); + + //Init screen off timer + initScreenOffTimer(); + + //Init the buffer(s) + initBuffer(); + + //Check battery for the first time + checkBattery(true); + + //Init the realtime clock + initRTC(); + + //Wait some time for the Lepton to do the FFC + delay(2000); +} diff --git a/Firmware_V3/src/hardware/lepton.cpp b/Firmware_V3/src/hardware/lepton.cpp new file mode 100644 index 0000000..5db46c2 --- /dev/null +++ b/Firmware_V3/src/hardware/lepton.cpp @@ -0,0 +1,863 @@ +/* + * + * LEPTON - Access the FLIR Lepton LWIR module + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################### STATIC DATA DECLARATIONS ##########################*/ + +//Array to store one Lepton frame +static byte lepton_packet[164]; + +volatile int lepton_curSeg = 1; +volatile bool lepton_validSegRegion = false; +volatile bool irqAttached = false; + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +uint32_t AbsDiff32u(uint32_t n1, uint32_t n2) +{ + if (n2 >= n1) + { + return (n2 - n1); + } + else + { + return (n2 - n1 + 0xFFFFFFFF); + } +} + +void lepton_startFrame() +{ + leptonBufferValid = false; + if (!irqAttached) + { + attachInterrupt(pin_lepton_vsync, lepton_getFrameAsync, RISING); + irqAttached = true; + } +} + +void lepton_endFrame() +{ + if (irqAttached) + { + detachInterrupt(pin_lepton_vsync); + irqAttached = false; + } +} + +/* Start Lepton SPI Transmission */ +void lepton_begin() +{ + SPI1.beginTransaction(SPISettings(25000000, MSBFIRST, SPI_MODE1)); + digitalWriteFast(pin_lepton_cs, LOW); +} + +/* Reset the SPI bus to re-initiate Lepton communication */ +void lepton_reset() +{ + lepton_end(); + delay(186); + lepton_begin(); +} + +/* End Lepton SPI Transmission */ +void lepton_end() +{ + digitalWriteFast(pin_lepton_cs, HIGH); + SPI1.endTransaction(); +} + +/* Store one package of 80 columns into RAM */ +void lepton_savePacket(uint8_t line, uint8_t segment) +{ + //Go through the video pixels for one video line + for (int column = 0; column < 80; column++) + { + //Apply horizontal mirroring + if (rotationHorizont) + column = 79 - column; + + //Make a 16-bit rawvalue from the lepton frame + uint16_t result = (uint16_t)(lepton_packet[2 * column + 4] << 8 | lepton_packet[2 * column + 5]); + + //Discard horizontal mirroring + if (rotationHorizont) + column = 79 - column; + + //Lepton2.5 + if (leptonVersion == leptonVersion_2_5_shutter) + { + //Non-rotated + if (!rotationVert) + { + smallBuffer[(line * 2 * 160) + (column * 2)] = result; + smallBuffer[(line * 2 * 160) + (column * 2) + 1] = result; + smallBuffer[(line * 2 * 160) + 160 + (column * 2)] = result; + smallBuffer[(line * 2 * 160) + 160 + (column * 2) + 1] = result; + } + //Rotated + else + { + smallBuffer[19199 - ((line * 2 * 160) + (column * 2))] = result; + smallBuffer[19199 - ((line * 2 * 160) + (column * 2) + 1)] = + result; + smallBuffer[19199 - ((line * 2 * 160) + 160 + (column * 2))] = + result; + smallBuffer[19199 - ((line * 2 * 160) + 160 + (column * 2) + 1)] = + result; + } + } + + //Lepton3.x + else + { + //Non-Rotated + if (!rotationVert) + { + switch (segment) + { + case 1: + smallBuffer[((line / 2) * 160) + ((line % 2) * 80) + (column)] = result; + break; + case 2: + smallBuffer[4800 + (((line / 2) * 160) + ((line % 2) * 80) + (column))] = + result; + break; + case 3: + smallBuffer[9600 + (((line / 2) * 160) + ((line % 2) * 80) + (column))] = + result; + break; + case 4: + smallBuffer[14400 + (((line / 2) * 160) + ((line % 2) * 80) + (column))] = + result; + break; + } + } + + //Rotated + else + { + switch (segment) + { + case 1: + if (rotationHorizont) + smallBuffer[19199 - (((line / 2) * 160) + ((1 - (line % 2)) * 80) + (column))] = result; + else + smallBuffer[19199 - (((line / 2) * 160) + ((line % 2) * 80) + (column))] = result; + break; + case 2: + if (rotationHorizont) + smallBuffer[14399 - (((line / 2) * 160) + ((1 - (line % 2)) * 80) + (column))] = result; + else + smallBuffer[14399 - (((line / 2) * 160) + ((line % 2) * 80) + (column))] = result; + break; + case 3: + if (rotationHorizont) + smallBuffer[9599 - (((line / 2) * 160) + ((1 - (line % 2)) * 80) + (column))] = result; + else + smallBuffer[9599 - (((line / 2) * 160) + ((line % 2) * 80) + (column))] = result; + break; + case 4: + if (rotationHorizont) + smallBuffer[4799 - (((line / 2) * 160) + ((1 - (line % 2)) * 80) + (column))] = result; + else + smallBuffer[4799 - (((line / 2) * 160) + ((line % 2) * 80) + (column))] = result; + break; + } + } + } + } +} + +/* Fetch frame asynchronously over IRQ */ +void lepton_getFrame() +{ + long timer = millis(); + while (!leptonBufferValid) + { + if (millis() - timer > 3000) + { + showFullMessage((char *)"Lepton error, try to reset.."); + lepton_reset(); + lepton_startFrame(); + timer = millis(); + } + } +} + +/* Get one line package from the Lepton */ +LeptonReadError lepton_getPacketSync(uint8_t line, uint8_t seg) +{ + //Receive one frame over SPI + SPI1.transfer(lepton_packet, 164); + + //Repeat as long as the frame is not valid, equals sync + if ((lepton_packet[0] & 0x0F) == 0x0F) + return DISCARD; + + //Check if the line number matches the expected line + if (lepton_packet[1] != line) + return ROW_ERROR; + + //For the Lepton3.5, check if the segment number matches + if ((line == 20) && (leptonVersion == leptonVersion_3_5_shutter)) + { + byte segment = (lepton_packet[0] >> 4); + if (segment == 0) + return SEGMENT_INVALID; + if (segment != seg) + return SEGMENT_ERROR; + } + + //Everything worked + return NONE; +} + +/* Get one line package from the Lepton */ +bool lepton_getPacketAsync(uint8_t *line, uint8_t *seg) +{ + bool valid = false; + *seg = 0; + + //Start transfer and get one package + lepton_begin(); + SPI1.transfer(lepton_packet, 164); + + //Repeat as long as the frame is not valid, equals sync + if ((lepton_packet[0] & 0x0F) == 0x0F) + { + valid = false; + } + else + { + *line = lepton_packet[1]; + + //Get segment when possible + if (*line == 20) + { + *seg = (lepton_packet[0] >> 4); + } + + valid = true; + } + + lepton_end(); + return valid; +} + +/* Get one frame of raw values from the Lepton asynchronously */ +void lepton_getFrameAsync() +{ + + uint32_t startUsec; + uint8_t line, prevLine; + uint8_t segment; + bool done = false; + bool beforeValidData = true; + + startUsec = micros(); + prevLine = 255; + + while (!done) + { + //Try to get a new packet over SPI + if (lepton_getPacketAsync(&line, &segment)) + { + //Saw a valid packet + if (line == prevLine) + { + //This is garbage data since line numbers should always increment + done = true; + } + else + { + //For the Lepton3.5, check if the segment number matches + if ((line == 20) && (leptonVersion == leptonVersion_3_5_shutter)) + { + //Check segment + if (!lepton_validSegRegion) + { + //Look for start of valid segment data + if (segment == 1) + { + beforeValidData = false; + lepton_validSegRegion = true; + } + } + //Hold / Reset in starting position (always collecting in segment 1 buffer locations) + else if ((segment < 2) || (segment > 4)) + { + + lepton_validSegRegion = false; + lepton_curSeg = 1; + } + } + + //Save one packet consisting of 164 bytes + if (((leptonVersion == leptonVersion_2_5_shutter) || ((beforeValidData || lepton_validSegRegion))) && (line <= 59)) + lepton_savePacket(line, lepton_curSeg); + + //Last line + if (line == 59) + { + //For 160x120, check segment + if (leptonVersion == leptonVersion_3_5_shutter) + { + if (lepton_validSegRegion) + { + if (lepton_curSeg < 4) + { + //Setup to get next segment + lepton_curSeg++; + } + else + { + if (!leptonBufferValid) + { + leptonBufferValid = true; + lepton_endFrame(); + } + + //Setup to get the next frame + lepton_curSeg = 1; + lepton_validSegRegion = false; + } + } + } + + //For 80x60, do not check segment + else + { + if (!leptonBufferValid) + { + leptonBufferValid = true; + lepton_endFrame(); + } + } + + done = true; + } + } + + prevLine = line; + } + //Did not see a valid packet within this segment interval + else if (AbsDiff32u(startUsec, micros()) > 9450) + { + done = true; + } + } +} + +/* Select I2C Register on the Lepton */ +void lepton_setReg(byte reg) +{ + Wire.beginTransmission(0x2A); + Wire.write(reg >> 8 & 0xff); + Wire.write(reg & 0xff); + Wire.endTransmission(); +} + +/* Read I2C Register on the lepton */ +int lepton_readReg(byte reg) +{ + uint16_t reading; + lepton_setReg(reg); + Wire.requestFrom(0x2A, 2); + reading = Wire.read(); + reading = reading << 8; + reading |= Wire.read(); + return reading; +} + +/* + First reads the DATA Length Register (0x006) + Then reads the acutal DATA Registers: + DATA 0 Register, DATA 1 Register etc. + + If the request length is smaller that the DATA Length it returns -1 + */ +int lepton_i2cReadDataRegister(byte *data, int data_length_request) +{ + + int data_length_recv; + int data_read; + // Wait for execution of the command + while (lepton_readReg(0x2) & 0x01) + ; + + // Read the data length (should be 4) + data_length_recv = lepton_readReg(0x6); + + if (data_length_recv < data_length_request) + { + return -1; + } + + Wire.requestFrom(0x2A, data_length_request); + data_read = Wire.readBytes(data, data_length_request); + Wire.endTransmission(); + return data_read; +} + +/* + First writes the actual DATA Registers (0x0008). + Then writes the DATA Length Register (0x006) + */ +byte lepton_i2cWriteDataRegister(byte *data, int length) +{ + + // Wait for execution of the command + while (lepton_readReg(0x2) & 0x01) + ; + + Wire.beginTransmission(0x2A); + // CCI/TWI Data Registers is at 0x0008 + Wire.write(0x00); + Wire.write(0x08); + for (int i = 0; i < length; i++) + { + Wire.write(data[i]); + } + Wire.endTransmission(); + + // CCI/TWI Data Length Register is at 0x0006 + Wire.beginTransmission(0x2A); + Wire.write(0x00); + Wire.write(0x06); + //Data length bytes + Wire.write((length >> 8) & 0xFF); + Wire.write(length & 0xFF); + return Wire.endTransmission(); +} + +/* + Write the command words (16-bit) via I2C + */ +byte lepton_i2c_execute_command(byte cmdbyte0, byte cmdbyte1) +{ + // Wait for execution of the command + while (lepton_readReg(0x2) & 0x01) + ; + + Wire.beginTransmission(0x2A); + Wire.write(0x00); + Wire.write(0x04); //COMMANDID_REG + Wire.write(cmdbyte0); + Wire.write(cmdbyte1); + return Wire.endTransmission(); +} + +/* Trigger a flat-field-correction on the Lepton */ +bool lepton_ffc(bool message, bool switch_gain) +{ + //Show a message for main menu + if (message) + { + //When in manual temperature mode, a FFC is not possible + if ((!autoMode) && (!switch_gain)) + { + showFullMessage((char *)"No FFC in manual mode", true); + delay(1000); + return false; + } + showFullMessage((char *)"Performing FFC..", true); + } + + byte error; + + if ((leptonVersion == leptonVersion_2_5_shutter) || (leptonVersion == leptonVersion_3_5_shutter)) + { + // For radiometric Lepton, send RAD FFC command + // RAD FFC Normalization Command + // 0x0E00 (SDK Module ID) + 0x2C (SDK Command ID) + 0x2 (RUN operation) + 0x4000 (Protection Bit) = 0x4E2E + error = lepton_i2c_execute_command(0x4E, 0x2E); + } + else + { + //For all others, send normal FFC command + // SYS Run FFC Normalization + // 0x0200 (SDK Module ID) + 0x40 (SDK Command ID) + 0x2 (RUN operation) + 0x0000 (Protection Bit) = 0x0242 + error = lepton_i2c_execute_command(0x02, 0x42); + } + + //Wait some time when in main menu + if (message) + delay(2000); + + return error; +} + +/* Get the spotmeter value on a radiometric lepton */ +float lepton_spotTemp() +{ + //Get RAD spotmeter value + Wire.beginTransmission(0x2A); + Wire.write(0x00); + Wire.write(0x04); + Wire.write(0x4E); + Wire.write(0xD0); + byte error = Wire.endTransmission(); + + //Lepton I2C error, set diagnostic + if (error != 0) + { + setDiagnostic(diag_lep_conf); + return 0; + } + + //Transfer the new package + Wire.beginTransmission(0x2A); + while (lepton_readReg(0x2) & 0x01) + ; + Wire.requestFrom(0x2A, lepton_readReg(0x6)); + byte response[8]; + Wire.readBytes(response, 8); + Wire.endTransmission(); + + //Calculate spot temperature in Kelvin + float spotTemp = (response[0] * 256.0) + response[1]; + //Multiply by correction factor + if (leptonGainMode == lepton_gain_high) + { + spotTemp *= 0.01; + } + else + { + spotTemp *= 0.1; + } + //Convert to celsius + spotTemp -= 273.15; + + return spotTemp; +} + +/* Set the shutter operation to manual/auto */ +void lepton_ffcMode(bool automatic) +{ + //When enabling auto FFC, check for some factors + if ((automatic) && ((hotColdMode != hotColdMode_disabled) || (autoMode == false) || (limitsLocked == true))) + return; + + //Contains the standard values for the FFC mode + byte package[] = + {automatic, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, + 147, 4, 0, 0, 0, 0, 0, 44, 1, 52, 0}; + + lepton_i2cWriteDataRegister(package, sizeof(package)); + + // SYS FFC Mode Control Command + // 0x0200 (SDK Module ID) + 0x3C (SDK Command ID) + 0x1 (SET operation) + 0x0000 (Protection Bit) = 0x023D + lepton_i2c_execute_command(0x02, 0x3D); +} + +/* + * Sets the GPIO pins mode + * gpio_mode: 0 = GPIO Mode (default) + * gpio_mode: 5 = VSync Enabled (DIY-Thermocam V3 only) + * + * The new Lepton Breakout V2 exposes the VSync pin on its 20 pin header + * The VSync signal is used to achieve maximum performance and avoid synchronization issues + */ +void lepton_setGpioMode(bool vsync_enabled) +{ + byte gpio_mode = 0x00; + if (vsync_enabled) + gpio_mode = 0x05; + + //The enum value is the LSB of DATA0 + byte data[4] = + {0x00, gpio_mode, 0x00, 0x00}; + lepton_i2cWriteDataRegister(data, 4); + + // OEM GPIO Mode Select Set Command + // 0x0800 (SDK Module ID) + 0x54 (SDK Command ID) + 0x1 (SET operation) + 0x4000 (Protection Bit) = 0x4855. + lepton_i2c_execute_command(0x48, 0x55); +} + +/* Checks the Lepton hardware revision */ +bool lepton_version() +{ + + // OEM FLIR Systems Part Number + // 0x0800 (SDK Module ID) + 0x1C (SDK Command ID) + 0x0 (GET operation) + 0x4000 (Protection Bit) = 0x481C + byte error = lepton_i2c_execute_command(0x48, 0x1C); + + //Lepton I2C error, set diagnostic + if (error != 0) + { + setDiagnostic(diag_lep_conf); + return false; + } + + char leptonhw[33]; + lepton_i2cReadDataRegister((byte *)leptonhw, 32); + + //Detected Lepton2.5 Shuttered (Radiometric) + if (strstr(leptonhw, "05-070360") != NULL) + { + leptonVersion = leptonVersion_2_5_shutter; + } + + //Detected Lepton3.5 Shuttered (Radiometric) + else if (strstr(leptonhw, "05-070170") != NULL) + { + leptonVersion = leptonVersion_3_5_shutter; + } + + //Unsupported Lepton + else + { + return false; + } + + //Write to EEPROM + EEPROM.write(eeprom_leptonVersion, leptonVersion); + return true; +} + +/* + * Set the SYS Gain Mode + * 0: high mode (hardware default), + * 1: low mode, + * 2: auto mode + * The measurement range for the Lepton 3.5 is (see datasheet for details): + * High Gain Mode: -10 to +140 deg C + * Low Gain Mode: -10 to +450 deg C + * + */ +void lepton_setSysGainMode(byte mode) +{ + if (mode > 2) + { + return; + } + + //The enum value is the LSB of DATA0 + byte data[4] = + {0x00, mode, 0x00, 0x00}; + lepton_i2cWriteDataRegister(data, 4); + + // Execute the SYS Gain Mode Set Command, so that the module applies the values + // 0x0200 (SDK Module ID) + 0x48 (SDK Command ID) + 0x1 (SET operation) + 0x0000 (Protection Bit) = 0x0249. + lepton_i2c_execute_command(0x02, 0x49); +} + +/* + * Sets the SYS Gain Mode to high gain mode + */ +void lepton_setSysGainHigh() +{ + lepton_setSysGainMode(0x00); +} + +/* + * Sets the SYS Gain Mode to low gain mode + */ +void lepton_setSysGainLow() +{ + lepton_setSysGainMode(0x01); +} + +/* + * Sets the SYS Gain Mode to auto mode + */ +void lepton_setSysGainAuto() +{ + lepton_setSysGainMode(0x02); +} + +/* + * Returns the SYS Gain Mode + * 0: high mode (hardware default), + * 1: low mode, + * 2: auto mode + * The measurement range for the Lepton 3.5 is (see datasheet for details): + * High Gain Mode: -10 to +140 deg C + * Low Gain Mode: -10 to +450 deg C + * + * Returns -1 if the gain mode could not be read + */ +int lepton_getSysGainMode() +{ + + byte data[4]; + + //SYS Gain Mode Get Command + // 0x0200 (SDK Module ID) + 0x48 (SDK Command ID) + 0x0 (GET operation) + 0x0000 (Protection Bit) = 0x0248. + lepton_i2c_execute_command(0x02, 0x48); + lepton_i2cReadDataRegister(data, 4); + + //The enum value is the LSB of DATA0 + return data[1]; +} + +/* + * Returns the RAD T-Linear Resolution + * 0: 100 + * 1: 10 + */ +byte lepton_getRadTlinearResolution() +{ + + byte data[4]; + + // RAD T-Linear Resolution Get Command + // 0x0E00 (SDK Module ID) + 0xC4 (SDK Command ID) + 0x0 (GET operation) + 0x4000 (Protection Bit) = 0x4EC4. + lepton_i2c_execute_command(0x4E, 0xC4); + lepton_i2cReadDataRegister(data, 4); + + //The enum value is the LSB of DATA0 + return data[1]; +} + +/* + * Returns the RAD T-Linear Resolution Factor as a float + * Returns -1 if the value could not be read + */ +float lepton_getResolution() +{ + byte resolution = lepton_getRadTlinearResolution(); + if (resolution == 0) + { + return 0.1; + } + else + { + return 0.01; + } +} + +/* + * Sets the RAD T-Linear Resolution + * resolution: 0 = factor 100 + * resolution: 1 = factor 10 + * + * You need to set this to factor 10 for temperature over 382.2 deg C + * The maximum temperature value of the 16-bit is 65535. + * For factor 100: The maximum is 655.35 Kelvin which equals to 655.35 - 273.15 = 382.2 deg C + * For factor 10: The maximum is 6553.5 Kelvin which equals to 6553.5 - 273.15 = 6280.35 deg C + */ +void lepton_setRadTlinearResolution(byte resolution) +{ + if (resolution > 1) + { + return; + } + + //The enum value is the LSB of DATA0 + byte data[4] = + {0x00, resolution, 0x00, 0x00}; + lepton_i2cWriteDataRegister(data, 4); + + // RAD T-Linear Resolution Set Command + // 0x0E00 (SDK Module ID) + 0xC4 (SDK Command ID) + 0x1 (SET operation) + 0x4000 (Protection Bit) = 0x4EC5. + lepton_i2c_execute_command(0x4E, 0xC5); +} + +/* + * Sets the RAD T-Linear Resolution to 10 (factor 0.1) + */ +void lepton_setRadTlinear10() +{ + lepton_setRadTlinearResolution(0); +} + +/* + * Sets the RAD T-Linear Resolution to 100 (factor 0.01) + */ +void lepton_setRadTlinear100() +{ + lepton_setRadTlinearResolution(1); +} + +/* Switch to low gain (-10 to +450 deg C) */ +void lepton_setLowGain() +{ + lepton_setSysGainLow(); + lepton_setRadTlinear10(); + leptonCalSlope = 0.1; + leptonGainMode = lepton_gain_low; +} + +/* Switch to high gain (-10 to +140 deg C) */ +void lepton_setHighGain() +{ + lepton_setSysGainHigh(); + lepton_setRadTlinear100(); + leptonCalSlope = 0.01; + leptonGainMode = lepton_gain_high; +} + +/* Check if Lepton is connected via SPI */ +bool lepton_spiCheck() +{ + lepton_begin(); + long timer = millis(); + do + { + SPI1.transfer(lepton_packet, 164); + } + //Repeat as long as the frame is not valid, equals sync + while (((lepton_packet[0] & 0x0F) == 0x0F) && ((millis() - timer) < 1000)); + lepton_end(); + + //If sync not received after a second, return false + if ((lepton_packet[0] & 0x0F) == 0x0F) + return false; + + //Lepton is connected via SPI + return true; +} + +/* Init the FLIR Lepton LWIR sensor */ +void lepton_init() +{ + //Check if SPI connection to Lepton works + if (!lepton_spiCheck()) + { + setDiagnostic(diag_lep_data); + setDiagnostic(diag_lep_conf); + return; + } + + //Check the Lepton version + if (!lepton_version()) + { + setDiagnostic(diag_lep_conf); + return; + } + + //Enable VSync IRQ + lepton_setGpioMode(true); + + //Do FFC + lepton_ffc(); + + autoMode = true; + limitsLocked = false; + leptonBufferValid = false; +} diff --git a/Firmware_V3/src/hardware/massstorage.cpp b/Firmware_V3/src/hardware/massstorage.cpp new file mode 100644 index 0000000..63f12a1 --- /dev/null +++ b/Firmware_V3/src/hardware/massstorage.cpp @@ -0,0 +1,127 @@ +/* + * + * MASS STORAGE - Mass storage mode to connect the internal storage to the PC + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/*################# DATA TYPES, CONSTANTS & MACRO DEFINITIONS #################*/ + +#define CPU_RESTART_ADDR (uint32_t *)0xE000ED0C +#define CPU_RESTART_VAL 0x5FA0004 +#define CPU_RESTART (*CPU_RESTART_ADDR = CPU_RESTART_VAL); + +const char *sd_str[] = {"sdio", "sd1"}; +const int cs[] = {BUILTIN_SDCARD, 10}; +const int nsd = sizeof(sd_str) / sizeof(const char *); +SDClass sdx[nsd]; +MTPStorage_SD storage; +MTPD mtpd(&storage); + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +extern "C" int usb_init_events(void); + +void dateTime(uint16_t *date, uint16_t *time, uint8_t *ms10) +{ + *date = FS_DATE(year(), month(), day()); + *time = FS_TIME(hour(), minute(), second()); + *ms10 = second() & 1 ? 100 : 0; +} + +void storage_configure() +{ + for (int ii = 0; ii < nsd; ii++) + { + if (cs[ii] == BUILTIN_SDCARD) + { + if (sdx[ii].sdfs.begin(SdioConfig(FIFO_SDIO))) + { + storage.addFilesystem(sdx[ii], sd_str[ii]); + } + } + } +} + +void enterMassStorage() +{ + //Only do this if the EEPROM marker is set + if ((EEPROM.read(eeprom_massStorage) == eeprom_setValue)) + { + EEPROM.write(eeprom_massStorage, 0); + showFullMessage((char *)"USB File Transfer, touch to exit!"); + usb_init_events(); + storage_configure(); + FsDateTime::callback = dateTime; + + //Do MTP until user wants to exit via touch + while (true) + { + mtpd.loop(); + if (touch_touched() || !isUSBConnected()) + break; + } + + showFullMessage((char *)"Exiting File Transfer mode.."); + delay(1000); + while (touch_touched()) + ; + sd.begin(SdioConfig(FIFO_SDIO)); + } +} + +void setMassStorage() +{ + //Set marker and reboot + showFullMessage((char *)"Entering MTP, device reboots.."); + EEPROM.write(eeprom_massStorage, eeprom_setValue); + delay(1000); + CPU_RESTART; +} + +void checkMassStorage() +{ + if (!usbConnected && isUSBConnected()) + { + detachInterrupt(pin_touch_irq); + + //Check if the user really wants to enter MTP + if (massStoragePrompt()) + { + setMassStorage(); + } + + attachInterrupt(pin_touch_irq, touchIRQ, FALLING); + usbConnected = true; + } + else if (usbConnected && !isUSBConnected()) + { + usbConnected = false; + } +} \ No newline at end of file diff --git a/Firmware_V3/src/hardware/sdcard.cpp b/Firmware_V3/src/hardware/sdcard.cpp new file mode 100644 index 0000000..def8981 --- /dev/null +++ b/Firmware_V3/src/hardware/sdcard.cpp @@ -0,0 +1,148 @@ +/* +* +* SD Card - Methods to access the internal SD storage +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include + +/*######################### STATIC DATA DECLARATIONS ##########################*/ + +uint32_t cardSectorCount = 0; +uint8_t sectorBuffer[512]; +SdCardFactory cardFactory; +SdCard *m_card = nullptr; +uint32_t const ERASE_SIZE = 262144L; + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Returns the free space on the card in KB */ +uint32_t getSDSpace() +{ + return sd.freeClusterCount() * sd.sectorsPerCluster() * 0.000512; +} + +/* Returns the full card size in MB */ +uint32_t getCardSize() +{ + return sd.clusterCount() * sd.sectorsPerCluster() * 0.000512; +} + +//Refresh free space of the internal space +void refreshFreeSpace() +{ + uint16_t cardSize = getCardSize(); + if (cardSize != 0) + sdInfo = String(getSDSpace()) + "/" + String(cardSize) + " MB"; +} + +/* Timestamp set for SDFat */ +void dateTime(uint16_t *date, uint16_t *time) +{ + // return date using FAT_DATE macro to format fields + *date = FAT_DATE(year(), month(), day()); + // return time using FAT_TIME macro to format fields + *time = FAT_TIME(hour(), minute(), second()); +} + +/* Begin the SD card */ +bool beginSD() +{ + return sd.begin(SdioConfig(FIFO_SDIO)); +} + +/* Initializes the SD card */ +void initSD() +{ + //Storage info string + sdInfo = " - / - MB"; + + //Check if the sd card works + if (!beginSD()) + setDiagnostic(diag_sd); + + refreshFreeSpace(); + SdFile::dateTimeCallback(dateTime); +} + +/* Erase Card before formatting */ +bool eraseCard() +{ + uint32_t firstBlock = 0; + uint32_t lastBlock; + uint16_t n = 0; + + do + { + lastBlock = firstBlock + ERASE_SIZE - 1; + if (lastBlock >= cardSectorCount) + { + lastBlock = cardSectorCount - 1; + } + if (!m_card->erase(firstBlock, lastBlock)) + { + return false; + } + if ((n++) % 64 == 63) + { + } + firstBlock += ERASE_SIZE; + } while (firstBlock < cardSectorCount); + + if (!m_card->readSector(0, sectorBuffer)) + { + return false; + } + + return true; +} + +/* Format FAT card */ +bool formatCard() +{ + m_card = cardFactory.newCard(SdioConfig(FIFO_SDIO)); + + if (!m_card || m_card->errorCode()) + { + return false; + } + + cardSectorCount = m_card->sectorCount(); + if (!cardSectorCount) + { + return false; + } + + //exFAT is not supported + if (cardSectorCount > 67108864) + return false; + + //First try to erase card + if (eraseCard()) + { + //Then format it + FatFormatter fatFormatter; + bool ret = fatFormatter.format(m_card, sectorBuffer, &Serial); + if (!ret) + return false; + return true; + } + return false; +} \ No newline at end of file diff --git a/Firmware_V3/src/hardware/touchscreen/ft6206_touchscreen.cpp b/Firmware_V3/src/hardware/touchscreen/ft6206_touchscreen.cpp new file mode 100644 index 0000000..4302562 --- /dev/null +++ b/Firmware_V3/src/hardware/touchscreen/ft6206_touchscreen.cpp @@ -0,0 +1,97 @@ +/* + * + * FT6206 Touch controller + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include + +/*###################### PRIVATE FUNCTION BODIES ##############################*/ + +boolean FT6206_Touchscreen::begin(uint8_t threshhold) { + writeRegister8(FT6206_REG_THRESHHOLD, threshhold); + if ((readRegister8(FT6206_REG_VENDID) != 17) || (readRegister8(FT6206_REG_CHIPID) != 54)) return false; + return true; +} + +boolean FT6206_Touchscreen::touched(void) { + uint8_t n = readRegister8(FT6206_REG_NUMTOUCHES); + if ((n == 1) || (n == 2)) return true; + return false; +} + +void FT6206_Touchscreen::readData(uint16_t *x, uint16_t *y) { + uint8_t i2cdat[16]; + Wire.beginTransmission(FT6206_ADDR); + Wire.write((byte)0); + Wire.endTransmission(); + Wire.beginTransmission(FT6206_ADDR); + Wire.requestFrom((byte)FT6206_ADDR, (byte)32); + for (uint8_t i = 0; i < 16; i++) + i2cdat[i] = Wire.read(); + Wire.endTransmission(); + touches = i2cdat[0x02]; + if (touches > 2) { + touches = 0; + *x = *y = 0; + } + if (touches == 0) { + *x = *y = 0; + return; + } + for (uint8_t i = 0; i < 2; i++) { + touchX[i] = i2cdat[0x03 + i * 6] & 0x0F; + touchX[i] <<= 8; + touchX[i] |= i2cdat[0x04 + i * 6]; + touchY[i] = i2cdat[0x05 + i * 6] & 0x0F; + touchY[i] <<= 8; + touchY[i] |= i2cdat[0x06 + i * 6]; + touchID[i] = i2cdat[0x05 + i * 6] >> 4; + } + *x = touchY[0]; + *y = touchX[0]; + *y = map(*y, 0, 240, 240, 0); +} + +TS_Point FT6206_Touchscreen::getPoint(void) { + uint16_t x, y; + readData(&x, &y); + if (rotated) { + y = map(y, 0, 240, 240, 0); + x = map(x, 0, 320, 320, 0); + } + return TS_Point(x, y, 1); +} + +uint8_t FT6206_Touchscreen::readRegister8(uint8_t reg) { + uint8_t x; + Wire.beginTransmission(FT6206_ADDR); + Wire.write((byte)reg); + Wire.endTransmission(); + Wire.beginTransmission(FT6206_ADDR); + Wire.requestFrom((byte)FT6206_ADDR, (byte)1); + x = Wire.read(); + Wire.endTransmission(); + return x; +} + +void FT6206_Touchscreen::writeRegister8(uint8_t reg, uint8_t val) { + Wire.beginTransmission(FT6206_ADDR); + Wire.write((byte)reg); + Wire.write((byte)val); + Wire.endTransmission(); +} diff --git a/Firmware_V3/src/hardware/touchscreen/touchscreen.cpp b/Firmware_V3/src/hardware/touchscreen/touchscreen.cpp new file mode 100644 index 0000000..bc8c3c7 --- /dev/null +++ b/Firmware_V3/src/hardware/touchscreen/touchscreen.cpp @@ -0,0 +1,123 @@ +/* +* +* Touchscreen - FT6206 or XPT2046 controller +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################### STATIC DATA DECLARATIONS ##########################*/ + +//Resistive Touch Controller +static XPT2046_Touchscreen resTouch; +//Capacitive Touch Controller +static FT6206_Touchscreen capTouch; + +/*############################# PUBLIC VARIABLES ##############################*/ + +//Choose the right touch screen +volatile bool touch_capacitive; + +/*###################### PRIVATE FUNCTION BODIES ##############################*/ + +/* Returns the coordinates of the touched point */ +TS_Point touch_getPoint() { + //Get touch from capacitive + if (touch_capacitive) + return capTouch.getPoint(); + //Get point from resistive + return resTouch.getPoint(); +} + +/* Initializes the touch module and checks if it is working */ +void touch_init() { + //Capacitive screen + if (capTouch.begin()) + touch_capacitive = true; + + //Resistive screen or none + else { + resTouch.begin(); + touch_capacitive = false; + } + + //If not capacitive, check if connected + if (!touch_capacitive) + { + //Get a point + TS_Point point = touch_getPoint(); + //Wait short + delay(10); + //Read one time to stabilize + digitalRead(pin_touch_irq); + + //Init touch status + bool touchStatus = true; + //Check IRQ 10 times, should be HIGH + for (int i = 0; i < 10; i++) + { + if (!digitalRead(pin_touch_irq)) + touchStatus = false; + delay(10); + } + + //Comparison value depending on rotation + uint16_t xval, yval; + if (rotationVert) { + xval = 320; + yval = 240; + } + else { + xval = 0; + yval = 0; + } + + //Check if touch is working, otherwise set diagnostic + if (!(((point.x == xval) && (point.y == yval) && (touchStatus == true)) + || ((point.x != xval) && (point.y != yval) && (touchStatus == false)))) + setDiagnostic(diag_touch); + } +} + +/* Returns if the screen is currently touched */ +volatile bool touch_touched() { + bool touch; + //Check for touch, capacitive or resistive + if (touch_capacitive) + touch = capTouch.touched(); + else + touch = resTouch.touched(); + //If touch registered, set screen pressed marker + if (touch) + screenPressed = true; + return touch; +} + +/* Set rotation for touch screen */ +void touch_setRotation(bool rotated) { + if (touch_capacitive) + capTouch.rotated = rotated; + else + resTouch.rotated = rotated; +} diff --git a/Firmware_V3/src/hardware/touchscreen/xpt2046_touchscreen.cpp b/Firmware_V3/src/hardware/touchscreen/xpt2046_touchscreen.cpp new file mode 100644 index 0000000..f7062c9 --- /dev/null +++ b/Firmware_V3/src/hardware/touchscreen/xpt2046_touchscreen.cpp @@ -0,0 +1,158 @@ +/* + * + * XPT2046 Touch controller + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include + +/*################# DATA TYPES, CONSTANTS & MACRO DEFINITIONS #################*/ + +#define Z_THRESHOLD 400 +#define MSEC_THRESHOLD 3 + +/*###################### PRIVATE FUNCTION BODIES ##############################*/ + +XPT2046_Touchscreen::XPT2046_Touchscreen() +{ + csPin = pin_touch_cs; + tirqPin = 255; + msraw = 0x80000000; + xraw = 0; + yraw = 0; + zraw = 0; + isrWake = true; +} + +static XPT2046_Touchscreen *isrPinptr; +void isrPin(void); + +bool XPT2046_Touchscreen::begin() +{ + if (255 != tirqPin) { + pinMode(tirqPin, INPUT); + attachInterrupt(tirqPin, isrPin, FALLING); + isrPinptr = this; + } + return true; +} + +void isrPin(void) +{ + XPT2046_Touchscreen *o = isrPinptr; + o->isrWake = true; +} + +TS_Point XPT2046_Touchscreen::getPoint() +{ + update(); + if (rotated) { + yraw = map(yraw, 0, 240, 240, 0); + xraw = map(xraw, 0, 320, 320, 0); + } + return TS_Point(xraw, yraw, zraw); +} + +bool XPT2046_Touchscreen::touched() +{ + update(); + //Prevent double touch + delay(20); + return (zraw >= Z_THRESHOLD); +} + +void XPT2046_Touchscreen::readData(uint16_t *x, uint16_t *y, uint8_t *z) +{ + update(); + *x = xraw; + *y = yraw; + *z = zraw; +} + +bool XPT2046_Touchscreen::bufferEmpty() +{ + return ((millis() - msraw) < MSEC_THRESHOLD); +} + +static int16_t besttwoavg(int16_t x, int16_t y, int16_t z) { + int16_t da, db, dc; + int16_t reta; + if (x > y) da = x - y; else da = y - x; + if (x > z) db = x - z; else db = z - x; + if (z > y) dc = z - y; else dc = y - z; + if (da <= db && da <= dc) reta = (x + y) >> 1; + else if (db <= da && db <= dc) reta = (x + z) >> 1; + else reta = (y + z) >> 1; + + return (reta); +} + +void XPT2046_Touchscreen::update() +{ + int16_t data[6]; + if (!isrWake) return; + uint32_t now = millis(); + if (now - msraw < MSEC_THRESHOLD) return; + SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0)); +#if defined(__MK20DX256__) + CORE_PIN13_CONFIG = PORT_PCR_MUX(1); + CORE_PIN14_CONFIG = PORT_PCR_DSE | PORT_PCR_MUX(2); +#endif + digitalWrite(csPin, LOW); + SPI.transfer(0xB1); + int16_t z1 = SPI.transfer16(0xC1) >> 3; + int z = z1 + 4095; + int16_t z2 = SPI.transfer16(0x91) >> 3; + z -= z2; + if (z >= Z_THRESHOLD) { + SPI.transfer16(0x91); + data[0] = SPI.transfer16(0xD1) >> 3; + data[1] = SPI.transfer16(0x91) >> 3; + data[2] = SPI.transfer16(0xD1) >> 3; + data[3] = SPI.transfer16(0x91) >> 3; + } + else data[0] = data[1] = data[2] = data[3] = 0; + data[4] = SPI.transfer16(0xD0) >> 3; + data[5] = SPI.transfer16(0) >> 3; + digitalWrite(csPin, HIGH); +#if defined(__MK20DX256__) + CORE_PIN13_CONFIG = PORT_PCR_DSE | PORT_PCR_MUX(2); + CORE_PIN14_CONFIG = PORT_PCR_MUX(1); +#endif + SPI.endTransaction(); + if (z < 0) z = 0; + if (z < Z_THRESHOLD) { + zraw = 0; + if (255 != tirqPin) isrWake = false; + return; + } + zraw = z; + int16_t x = besttwoavg(data[0], data[2], data[4]); + int16_t y = besttwoavg(data[1], data[3], data[5]); + if (z >= Z_THRESHOLD) { + msraw = now; + xraw = x; + yraw = y; + } + xraw = map(xraw, 230, 3670, 0, 320); + yraw = map(yraw, 230, 3800, 240, 0); + if (xraw < 0) xraw = 0; + if (xraw > 319) xraw = 319; + if (yraw < 0) yraw = 0; + if (yraw > 239) yraw = 239; +} diff --git a/Firmware_V3/src/main.cpp b/Firmware_V3/src/main.cpp new file mode 100644 index 0000000..44e7d41 --- /dev/null +++ b/Firmware_V3/src/main.cpp @@ -0,0 +1,60 @@ +/* +* +* MAIN SKETCH - Main entry point for the firmware +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Main entry point */ +void setup() +{ + //Init the hardware components + initHardware(); + + //Check for firmware upgrade done + checkFWUpgrade(); + + //Enter USB connection if no display attached + if (checkNoDisplay()) + serialInit(); + + //Check for hardware issues + checkHardware(); + + //Do the first start setup if required + if (checkFirstStart()) + firstStart(); + + //Read all settings from EEPROM + readEEPROM(); + + //Show the live mode helper if required + if (checkLiveModeHelper()) + liveModeHelper(); + + //Go to the live Mode + liveMode(); +} + +/* Loop not used */ +void loop() +{ +} diff --git a/Firmware_V3/src/thermal/create.cpp b/Firmware_V3/src/thermal/create.cpp new file mode 100644 index 0000000..a266265 --- /dev/null +++ b/Firmware_V3/src/thermal/create.cpp @@ -0,0 +1,596 @@ +/* +* +* CREATE - Functions to create and display the thermal frameBuffer +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Filter a 160x120 smallBuffer with 3x3 gaussian kernel */ +void gaussianFilter() +{ + byte gaussianKernel[3][3] = { + {1, 2, 1}, + {2, 4, 2}, + {1, 2, 1}}; + long sum; + + for (int y = 1; y < 119; y++) + { + for (int x = 1; x < 159; x++) + { + sum = 0; + for (int k = -1; k <= 1; k++) + { + for (int j = -1; j <= 1; j++) + { + sum += gaussianKernel[j + 1][k + 1] * smallBuffer[(y - j) * 160 + (x - k)]; + } + } + smallBuffer[(y * 160) + x] = (unsigned short)(sum / 16.0); + } + } +} + +/* Filter a 160x120 smallBuffer with a 3x3 box kernel */ +void boxFilter() +{ + byte boxKernel[3][3] = { + {1, 1, 1}, + {1, 1, 1}, + {1, 1, 1}}; + long sum; + + for (int y = 1; y < 119; y++) + { + for (int x = 1; x < 159; x++) + { + sum = 0; + for (int k = -1; k <= 1; k++) + { + for (int j = -1; j <= 1; j++) + { + sum += boxKernel[j + 1][k + 1] * smallBuffer[(y - j) * 160 + (x - k)]; + } + } + smallBuffer[(y * 160) + x] = (unsigned short)(sum / 9.0); + } + } +} + +//Resize the pixels of thermal smallBuffer +void resizePixels(unsigned short *pixels, int w1, int h1, int w2, int h2) +{ + //Calculate the ratio + int x_ratio = (int)((w1 << 16) / w2) + 1; + int y_ratio = (int)((h1 << 16) / h2) + 1; + int x2, y2; + for (int i = 0; i < h2; i++) + { + for (int j = 0; j < w2; j++) + { + x2 = ((j * x_ratio) >> 16); + y2 = ((i * y_ratio) >> 16); + pixels[(i * w1) + j] = pixels[(y2 * w1) + x2]; + } + } + //Set the other pixels to zero + for (int j = 0; j < h2; j++) + { + for (int i = w2; i < w1; i++) + { + pixels[i + (j * w1)] = 65535; + } + } + for (int j = h2; j < h1; j++) + { + for (int i = 0; i < w1; i++) + { + pixels[i + (j * w1)] = 65535; + } + } +} + +/* Write the smallBuffer to the bigBuffer by resizing */ +void smallToBigBuffer() +{ + unsigned short A, B, C, D, outVal; + int x, y, index; + float x_ratio = 159.0 / 320; + float y_ratio = 119.0 / 240; + float x_diff, y_diff; + + int offset = 0; + for (int i = 0; i < 240; i++) + { + for (int j = 0; j < 320; j++) + { + + x = (int)(x_ratio * j); + y = (int)(y_ratio * i); + x_diff = (x_ratio * j) - x; + y_diff = (y_ratio * i) - y; + index = y * 160 + x; + + A = smallBuffer[index]; + B = smallBuffer[index + 1]; + C = smallBuffer[index + 160]; + D = smallBuffer[index + 160 + 1]; + + outVal = (unsigned short)(A * (1 - x_diff) * (1 - y_diff) + B * (x_diff) * (1 - y_diff) + + C * (y_diff) * (1 - x_diff) + D * (x_diff * y_diff)); + + bigBuffer[offset] = outVal; + + //Raise counter + offset++; + } + } +} + +/* Clears the temperature points array */ +void clearTempPoints() +{ + //Go through the array + for (byte i = 0; i < 96; i++) + { + //Set the index to zero + tempPoints[i][0] = 0; + //Set the value to zero + tempPoints[i][1] = 0; + } +} + +/* Shows the temperatures over the smallBuffer on the screen */ +void showTemperatures() +{ + int16_t xpos, ypos; + + //Go through the array + for (byte i = 0; i < 96; i++) + { + //Get index + uint16_t index = tempPoints[i][0]; + + //Check if the tempPoint is active + if (index != 0) + { + //Index goes from 1 to max + index -= 1; + + //Calculate x and y position + calculatePointPos(&xpos, &ypos, index); + + //Draw the marker + display_drawLine(xpos, ypos, xpos, ypos); + + //Calc x position for the text + xpos -= 20; + if (xpos < 0) + xpos = 0; + if (xpos > 279) + xpos = 279; + + //Calc y position for the text + ypos += 15; + if (ypos > 229) + ypos = 229; + + //Display the absolute temperature + display_printNumF(rawToTemp(tempPoints[i][1]), 2, xpos, ypos); + } + } +} + +/* Read the x and y coordinates when touch screen is pressed for Add Point */ +void getTouchPos(uint16_t *x, uint16_t *y) +{ + int iter = 0; + TS_Point point; + unsigned long tx = 0; + unsigned long ty = 0; + //Wait for touch press + while (!touch_touched()) + ; + //While touch pressed, iterate over readings + while (touch_touched() == true) + { + point = touch_getPoint(); + if ((point.x >= 0) && (point.x <= 320) && (point.y >= 0) && (point.y <= 240)) + { + tx += point.x; + ty += point.y; + iter++; + } + } + *x = tx / iter; + *y = ty / iter; +} + +/* Function to add or remove a measurement point */ +void tempPointFunction(bool remove) +{ + uint16_t xpos, ypos; + byte pos = 0; + bool removed = false; + + //If remove points, check if there are some first + if (remove) + { + //Go through the array + for (byte i = 0; i < 96; i++) + { + if (tempPoints[i][0] != 0) + { + removed = true; + break; + } + } + //No points available to remove + if (!removed) + { + showFullMessage((char *)"No points available", true); + delay(1000); + return; + } + } + //If add points, check if we have space left + else + { + //Go through the array + byte i; + for (i = 0; i < 96; i++) + { + if (tempPoints[i][0] == 0) + { + pos = i; + break; + } + } + //Maximum number of points added + if (i == 96) + { + showFullMessage((char *)"Remove a point first", true); + delay(1000); + return; + } + } + +redraw: + //Create thermal small buffer + lepton_startFrame(); + createThermalImg(); + + //Show it on the screen + displayBuffer(); + + //Set text color, font and background + changeTextColor(); + display_setBackColor(VGA_TRANSPARENT); + display_setFont(smallFont); + //Show current temperature points + showTemperatures(); + //Display title + display_setFont(bigFont); + display_print((char *)"Select position", CENTER, 210); + + //Get touched coordinates + getTouchPos(&xpos, &ypos); + + //Divide through 2 to match array size + xpos /= 2; + ypos /= 2; + + //Remove point + if (remove) + { + //Set marker to false + removed = false; + + //Check for 10 pixels around the touch position + for (uint16_t x = xpos - 10; x <= xpos + 10; x++) + { + for (uint16_t y = ypos - 10; y <= ypos + 10; y++) + { + //Calculate index number + uint16_t index = x + (y * 160) + 1; + //If index is valid + if ((index >= 1) && (index <= 19200)) + { + //Check for all 96 points + for (byte i = 0; i < 96; i++) + { + //We found a valid temperature point + if (tempPoints[i][0] == index) + { + //Set to invalid + tempPoints[i][0] = 0; + //Reset value + tempPoints[i][1] = 0; + //Set markter to true + removed = true; + } + } + } + } + } + //Show border + drawMainMenuBorder(); + //Show removed message + if (removed) + showFullMessage((char *)"Point removed", true); + //Invalid position, redraw + else + { + showFullMessage((char *)"Invalid position", true); + delay(1000); + goto redraw; + } + } + + //Add point + else + { + //Add index + tempPoints[pos][0] = xpos + (ypos * 160) + 1; + //Set raw value to zero + tempPoints[pos][1] = 0; + //Show border + drawMainMenuBorder(); + //Show message + showFullMessage((char *)"Point added", true); + } + + //Wait some time + delay(1000); +} + +/* Go through the array of temperatures and find min and max temp */ +void limitValues() +{ + maxValue = 0; + minValue = 65535; + uint16_t temp; + for (int i = 0; i < 19200; i++) + { + //Get value + temp = smallBuffer[i]; + //Find maximum temp + if (temp > maxValue) + maxValue = temp; + //Find minimum temp + if (temp < minValue) + minValue = temp; + } +} + +/* Get the colors for hot / cold mode selection */ +void getHotColdColors(byte *red, byte *green, byte *blue) +{ + switch (hotColdColor) + { + //White + case hotColdColor_white: + *red = 255; + *green = 255; + *blue = 255; + break; + //Black + case hotColdColor_black: + *red = 0; + *green = 0; + *blue = 0; + break; + //White + case hotColdColor_red: + *red = 255; + *green = 0; + *blue = 0; + break; + //White + case hotColdColor_green: + *red = 0; + *green = 255; + *blue = 0; + break; + //White + case hotColdColor_blue: + *red = 0; + *green = 0; + *blue = 255; + break; + } +} + +/* Convert the lepton values to RGB colors */ +void convertColors(bool small) +{ + uint8_t red = 0; + uint8_t green = 0; + uint8_t blue = 0; + uint16_t value; + + //Calculate the scale + float scale = (colorElements - 1.0) / (maxValue - minValue); + + //For hot and cold mode, calculate rawlevel + float hotColdRawLevel = 0.0; + if (hotColdMode != hotColdMode_disabled) + hotColdRawLevel = tempToRaw(hotColdLevel); + + //Size of the array & buffer + int size; + unsigned short* frameBuffer; + if (!small) { + size = 76800; + frameBuffer = bigBuffer; + } + else { + size = 19200; + frameBuffer = smallBuffer; + } + + + //Repeat for 160x120 data + for (int i = 0; i < size; i++) + { + + value = frameBuffer[i]; + + //Limit values + if (value > maxValue) + value = maxValue; + if (value < minValue) + value = minValue; + + //Hot + if ((hotColdMode == hotColdMode_hot) && (value >= hotColdRawLevel)) + getHotColdColors(&red, &green, &blue); + //Cold + else if ((hotColdMode == hotColdMode_cold) && (value <= hotColdRawLevel)) + getHotColdColors(&red, &green, &blue); + + //Apply colorscheme + else + { + value = (value - minValue) * scale; + red = colorMap[3 * value]; + green = colorMap[3 * value + 1]; + blue = colorMap[3 * value + 2]; + } + + //Convert to RGB565 + frameBuffer[i] = (((red & 248) | green >> 5) << 8) | ((green & 28) << 3 | blue >> 3); + } +} + +/* Refresh the position and value of the min / max value */ +void refreshMinMax() +{ + //Reset values + minTempVal = 65535; + maxTempVal = 0; + + //Go through the smallBuffer + for (int i = 0; i < 19200; i++) + { + //We found a new min + if (smallBuffer[i] < minTempVal) + { + //Save position and value + minTempPos = i; + minTempVal = smallBuffer[i]; + } + + //We found a new max + if (smallBuffer[i] > maxTempVal) + { + maxTempPos = i; + maxTempVal = smallBuffer[i]; + } + } +} + +/* Refresh the temperature points*/ +void refreshTempPoints() +{ + //Go through the array + for (byte i = 0; i < 96; i++) + { + //Get index + uint16_t index = tempPoints[i][0]; + + //Check if point is active + if (index != 0) + { + //Index goes from 1 to max + index -= 1; + + //Calculate x and y position + uint16_t xpos = index % 160; + uint16_t ypos = index / 160; + + //Update value + tempPoints[i][1] = smallBuffer[xpos + (ypos * 160)]; + } + } +} + +/* Calculate the x and y position out of the pixel index */ +void calculatePointPos(int16_t *xpos, int16_t *ypos, uint16_t pixelIndex) +{ + //Get xpos and ypos + *xpos = (pixelIndex % 160) * 2; + *ypos = (pixelIndex / 160) * 2; + + //Limit position + if (*ypos > 238) + *ypos = 228; + if (*xpos > 318) + *xpos = 318; +} + +/* Creates a thermal smallBuffer and stores it in the array */ +void createThermalImg(bool small) +{ + //Get Lepton frame + if (small) + lepton_startFrame(); + lepton_getFrame(); + + //Get the spot temperature + getSpotTemp(); + + //Refresh the temp points if required + refreshTempPoints(); + + //Find min / max position + if (minMaxPoints != minMaxPoints_disabled) + refreshMinMax(); + + //Find min and max if not in manual mode and limits not locked + if ((autoMode) && (!limitsLocked)) + limitValues(); + + //If smallBuffer save, save the raw data + if (imgSave == imgSave_create) + saveRawData(true, saveFilename); + + //Apply low-pass filter + if (filterType == filterType_box) + boxFilter(); + else if (filterType == filterType_gaussian) + gaussianFilter(); + + //Resize to big buffer when not preview + if (!small) + smallToBigBuffer(); + + //Convert lepton data to RGB565 colors + if (!videoSave) + convertColors(small); +} \ No newline at end of file diff --git a/Firmware_V3/src/thermal/load.cpp b/Firmware_V3/src/thermal/load.cpp new file mode 100644 index 0000000..d3836c0 --- /dev/null +++ b/Firmware_V3/src/thermal/load.cpp @@ -0,0 +1,1341 @@ +/* +* +* LOAD - Load images and videos from the internal storage +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*################# DATA TYPES, CONSTANTS & MACRO DEFINITIONS #################*/ + +#define lepton2_small 9621 +#define lepton2_big 10005 +#define lepton3_small 38421 +#define lepton3_big 38805 +#define bitmap 614466 +#define maxFiles 500 +#define loadMode_image 0 +#define loadMode_video 1 + +/*######################### STATIC DATA DECLARATIONS ##########################*/ + +//Storage for up to maxFiles images/videos +static uint16_t *yearStorage; +static byte *monthStorage; +static byte *dayStorage; +static byte *hourStorage; +static byte *minuteStorage; +static byte *secondStorage; + +//Buffer for the single elements +static char yearBuf[] = "2016"; +static char monthBuf[] = "12"; +static char dayBuf[] = "31"; +static char hourBuf[] = "23"; +static char minuteBuf[] = "60"; +static char secondBuf[] = "60"; + +//Save how many different elements we have +static byte yearnum = 0; +static byte monthnum = 0; +static byte daynum = 0; +static byte hournum = 0; +static byte minutenum = 0; +static byte secondnum = 0; + +//Keep track how many images are on the SDCard +static int imgCount = 0; + +//Decide if we load videos or images +static bool loadMode; + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Clear all previous data */ +void clearData() +{ + for (int i = 0; i < maxFiles; i++) + { + yearStorage[i] = 0; + monthStorage[i] = 0; + dayStorage[i] = 0; + hourStorage[i] = 0; + minuteStorage[i] = 0; + secondStorage[i] = 0; + } + strcpy(yearBuf, "2016"); + strcpy(monthBuf, "12"); + strcpy(dayBuf, "31"); + strcpy(hourBuf, "23"); + strcpy(minuteBuf, "60"); + strcpy(secondBuf, "60"); + yearnum = 0; + monthnum = 0; + daynum = 0; + hournum = 0; + minutenum = 0; + secondnum = 0; + imgCount = 0; + clearTempPoints(); +} + +/* Display the image on the screen */ +void displayRawData() +{ + //Select Color Scheme + selectColorScheme(); + + //Apply low-pass filter + if (filterType == filterType_box) + boxFilter(); + else if (filterType == filterType_gaussian) + gaussianFilter(); + + //Find min / max position + if (minMaxPoints != minMaxPoints_disabled) + refreshMinMax(); + + //Resize to big buffer + smallToBigBuffer(); + + //Convert lepton data to RGB565 colors + convertColors(); + + //Display additional information + displayInfos(); + + //Display on screen + displayBuffer(); +} + +/* Loads a 640x480 BMP image from the SDCard and prints it on screen */ +void loadBMPImage(char *filename) +{ + //Help variables + byte low, high; + // Open the file for reading + sdFile.open(filename, O_READ); + + //Skip the 66 bytes BMP header + for (int i = 0; i < 66; i++) + sdFile.read(); + + for (int y = 479; y >= 0; y--) + { + for (int x = 0; x < 640; x++) + { + low = sdFile.read(); + high = sdFile.read(); + //Get the image color + bigBuffer[(x / 2) + ((y / 2) * 320)] = (high << 8) | low; + } + } + //Close data file + sdFile.close(); + + //Draw it on the screen + display_drawBitmap(0, 0, 320, 240, bigBuffer); +} + +/* Checks if the file is an image*/ +bool isImage(char *filename) +{ + bool isImg; + + //Open file + sdFile.open(filename, O_READ); + + //Check if it is a file + if (sdFile.isFile()) + isImg = true; + + //Otherwise it is a video + else + { + //Delete the ending + filename[14] = '\0'; + isImg = false; + } + + sdFile.close(); + + return isImg; +} + +/* Read the temperature points from the file, new or old format */ +void readTempPoints() +{ + uint16_t tempArray[192]; + bool oldFormat = false; + + //Read the array + for (byte i = 0; i < 192; i++) + { + //Read from SD file + tempArray[i] = (sdFile.read() << 8) + sdFile.read(); + + //Correct old not_set marker + if (tempArray[i] == 65535) + tempArray[i] = 0; + } + + //Check for old format + for (byte i = 1; i < 191; i++) + { + //Valid value found + if (tempArray[i] != 0) + { + //If the value before and after is zero, it is the old format + if ((tempArray[i - 1] == 0) && (tempArray[i + 1] == 0)) + oldFormat = true; + } + } + + //Load the old format + if (oldFormat) + { + //Position counter + byte pos = 0; + + //Go through the array + for (byte i = 0; i < 192; i++) + { + //Valid value found + if (tempArray[i] != 0) + { + //Calculate x and y position + uint16_t xpos = (i % 16) * 10; + uint16_t ypos = (i / 16) * 10; + + //Calculate index + tempPoints[pos][0] = xpos + (ypos * 160) + 1; + //Save value + tempPoints[pos][1] = tempArray[i]; + + //Raise position + pos++; + //When maximum number of temp points reached, exit + if (pos == 96) + return; + } + } + } + + //Load the new format + else + { + //Go through the array + for (byte i = 0; i < 96; i++) + { + //Read index + tempPoints[i][0] = tempArray[(i * 2)]; + //Read value + tempPoints[i][1] = tempArray[(i * 2) + 1]; + } + } +} + +/* Loads raw data from the internal storage*/ +void loadRawData(char *filename) +{ + byte msb, lsb; + uint16_t result; + uint32_t fileSize; + + // Open the file for reading + sdFile.open(filename, O_READ); + + //Get file size + fileSize = sdFile.fileSize(); + + //For the Lepton2 sensor, read 4800 raw values + if ((fileSize == lepton2_small) || (fileSize == lepton2_big)) + { + for (int line = 0; line < 60; line++) + { + for (int column = 0; column < 80; column++) + { + msb = sdFile.read(); + lsb = sdFile.read(); + result = (((msb) << 8) + lsb); + smallBuffer[(line * 2 * 160) + (column * 2)] = result; + smallBuffer[(line * 2 * 160) + (column * 2) + 1] = result; + smallBuffer[(line * 2 * 160) + 160 + (column * 2)] = result; + smallBuffer[(line * 2 * 160) + 160 + (column * 2) + 1] = result; + } + } + } + + //For the Lepton3 sensor, read 19200 raw values + else if ((fileSize == lepton3_small) || (fileSize == lepton3_big)) + { + for (int i = 0; i < 19200; i++) + { + msb = sdFile.read(); + lsb = sdFile.read(); + smallBuffer[i] = (((msb) << 8) + lsb); + } + } + //Invalid data + else + { + showFullMessage((char *)"Invalid file size"); + delay(1000); + sdFile.close(); + return; + } + + //Read Min + msb = sdFile.read(); + lsb = sdFile.read(); + minValue = (((msb) << 8) + lsb); + //Read Max + msb = sdFile.read(); + lsb = sdFile.read(); + maxValue = (((msb) << 8) + lsb); + + //Read object temperature + uint8_t farray[4]; + for (int i = 0; i < 4; i++) + farray[i] = sdFile.read(); + spotTemp = bytesToFloat(farray); + + //Read color scheme + colorScheme = sdFile.read(); + //Read temp format + tempFormat = sdFile.read(); + //Read spot enabled + spotEnabled = sdFile.read(); + //Read colorbar enabled + colorbarEnabled = sdFile.read(); + //Read min max enabled + minMaxPoints = sdFile.read(); + + //Read calibration offset but do not use + for (int i = 0; i < 4; i++) + farray[i] = sdFile.read(); + //Read calibration slope but do not use + for (int i = 0; i < 4; i++) + farray[i] = sdFile.read(); + + //Lepton2.5 + if ((fileSize == lepton2_small) || (fileSize == lepton2_big)) + { + leptonVersion = leptonVersion_2_5_shutter; + } + + //Lepton3.5 + if ((fileSize == lepton3_small) || (fileSize == lepton3_big)) + { + leptonVersion = leptonVersion_3_5_shutter; + } + + //Clear temperature points array + clearTempPoints(); + + //Read temperatures if they are included + if ((fileSize == lepton3_big) || (fileSize == lepton2_big)) + readTempPoints(); + + sdFile.close(); +} + +/* A method to choose the right yearStorage */ +bool yearChoose(char *filename) +{ + //Can imgCount up to 50 years + bool years[50] = {0}; + //Go through all images + for (int i = 0; i < imgCount; i++) + { + int yearCheck = yearStorage[i] - 2016; + //Check if the yearStorage is at least 2016 + if (yearCheck < 0) + { + //if it is not, return to main menu with error message + showFullMessage((char *)"The year must be >= 2016"); + delay(1000); + return true; + //Check if yearStorage is smaller than 2064 - unlikely the Thermocam is still in use then ! + } + else if (yearCheck > 49) + { + //if it is not, return to main menu with error message + showFullMessage((char *)"The year must be < 2064"); + delay(1000); + return true; + //Add yearStorage to the array if passes the checks + } + else + { + years[yearCheck] = true; + } + } + for (int i = 0; i < 50; i++) + { + if (years[i]) + yearnum = yearnum + 1; + } + //Create an array for those years + int Years[yearnum]; + yearnum = 0; + //Add them in descending order + for (int i = 49; i >= 0; i--) + { + if (years[i]) + { + Years[yearnum] = 2016 + i; + yearnum = yearnum + 1; + } + } + //If there is only one yearStorage, choose it directly + if (yearnum == 1) + { + itoa(Years[0], yearBuf, 10); + } + //Otherwise open the yearStorage chooser + else + { + int res = loadMenu((char *)"Load file: Year", Years, yearnum); + //User want to return to the start menu + if (res == -1) + { + return true; + //Save the chosen yearStorage + } + else + { + itoa(Years[res], yearBuf, 10); + } + } + //Copy the chosen yearStorage to the filename + strncpy(&filename[0], yearBuf, 4); + return false; +} + +/* A method to choose the right monthStorage */ +bool monthChoose(bool *months, char *filename) +{ + for (int i = 0; i < 12; i++) + { + if (months[i]) + monthnum = monthnum + 1; + } + //Add them to the array in descending order + int Months[monthnum]; + monthnum = 0; + for (int i = 11; i >= 0; i--) + { + if (months[i]) + { + //Add one as monthStorage start with January + Months[monthnum] = 1 + i; + monthnum = monthnum + 1; + } + } + //If there is only one, chose it directly + if (monthnum == 1) + { + itoa(Months[0], monthBuf, 10); + } + //If not, open the image chooser + else + { + int res = loadMenu((char *)"Load file: Month", Months, + monthnum); + //the user wants to go back to the years + if (res == -1) + { + return true; + } + else + { + itoa(Months[res], monthBuf, 10); + } + } + //Add a zero if monthStorage is smaller than 10 + if (atoi(monthBuf) < 10) + { + filename[4] = '0'; + strncpy(&filename[5], monthBuf, 1); + } + //Else copy those two digits + else + strncpy(&filename[4], monthBuf, 2); + return false; +} + +/* A method to choose the right dayStorage */ +bool dayChoose(bool *days, char *filename) +{ + for (int i = 0; i < 31; i++) + { + if (days[i]) + daynum = daynum + 1; + } + //Sort them descending + int Days[daynum]; + daynum = 0; + for (int i = 30; i >= 0; i--) + { + if (days[i]) + { + Days[daynum] = 1 + i; + daynum = daynum + 1; + } + } + //We only have one + if (daynum == 1) + { + itoa(Days[0], dayBuf, 10); + } + //Choose dayStorage + else + { + int res = loadMenu((char *)"Load file: Day", Days, daynum); + if (res == -1) + { + return true; + } + else + { + itoa(Days[res], dayBuf, 10); + } + } + //Add dayStorage to the filename + if (atoi(dayBuf) < 10) + { + filename[6] = '0'; + strncpy(&filename[7], dayBuf, 1); + } + else + strncpy(&filename[6], dayBuf, 2); + return false; +} + +/* A method to choose the right hourStorage */ +bool hourChoose(bool *hours, char *filename) +{ + for (int i = 0; i < 24; i++) + { + if (hours[i]) + hournum = hournum + 1; + } + //Sort them descending + int Hours[hournum]; + hournum = 0; + for (int i = 23; i >= 0; i--) + { + if (hours[i]) + { + Hours[hournum] = i; + hournum = hournum + 1; + } + } + //If there is only one + if (hournum == 1) + { + itoa(Hours[0], hourBuf, 10); + } + //Otherwise choose + else + { + int res = loadMenu((char *)"Load file: Hour", Hours, hournum); + if (res == -1) + { + return true; + } + else + { + itoa(Hours[res], hourBuf, 10); + } + } + //Add hourStorage to the filename + if (atoi(hourBuf) < 10) + { + filename[8] = '0'; + strncpy(&filename[9], hourBuf, 1); + } + else + strncpy(&filename[8], hourBuf, 2); + return false; +} + +/* A method to choose the right minuteStorage */ +bool minuteChoose(bool *minutes, char *filename) +{ + for (int i = 0; i < 60; i++) + { + if (minutes[i]) + minutenum = minutenum + 1; + } + //Sort them descending + int Minutes[minutenum]; + minutenum = 0; + for (int i = 59; i >= 0; i--) + { + if (minutes[i]) + { + Minutes[minutenum] = i; + minutenum = minutenum + 1; + } + } + //if there is only one + if (minutenum == 1) + { + itoa(Minutes[0], minuteBuf, 10); + } + //Otherwise choose + else + { + int res = loadMenu((char *)"Load file: Minute", Minutes, + minutenum); + if (res == -1) + { + return true; + } + else + { + itoa(Minutes[res], minuteBuf, 10); + } + } + //Copy minutes to the filename + if (atoi(minuteBuf) < 10) + { + filename[10] = '0'; + strncpy(&filename[11], minuteBuf, 1); + } + else + strncpy(&filename[10], minuteBuf, 2); + return false; +} + +/* A method to choose the right secondStorage */ +bool secondChoose(bool *seconds, char *filename) +{ + for (int i = 0; i < 60; i++) + { + if (seconds[i]) + secondnum = secondnum + 1; + } + //Sort them descending + int Seconds[secondnum]; + secondnum = 0; + for (int i = 59; i >= 0; i--) + { + if (seconds[i]) + { + Seconds[secondnum] = i; + secondnum = secondnum + 1; + } + } + //if there is only one + if (secondnum == 1) + { + itoa(Seconds[0], secondBuf, 10); + } + //Otherwise choose the right + else + { + int res = loadMenu((char *)"Load file: Second", Seconds, + secondnum); + if (res == -1) + { + return true; + } + else + { + itoa(Seconds[res], secondBuf, 10); + } + } + //Add secondStorage to the filename + if (atoi(secondBuf) < 10) + { + filename[12] = '0'; + strncpy(&filename[13], secondBuf, 1); + } + else + strncpy(&filename[12], secondBuf, 2); + return false; +} + +/* Extract the filename into the buffers */ +void copyIntoBuffers(char *filename) +{ + //Save filename into the buffer + sdFile.getName(filename, 19); + //Extract the time and date components into extra buffer + strncpy(yearBuf, &filename[0], 4); + strncpy(monthBuf, &filename[4], 2); + strncpy(dayBuf, &filename[6], 2); + strncpy(hourBuf, &filename[8], 2); + strncpy(minuteBuf, &filename[10], 2); + strncpy(secondBuf, &filename[12], 2); +} + +/* Check if the file is a valid one */ +bool checkFileValidity() +{ + //Load images + if (loadMode == loadMode_image) + { + uint32_t fileSize = sdFile.fileSize(); + return (sdFile.isFile() && ((fileSize == lepton2_small) || (fileSize == lepton2_big) || + (fileSize == lepton3_small) || (fileSize == lepton3_big) || (fileSize == bitmap))); + } + //Load videos + return sdFile.isDir(); +} + +/* Check if the name matches the criterion */ +void checkFileStructure(bool *check) +{ + //Check if yearStorage is 4 digit + for (int i = 0; i < 4; i++) + { + if (!(isdigit(yearBuf[i]))) + *check = false; + } + //Check if the other elements are two digits each + for (int i = 0; i < 2; i++) + { + if (!(isdigit(monthBuf[i]))) + *check = false; + if (!(isdigit(dayBuf[i]))) + *check = false; + if (!(isdigit(hourBuf[i]))) + *check = false; + if (!(isdigit(minuteBuf[i]))) + *check = false; + if (!(isdigit(secondBuf[i]))) + *check = false; + } +} + +/* Check if the filename ends with .DAT or .BMP if the file is a single image */ +void checkFileEnding(bool *check, char *filename) +{ + char ending_dat[] = ".DAT"; + //Check for DAT first + if (((filename[14] != ending_dat[0]) || (filename[15] != ending_dat[1]) || (filename[16] != ending_dat[2]) || (filename[17] != ending_dat[3]))) + { + + //If it is not DAT, it could be BMP + char ending_bmp[] = ".BMP"; + if ((filename[14] != ending_bmp[0]) || (filename[15] != ending_bmp[1]) || (filename[16] != ending_bmp[2]) || (filename[17] != ending_bmp[3])) + //None of both + *check = false; + + //If bitmap, check if the file has a corresponding DAT + else + { + strcpy(&filename[14], ".DAT"); + sdFile.close(); + //Check if it is a file + sdFile.open(filename, O_READ); + if (sdFile.isFile()) + *check = false; + //Open the old file + strcpy(&filename[14], ".BMP"); + sdFile.close(); + sdFile.open(filename, O_READ); + } + } +} + +/* Find the next or previous file/folder on the SD card or the position */ +bool findFile(char *filename, bool next, bool restart, int *position, char *compare) +{ + bool found = false; + int counter = 0; + + //Point to root directory + dir.open("/"); + + //Get filenames from SD Card - one after another and a maximum of maxFiles + while (sdFile.openNext(&dir, O_RDONLY)) + { + //Either folder for video or file with specific size for single image + if (checkFileValidity()) + { + //Extract the filename into the buffers + copyIntoBuffers(filename); + //Check if the name matches the criterion + bool check = true; + //Check if the other elements are two digits each + checkFileStructure(&check); + //Check if the filename ends with .DAT or .BMP if the file is a single image + if (loadMode == loadMode_image) + checkFileEnding(&check, filename); + //If all checks were successfull, add image to the results + if (check) + { + //If we want to get the next image + if (next) + { + found = true; + break; + } + //If we want to get the previous image or get the position + if (compare != NULL) + { + if (strcmp(compare, filename) == 0) + { + *position = counter; + found = true; + break; + } + counter++; + } + //In case we found it + else if (*position == counter) + { + found = true; + break; + } + //If not, raise the counter + else + counter++; + } + } + //Close the file + sdFile.close(); + } + + dir.close(); + + //Return result + return found; +} + +/* Search for files and videos */ +void searchFiles() +{ + char filename[20]; + + //Point to root directory + dir.open("/"); + + //Get filenames from SD Card - one after another and a maximum of maxFiles + while ((imgCount < maxFiles) && (sdFile.openNext(&dir, O_RDONLY))) + { + //Either folder for video or file with specific size for single image + if (checkFileValidity()) + { + //Extract the filename into the buffers + copyIntoBuffers(filename); + //Check if the name matches the criterion + bool check = true; + //Check if the other elements are two digits each + checkFileStructure(&check); + //Check if the filename ends with .DAT or .BMP if the file is a single image + if (loadMode == loadMode_image) + checkFileEnding(&check, filename); + //If all checks were successfull, add image to the results + if (check) + { + //If the size of images is not too high + if (imgCount < maxFiles) + { + yearStorage[imgCount] = atoi(yearBuf); + monthStorage[imgCount] = atoi(monthBuf); + dayStorage[imgCount] = atoi(dayBuf); + hourStorage[imgCount] = atoi(hourBuf); + minuteStorage[imgCount] = atoi(minuteBuf); + secondStorage[imgCount] = atoi(secondBuf); + //And raise imgCount by one + imgCount++; + } + //If there are maxFiles images or more + else + { + //Close the file + sdFile.close(); + + //Display an error message + showFullMessage((char *)"Maximum number of files exceeded"); + delay(1000); + + //And return to the main menu + mainMenu(); + return; + } + } + } + //Close the file + sdFile.close(); + } + + dir.close(); +} + +/* Choose file */ +void chooseFile(char *filename) +{ + //Look for Years +YearLabel: + //If the user wants to return to the main menu + if (yearnum == 1 || yearChoose(filename)) + { + return; + } + //Look for monthStorage +MonthLabel: + //We have twelve months + bool months[12] = {0}; + for (int i = 0; i < imgCount; i++) + { + //Add monthStorage if it belongs to the chosen yearStorage + if (yearStorage[i] == atoi(yearBuf)) + //Substract one to start array by zero + months[monthStorage[i] - 1] = true; + } + //If the user wants to go back to the years + if (monthChoose(months, filename)) + goto YearLabel; + //Look for dayStorage +DayLabel: + //We have 31 days + bool days[31] = {0}; + for (int i = 0; i < imgCount; i++) + { + //The dayStorage has to match the yearStorage and the monthStorage chosen + if ((yearStorage[i] == atoi(yearBuf)) && (monthStorage[i] == atoi(monthBuf))) + //Substract one to start array by zero + days[dayStorage[i] - 1] = true; + } + //If the user wants to go back to the months + if (dayChoose(days, filename)) + { + if (monthnum > 1) + goto MonthLabel; + else + goto YearLabel; + } + //Look for hourStorage +HourLabel: + //We have 24 hours + bool hours[24] = {0}; + for (int i = 0; i < imgCount; i++) + { + //Look for match at years, monthStorage and dayStorage + if ((yearStorage[i] == atoi(yearBuf)) && (monthStorage[i] == atoi(monthBuf)) && (dayStorage[i] == atoi(dayBuf))) + hours[hourStorage[i]] = true; + } + //If the user wants to go back to the days + if (hourChoose(hours, filename)) + { + if (daynum > 1) + goto DayLabel; + else if (monthnum > 1) + goto MonthLabel; + else + goto YearLabel; + } + //Look for minuteStorage +MinuteLabel: + //We have 60 minutes + bool minutes[60] = {0}; + for (int i = 0; i < imgCount; i++) + { + //Watch for yearStorage, monthStorage, dayStorage and hourStorage + if ((yearStorage[i] == atoi(yearBuf)) && (monthStorage[i] == atoi(monthBuf)) && (dayStorage[i] == atoi(dayBuf)) && (hourStorage[i] == atoi(hourBuf))) + minutes[minuteStorage[i]] = true; + } + //If the user wants to go back to the hours + if (minuteChoose(minutes, filename)) + { + if (hournum > 1) + goto HourLabel; + else if (daynum > 1) + goto DayLabel; + else if (monthnum > 1) + goto MonthLabel; + else + goto YearLabel; + } + //Look for secondStorage + //We have 60 seconds + bool seconds[60] = {0}; + for (int i = 0; i < imgCount; i++) + { + //Watch for yearStorage, monthStorage, dayStorage, hourStorage and minuteStorage + if ((yearStorage[i] == atoi(yearBuf)) && (monthStorage[i] == atoi(monthBuf)) && (dayStorage[i] == atoi(dayBuf)) && (hourStorage[i] == atoi(hourBuf)) && (minuteStorage[i] == atoi(minuteBuf))) + seconds[secondStorage[i]] = true; + } + //If the user wants to go back to the minutes + if (secondChoose(seconds, filename)) + { + if (minutenum > 1) + goto MinuteLabel; + else if (hournum > 1) + goto HourLabel; + else if (daynum > 1) + goto DayLabel; + else if (monthnum > 1) + goto MonthLabel; + else + goto YearLabel; + } + + //For an image, add the ending + if (loadMode == loadMode_image) + { + //Try to add the DAT file + strcpy(&filename[14], ".DAT"); + //Check if it is a DAT file + sdFile.open(filename, O_READ); + if (sdFile.isFile()) + { + sdFile.close(); + return; + } + //If not, use bitmap + strcpy(&filename[14], ".BMP"); + //Close the file + sdFile.close(); + } + + //Video ending + else + //Delete the ending for a video + filename[14] = '\0'; +} + +/* Delete image / video function */ +bool loadDelete(char *filename, int *pos) +{ + //Image + if (isImage(filename)) + deleteImage(filename); + //Video + else + deleteVideo(filename); + //Clear all previous data + clearData(); + //Search files + searchFiles(); + //If there are no files left, return + if (imgCount == 0) + { + if (loadMode == loadMode_image) + showFullMessage((char *)"No images found"); + else + showFullMessage((char *)"No videos found"); + delay(1000); + return false; + } + //Decrease by one if the last image/video was deleted + if (*pos > (imgCount - 1)) + *pos = imgCount - 1; + //Find the name of the next file + findFile(filename, false, true, pos); + return true; +} + +/* Find image / video function */ +void loadFind(char *filename, int *pos) +{ + //If there is only one image + if (imgCount == 1) + { + if (loadMode == loadMode_image) + showFullMessage((char *)"Only one image available"); + else + showFullMessage((char *)"Only one video available"); + delay(1000); + return; + } + //Clear all previous data + clearData(); + //Search files + searchFiles(); + //Fill screen + display_fillScr(200, 200, 200); + //Let the user choose a new file + chooseFile(filename); + //Copy the filename for a compare value + char compare[20]; + strncpy(compare, filename, 20); + //Find the new file position + findFile(filename, false, true, pos, compare); +} + +/* Alloc space for the different arrays*/ +void loadAlloc() +{ + yearStorage = (uint16_t *)calloc(maxFiles, sizeof(uint16_t)); + monthStorage = (byte *)calloc(maxFiles, sizeof(byte)); + dayStorage = (byte *)calloc(maxFiles, sizeof(byte)); + hourStorage = (byte *)calloc(maxFiles, sizeof(byte)); + minuteStorage = (byte *)calloc(maxFiles, sizeof(byte)); + secondStorage = (byte *)calloc(maxFiles, sizeof(byte)); +} + +/* Change settings for load menu */ +void loadSettings() +{ + //Do not show additional information that are not required + batteryEnabled = false; + dateEnabled = false; + timeEnabled = false; + storageEnabled = false; + minMaxPoints = minMaxPoints_disabled; + hotColdMode = hotColdMode_disabled; +} + +/* De-Alloc space for the different arrays*/ +void loadDeAlloc() +{ + free(yearStorage); + free(monthStorage); + free(dayStorage); + free(hourStorage); + free(minuteStorage); + free(secondStorage); +} + +/* Interrupt handler for the load touch menu */ +void loadTouchIRQ() +{ + //Get touch coordinates + TS_Point p = touch_getPoint(); + uint16_t x = p.x; + uint16_t y = p.y; + + //Find + if ((x >= 10) && (x <= 140) && (y >= 10) && (y <= 80)) + loadTouch = loadTouch_find; + + //Exit + else if ((x >= 180) && (x <= 310) && (y >= 160) && (y <= 230)) + loadTouch = loadTouch_exit; + + //Previous + else if ((x >= 10) && (x <= 140) && (y >= 90) && (y <= 150) && (imgCount != 1)) + loadTouch = loadTouch_previous; + + //Next + else if ((x >= 180) && (x <= 310) && (y >= 90) && (y <= 150) && (imgCount != 1)) + loadTouch = loadTouch_next; + + //Delete + else if ((x >= 180) && (x <= 310) && (y >= 10) && (y <= 80)) + loadTouch = loadTouch_delete; + + //Convert + else if ((x >= 10) && (x <= 140) && (y >= 160) && (y <= 230)) + loadTouch = loadTouch_convert; + + //Middle + else if ((x > 140) && (x < 180) && (y > 80) && (y < 160)) + loadTouch = loadTouch_middle; +} + +/* Main entry point for loading images/videos*/ +void loadFiles() +{ +redraw: + //Title & Background + mainMenuBackground(); + mainMenuTitle((char *)"Load Menu"); + //Draw the buttons + buttons_deleteAllButtons(); + buttons_setTextFont(bigFont); + buttons_addButton(15, 47, 140, 120, (char *)"Images"); + buttons_addButton(165, 47, 140, 120, (char *)"Videos"); + buttons_addButton(15, 188, 140, 40, (char *)"Back"); + buttons_drawButtons(); + //Touch handler + while (true) + { + //If touch pressed + if (touch_touched() == true) + { + int pressedButton = buttons_checkButtons(true); + //IMAGES + if (pressedButton == 0) + { + loadMode = loadMode_image; + break; + } + //VIDEOS + if (pressedButton == 1) + { + loadMode = loadMode_video; + break; + } + //BACK + if (pressedButton == 2) + return; + } + } + + //Store the filename + char filename[20]; + + //Save old settings + uint16_t old_minValue = minValue; + uint16_t old_maxValue = maxValue; + byte old_leptonVersion = leptonVersion; + + //Load message + showFullMessage((char *)"Please wait..", true); + //Alloc space + loadAlloc(); + //Clear all previous data + clearData(); + //Search files + searchFiles(); + + //If there are no images or videos, return + if (imgCount == 0) + { + //Show message + if (loadMode == loadMode_image) + showFullMessage((char *)"No images found", true); + else + showFullMessage((char *)"No videos found", true); + delay(1000); + //Deallocate space + loadDeAlloc(); + //Redraw menu + goto redraw; + } + + //Change settings + loadSettings(); + + //Open the latest file + int pos = imgCount - 1; + findFile(filename, false, true, &pos); + bool exit = false; + + //New touch interrupt + detachInterrupt(pin_touch_irq); + + //Main loop + while (true) + { + + //Disable decision marker + loadTouch = loadTouch_none; + + //Load image + if (isImage(filename) && (loadMode == loadMode_image)) + openImage(filename, imgCount); + + //Play video + else + playVideo(filename, imgCount); + + //Touch actions + switch (loadTouch) + { + + //Find + case loadTouch_find: + loadFind(filename, &pos); + break; + + //Delete + case loadTouch_delete: + if (!loadDelete(filename, &pos)) + exit = true; + break; + + //Previous + case loadTouch_previous: + showFullMessage((char *)"Loading.."); + if (pos == imgCount - 1) + pos = 0; + else + pos++; + findFile(filename, false, true, &pos); + break; + + //Next + case loadTouch_next: + showFullMessage((char *)"Loading.."); + if (pos == 0) + pos = imgCount - 1; + else + pos--; + findFile(filename, false, true, &pos); + break; + + //Exit + case loadTouch_exit: + exit = true; + break; + + //Convert + case loadTouch_convert: + //Image + if (isImage(filename) && (loadMode == loadMode_image)) + convertImage(filename); + //Video + else + convertVideo(filename); + break; + } + + //Wait for touch release + if (touch_capacitive) + while (touch_touched()) + ; + else + while (!digitalRead(pin_touch_irq)) + ; + + //Leave + if (exit) + break; + } + //Show message + drawMainMenuBorder(); + showFullMessage((char *)"Please wait..", true); + + //Deallocate space + loadDeAlloc(); + + //Restore old settings from variables + minValue = old_minValue; + maxValue = old_maxValue; + leptonVersion = old_leptonVersion; + + //Restore the rest from EEPROM + readEEPROM(); + + //Refresh SD space + refreshFreeSpace(); + + //Restore old touch handler + attachInterrupt(pin_touch_irq, touchIRQ, FALLING); +} diff --git a/Firmware_V3/src/thermal/save.cpp b/Firmware_V3/src/thermal/save.cpp new file mode 100644 index 0000000..efd150f --- /dev/null +++ b/Firmware_V3/src/thermal/save.cpp @@ -0,0 +1,414 @@ +/* +* +* SAVE - Save images and videos to the internal storage +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################### STATIC DATA DECLARATIONS ##########################*/ + +//160 x 120 bitmap header +static const char bmp_header_small[66] = {0x42, 0x4D, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, + 0x00, 0xA0, 0x00, 0x00, 0x00, 0x88, 0xFF, 0xFF, 0xFF, 0x01, + 0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, + 0x00, 0xE0, 0x07, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00}; + +//640 x 480 bitmap header +static const char bmp_header_large[66] = {0x42, 0x4D, 0x36, 0x60, 0x09, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, + 0x80, 0x02, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x01, 0x00, 0x10, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x60, 0x09, 0x00, 0xC4, 0x0E, + 0x00, 0x00, 0xC4, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, + 0x1F, 0x00, 0x00, 0x00}; + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Creates a filename from the current time & date */ +void createSDName(char *filename, boolean folder) +{ + char buffer[5]; + //Year + itoa(year(), buffer, 10); + strncpy(&filename[0], buffer, 4); + //Month + itoa(month(), buffer, 10); + if (month() < 10) + { + filename[4] = '0'; + strncpy(&filename[5], buffer, 1); + } + else + { + strncpy(&filename[4], buffer, 2); + } + //Day + itoa(day(), buffer, 10); + if (day() < 10) + { + filename[6] = '0'; + strncpy(&filename[7], buffer, 1); + } + else + { + strncpy(&filename[6], buffer, 2); + } + //Hour + itoa(hour(), buffer, 10); + if (hour() < 10) + { + filename[8] = '0'; + strncpy(&filename[9], buffer, 1); + } + else + { + strncpy(&filename[8], buffer, 2); + } + //Minute + itoa(minute(), buffer, 10); + if (minute() < 10) + { + filename[10] = '0'; + strncpy(&filename[11], buffer, 1); + } + else + { + strncpy(&filename[10], buffer, 2); + } + //Second + itoa(second(), buffer, 10); + if (second() < 10) + { + filename[12] = '0'; + if (!folder) + strncpy(&filename[13], buffer, 1); + else + strcpy(&filename[13], buffer); + } + else + { + if (!folder) + strncpy(&filename[12], buffer, 2); + else + strcpy(&filename[12], buffer); + } +} + +/* Creates a bmp file for the thermal image */ +void createBMPFile(char *filename) +{ + //File extension and open + strcpy(&filename[14], ".BMP"); + sdFile.open(filename, O_RDWR | O_CREAT | O_AT_END); + //Write the BMP header + sdFile.write((uint8_t *)bmp_header_large, 66); +} + +/* Start the image save procedure */ +void imgSaveStart() +{ + //Check if there is at least 1MB of space left + if (getSDSpace() < 1000) + { + //Show message + showFullMessage((char *)"The SD card is full"); + delay(1000); + + //Disable and return + imgSave = imgSave_disabled; + return; + } + + //Build save filename from the current time & date + createSDName(saveFilename); + + //Set text color + changeTextColor(); + //Set background transparent + display_setBackColor(VGA_TRANSPARENT); + //Display to screen in big font + display_setFont(bigFont); + + if (spotEnabled) + display_print((char *)"SAVING", CENTER, 70); + else + display_print((char *)"SAVING", CENTER, 110); + + imgSave = imgSave_create; + display_setFont(smallFont); +} + +/* Creates the filename for the video frames */ +void frameFilename(char *filename, uint16_t count) +{ + filename[0] = '0' + count / 10000 % 10; + filename[1] = '0' + count / 1000 % 10; + filename[2] = '0' + count / 100 % 10; + filename[3] = '0' + count / 10 % 10; + filename[4] = '0' + count % 10; +} + +/* Save video frame to image file */ +void saveVideoFrame(char *filename) +{ + + + // Open the file for writing + sdFile.open(filename, O_RDWR | O_CREAT | O_AT_END); + + //Write 160x120 BMP header + sdFile.write((uint8_t *)bmp_header_small, 66); + + //Write 160x120 data + for (int i = 0; i < 19200; i++) + { + sdFile.write(smallBuffer[i] & 0x00FF); + sdFile.write((smallBuffer[i] & 0xFF00) >> 8); + } + + sdFile.close(); +} + +/* Proccess video frames */ +void processVideoFrames(int framesCaptured, char *dirname) +{ + char buffer[30]; + char filename[] = "00000.DAT"; + int framesConverted = 0; + + //Display title + display_fillScr(200, 200, 200); + display_setBackColor(200, 200, 200); + display_setFont(bigFont); + display_setColor(VGA_BLUE); + display_print((char *)"Video conversion", CENTER, 30); + + //Display info + display_setFont(smallFont); + display_setColor(VGA_BLACK); + display_print((char *)"Converts all .DAT to .BMP frames", CENTER, 80); + display_print((char *)"Press button to abort the process", CENTER, 120); + + //Display content + sprintf(buffer, "Frames converted: %5d / %5d", framesConverted, framesCaptured); + display_print(buffer, CENTER, 160); + sprintf(buffer, "Folder name: %s", dirname); + display_print(buffer, CENTER, 200); + + //Switch to processing mode + videoSave = videoSave_processing; + + //Go through all the frames in the folder + for (framesConverted = 0; framesConverted < framesCaptured; framesConverted++) + { + //Button pressed, exit + if (videoSave != videoSave_processing) + break; + + //Get filename + frameFilename(filename, framesConverted); + strcpy(&filename[5], ".DAT"); + + //Load Raw data + loadRawData(filename); + + //Apply low-pass filter + if (filterType == filterType_box) + boxFilter(); + else if (filterType == filterType_gaussian) + gaussianFilter(); + + //Find min / max position + if (minMaxPoints != minMaxPoints_disabled) + refreshMinMax(); + + //Convert lepton data to RGB565 colors + convertColors(true); + + //Display additional information + displayInfos(); + + //Save frame to image file + strcpy(&filename[5], ".BMP"); + saveVideoFrame(filename); + + //Font color + display_setBackColor(200, 200, 200); + display_setFont(smallFont); + display_setColor(VGA_BLACK); + + //Update screen content + sprintf(buffer, "Frames converted: %5d / %5d", framesConverted + 1, framesCaptured); + display_print(buffer, CENTER, 160); + } + + //All images converted! + showFullMessage((char *)"Video converted"); + delay(1000); +} + +/* Saves raw data for an image or an video frame */ +void saveRawData(bool isImage, char *name, uint16_t framesCaptured) +{ + uint16_t result; + + //Create filename for image + if (isImage) + { + strcpy(&name[14], ".DAT"); + sdFile.open(name, O_RDWR | O_CREAT | O_AT_END); + } + + //Create filename for video frame + else + { + char filename[] = "00000.DAT"; + frameFilename(filename, framesCaptured); + sdFile.open(filename, O_RDWR | O_CREAT | O_AT_END); + } + + //For the Lepton2.5 sensor, write 4800 raw values + if (leptonVersion == leptonVersion_2_5_shutter) + { + for (int line = 0; line < 60; line++) + { + for (int column = 0; column < 80; column++) + { + result = smallBuffer[(line * 2 * 160) + (column * 2)]; + sdFile.write((result & 0xFF00) >> 8); + sdFile.write(result & 0x00FF); + } + } + } + + //For the Lepton3.5 sensor, write 19200 raw values + else + { + for (int i = 0; i < 19200; i++) + { + sdFile.write((smallBuffer[i] & 0xFF00) >> 8); + sdFile.write(smallBuffer[i] & 0x00FF); + } + } + + //Write min and max + sdFile.write((minValue & 0xFF00) >> 8); + sdFile.write(minValue & 0x00FF); + sdFile.write((maxValue & 0xFF00) >> 8); + sdFile.write(maxValue & 0x00FF); + + //Write the object temp + uint8_t farray[4]; + floatToBytes(farray, spotTemp); + for (int i = 0; i < 4; i++) + sdFile.write(farray[i]); + + //Write the color scheme + sdFile.write(colorScheme); + //Write the temperature format + sdFile.write(tempFormat); + //Write the show spot attribute + sdFile.write(spotEnabled); + //Write the show colorbar attribute + sdFile.write(colorbarEnabled); + //Write the show hottest / coldest attribute + sdFile.write(minMaxPoints); + + //Write calibration offset + float calOffset = -273.15; + floatToBytes(farray, (float)calOffset); + for (int i = 0; i < 4; i++) + sdFile.write(farray[i]); + //Write calibration slope + floatToBytes(farray, (float)leptonCalSlope); + for (int i = 0; i < 4; i++) + sdFile.write(farray[i]); + + //Write temperature points + for (byte i = 0; i < 96; i++) + { + //Write index + sdFile.write((tempPoints[i][0] & 0xFF00) >> 8); + sdFile.write(tempPoints[i][0] & 0x00FF); + //Write value + sdFile.write((tempPoints[i][1] & 0xFF00) >> 8); + sdFile.write(tempPoints[i][1] & 0x00FF); + } + + sdFile.close(); +} + +/* End the image save procedure */ +void imgSaveEnd() +{ + //Save Bitmap image if activated + if (convertEnabled) + saveBuffer(saveFilename); + + //Refresh free space + refreshFreeSpace(); + + //Disable image save marker + imgSave = imgSave_disabled; +} + +/* Saves the content of the screen buffer to the sd card */ +void saveBuffer(char *filename) +{ + unsigned short pixel; + + //Create file + createBMPFile(filename); + + //Allocate space for sd buffer + uint8_t *sdBuffer = (uint8_t *)calloc(1280, sizeof(uint8_t)); + + //Save 640x480 pixels + for (int16_t y = 239; y >= 0; y--) + { + //Write them into the sd buffer + for (uint16_t x = 0; x < 320; x++) + { + pixel = bigBuffer[(y * 320) + x]; + sdBuffer[x * 4] = pixel & 0x00FF; + sdBuffer[(x * 4) + 1] = (pixel & 0xFF00) >> 8; + sdBuffer[(x * 4) + 2] = pixel & 0x00FF; + sdBuffer[(x * 4) + 3] = (pixel & 0xFF00) >> 8; + } + //Write them to the sd card with 640x480 resolution + sdFile.write(sdBuffer, 1280); + sdFile.write(sdBuffer, 1280); + } + + //De-allocate space + free(sdBuffer); + sdFile.close(); +} diff --git a/Firmware_V3/src/thermal/temperature.cpp b/Firmware_V3/src/thermal/temperature.cpp new file mode 100644 index 0000000..a2be13f --- /dev/null +++ b/Firmware_V3/src/thermal/temperature.cpp @@ -0,0 +1,71 @@ +/* + * + * TEMPERATURE - Functions to convert Lepton raw values to absolute temperatures and back + * + * DIY-Thermocam Firmware + * + * GNU General Public License v3.0 + * + * Copyright by Max Ritter + * + * http://www.diy-thermocam.net + * https://github.com/maxritter/DIY-Thermocam + * + */ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Converts a given Temperature in Celcius to Fahrenheit */ +float celciusToFahrenheit(float Tc) { + float Tf = ((float) 9.0 / 5.0) * Tc + 32.0; + return (Tf); +} + +/* Converts a given temperature in Fahrenheit to Celcius */ +float fahrenheitToCelcius(float Tf) { + float Tc = (Tf - 32.0) * ((float) 5.0 / 9.0); + return Tc; +} + +/* Function to calculate temperature out of Lepton value */ +float rawToTemp(uint16_t rawValue) { + //Calculate the temperature in Celcius out of the coefficients + float temp = (leptonCalSlope * rawValue) - 273.15; + + //Convert to Fahrenheit if needed + if (tempFormat == tempFormat_fahrenheit) + temp = celciusToFahrenheit(temp); + + return temp; +} + +/* Calculate the lepton value out of an absolute temperature */ +uint16_t tempToRaw(float temp) { + //Convert to Celcius if needed + if (tempFormat == tempFormat_fahrenheit) + temp = fahrenheitToCelcius(temp); + + //Calcualte the raw value + uint16_t rawValue = (temp + 273.15) / leptonCalSlope; + return rawValue; +} + +/* Calculates the average of the 196 (14x14) pixels in the middle */ +uint16_t calcAverage() { + int sum = 0; + uint16_t val; + for (byte vert = 52; vert < 66; vert++) { + for (byte horiz = 72; horiz < 86; horiz++) { + val = smallBuffer[(vert * 160) + horiz]; + sum += val; + } + } + uint16_t avg = (uint16_t)(sum / 196.0); + return avg; +} \ No newline at end of file diff --git a/Firmware_V3/src/thermal/thermal.cpp b/Firmware_V3/src/thermal/thermal.cpp new file mode 100644 index 0000000..1974f8d --- /dev/null +++ b/Firmware_V3/src/thermal/thermal.cpp @@ -0,0 +1,610 @@ +/* +* +* THERMAL - Main functions in the live mode +* +* DIY-Thermocam Firmware +* +* GNU General Public License v3.0 +* +* Copyright by Max Ritter +* +* http://www.diy-thermocam.net +* https://github.com/maxritter/DIY-Thermocam +* +*/ + +/*################################# INCLUDES ##################################*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*######################## PUBLIC FUNCTION BODIES #############################*/ + +/* Touch interrupt handler */ +void touchIRQ() +{ + //When not in menu, video save, image save, serial mode or lock/release limits + if ((!showMenu) && (!videoSave) && (!longTouch) && (!imgSave) && (!serialMode)) + { + //Count the time to choose selection + long startTime = millis(); + delay(10); + long endTime = millis() - startTime; + + //Wait for touch release, but not longer than a second + if (touch_capacitive) + { + while ((touch_touched()) && (endTime <= 1000)) + endTime = millis() - startTime; + } + else + { + while ((!digitalRead(pin_touch_irq)) && (endTime <= 1000)) + endTime = millis() - startTime; + } + endTime = millis() - startTime; + + //Short press - show menu + if (endTime < 1000) + { + if (showMenu == showMenu_disabled) + showMenu = showMenu_desired; + } + else{ + longTouch = true; + } + + } +} + +/* Button interrupt handler */ +void buttonIRQ() +{ + //When not in menu, video save, image save, serial mode or lock/release limits + if ((!showMenu) && (!videoSave) && (!longTouch) && (!imgSave) && (!serialMode)) + { + //Count the time to choose selection + long startTime = millis(); + delay(10); + long endTime = millis() - startTime; + + //As long as the button is pressed + while (extButtonPressed() && (endTime <= 1000)) + endTime = millis() - startTime; + + //Short press - save image to SD Card + if (endTime < 1000) + //Prepare image save but let screen refresh first + imgSave = imgSave_set; + + //Enable video mode + else + videoSave = videoSave_menu; + } + + //When in video save recording mode, go to processing + if (videoSave == videoSave_recording) + { + videoSave = videoSave_processing; + while (extButtonPressed()) + ; + } + + //When in video save processing, end it + else if (videoSave == videoSave_processing) + { + videoSave = videoSave_menu; + while (extButtonPressed()) + ; + } +} + +/* Lepton interrupt handler */ +void leptonIRQ() +{ +} + +/* Handler for a long touch press */ +void longTouchHandler() +{ + //When in auto mode, toggle between locked & unlocked + if (autoMode) + { + //Unlock limits and enable auto FFC + if (limitsLocked) + { + showTransMessage((char *)"Limits unlocked"); + limitsLocked = false; + lepton_ffcMode(true); + } + + //Lock limits and disable auto FFC + else + { + lepton_ffcMode(false); + showTransMessage((char *)"Limits locked"); + limitsLocked = true; + } + } + + //When in manual mode, toggle between presets + else + { + //Read preset from EEPROM + byte minMaxPreset = EEPROM.read(eeprom_minMaxPreset); + + //When in temporary limits + if (minMaxPreset == minMax_temporary) + { + if (EEPROM.read(eeprom_minMax1Set) == eeprom_setValue) + { + showTransMessage((char *)"Switch to Preset 1"); + minMaxPreset = minMax_preset1; + } + else if (EEPROM.read(eeprom_minMax2Set) == eeprom_setValue) + { + showTransMessage((char *)"Switch to Preset 2"); + minMaxPreset = minMax_preset2; + } + else if (EEPROM.read(eeprom_minMax3Set) == eeprom_setValue) + { + showTransMessage((char *)"Switch to Preset 3"); + minMaxPreset = minMax_preset3; + } + else + showTransMessage((char *)"No other Preset"); + } + + //When in preset 1 + else if (minMaxPreset == minMax_preset1) + { + if (EEPROM.read(eeprom_minMax2Set) == eeprom_setValue) + { + showTransMessage((char *)"Switch to Preset 2"); + minMaxPreset = minMax_preset2; + } + else if (EEPROM.read(eeprom_minMax3Set) == eeprom_setValue) + { + showTransMessage((char *)"Switch to Preset 3"); + minMaxPreset = minMax_preset3; + } + else + showTransMessage((char *)"No other Preset"); + } + + //When in preset 2 + else if (minMaxPreset == minMax_preset2) + { + if (EEPROM.read(eeprom_minMax3Set) == eeprom_setValue) + { + showTransMessage((char *)"Switch to Preset 3"); + minMaxPreset = minMax_preset3; + } + else if (EEPROM.read(eeprom_minMax1Set) == eeprom_setValue) + { + showTransMessage((char *)"Switch to Preset 1"); + minMaxPreset = minMax_preset1; + } + else + showTransMessage((char *)"No other Preset"); + } + + //When in preset 3 + else if (minMaxPreset == minMax_preset3) + { + if (EEPROM.read(eeprom_minMax1Set) == eeprom_setValue) + { + showTransMessage((char *)"Switch to Preset 1"); + minMaxPreset = minMax_preset1; + } + else if (EEPROM.read(eeprom_minMax2Set) == eeprom_setValue) + { + showTransMessage((char *)"Switch to Preset 2"); + minMaxPreset = minMax_preset2; + } + else + showTransMessage((char *)"No other Preset"); + } + + //When not using temporary preset + if (minMaxPreset != minMax_temporary) + { + //Save new preset to EEPROM + EEPROM.write(eeprom_minMaxPreset, minMaxPreset); + + //Load the new limits + readTempLimits(); + } + } + + //Disable lock limits menu + longTouch = false; +} + +/* Show the color bar on screen */ +void showColorBar() +{ + //Help variables + char buffer[6]; + byte red, green, blue, fac; + byte count = 0; + + //Calculate color bar height corresponding on color elements + byte height = 240 - ((240 - (colorElements / 2)) / 2); + + //Calculate color level for hot and cold + float colorLevel = 0; + if (hotColdMode != hotColdMode_disabled) + colorLevel = (tempToRaw(hotColdLevel) * 1.0 - minValue) / (maxValue * 1.0 - minValue); + + //Display color bar + for (int i = 0; i < (colorElements - 1); i++) + { + fac = 2; + + if ((i % fac) == 0) + { + //Hot + if ((hotColdMode == hotColdMode_hot) && (i >= (colorLevel * colorElements))) + getHotColdColors(&red, &green, &blue); + + //Cold + else if ((hotColdMode == hotColdMode_cold) && (i <= (colorLevel * colorElements))) + getHotColdColors(&red, &green, &blue); + + //Other + else + { + red = colorMap[i * 3]; + green = colorMap[(i * 3) + 1]; + blue = colorMap[(i * 3) + 2]; + } + + //Draw the line + display_setColor(red, green, blue); + display_drawLine(298, height - count, 314, height - count); + + //Raise counter + count++; + } + } + + //Set text color + changeTextColor(); + + //Calculate min and max temp in celcius/fahrenheit + float min = rawToTemp(minValue); + float max = rawToTemp(maxValue); + + //Calculate step + float step = (max - min) / 3.0; + fac = 1; + + //Draw min temp + sprintf(buffer, "%d", (int)round(min)); + display_print(buffer, 270, (height * fac) - 5); + + //Draw temperatures after min before max + for (int i = 2; i >= 1; i--) + { + float temp = min + (i * step); + sprintf(buffer, "%d", (int)round(temp)); + display_print(buffer, 270, (height * fac) - 5 - (i * (colorElements / 6))); + } + + //Draw max temp + sprintf(buffer, "%d", (int)round(max)); + display_print(buffer, 270, (height * fac) - 5 - (3 * (colorElements / 6))); +} + +/* Change the display options */ +void changeDisplayOptions(byte *pos) +{ + switch (*pos) + { + //Battery + case 0: + batteryEnabled = !batteryEnabled; + EEPROM.write(eeprom_batteryEnabled, batteryEnabled); + break; + + //Time + case 1: + timeEnabled = !timeEnabled; + EEPROM.write(eeprom_timeEnabled, timeEnabled); + break; + + //Date + case 2: + dateEnabled = !dateEnabled; + EEPROM.write(eeprom_dateEnabled, dateEnabled); + break; + + //Spot + case 3: + spotEnabled = !spotEnabled; + EEPROM.write(eeprom_spotEnabled, spotEnabled); + break; + + //Colorbar + case 4: + colorbarEnabled = !colorbarEnabled; + EEPROM.write(eeprom_colorbarEnabled, colorbarEnabled); + break; + + //Storage + case 5: + storageEnabled = !storageEnabled; + EEPROM.write(eeprom_storageEnabled, storageEnabled); + break; + + //Filter + case 6: + if (filterType == filterType_box) + filterType = filterType_gaussian; + else if (filterType == filterType_gaussian) + filterType = filterType_none; + else + filterType = filterType_box; + EEPROM.write(eeprom_filterType, filterType); + break; + + //Text color + case 7: + if (textColor == textColor_white) + textColor = textColor_black; + else if (textColor == textColor_black) + textColor = textColor_red; + else if (textColor == textColor_red) + textColor = textColor_green; + else if (textColor == textColor_green) + textColor = textColor_blue; + else + textColor = textColor_white; + EEPROM.write(eeprom_textColor, textColor); + break; + + //Hottest or coldest display + case 8: + if (minMaxPoints == minMaxPoints_disabled) + minMaxPoints = minMaxPoints_min; + else if (minMaxPoints == minMaxPoints_min) + minMaxPoints = minMaxPoints_max; + else if (minMaxPoints == minMaxPoints_max) + minMaxPoints = minMaxPoints_both; + else + minMaxPoints = minMaxPoints_disabled; + EEPROM.write(eeprom_minMaxPoints, minMaxPoints); + break; + } +} + +/* Map to the right color scheme */ +void selectColorScheme() +{ + //Select the right color scheme + switch (colorScheme) + { + //Arctic + case colorScheme_arctic: + colorMap = colorMap_arctic; + colorElements = 240; + break; + + //Black-Hot + case colorScheme_blackHot: + colorMap = colorMap_blackHot; + colorElements = 224; + break; + + //Blue-Red + case colorScheme_blueRed: + colorMap = colorMap_blueRed; + colorElements = 192; + break; + + //Coldest + case colorScheme_coldest: + colorMap = colorMap_coldest; + colorElements = 224; + break; + + //Contrast + case colorScheme_contrast: + colorMap = colorMap_contrast; + colorElements = 224; + break; + + //Double-Rainbow + case colorScheme_doubleRainbow: + colorMap = colorMap_doubleRainbow; + colorElements = 256; + break; + + //Gray-Red + case colorScheme_grayRed: + colorMap = colorMap_grayRed; + colorElements = 224; + break; + + //Glowbow + case colorScheme_glowBow: + colorMap = colorMap_glowBow; + colorElements = 224; + break; + + //Grayscale + case colorScheme_grayscale: + colorMap = colorMap_grayscale; + colorElements = 256; + break; + + //Hottest + case colorScheme_hottest: + colorMap = colorMap_hottest; + colorElements = 224; + break; + + //Ironblack + case colorScheme_ironblack: + colorMap = colorMap_ironblack; + colorElements = 256; + break; + + //Lava + case colorScheme_lava: + colorMap = colorMap_lava; + colorElements = 240; + break; + + //Medical + case colorScheme_medical: + colorMap = colorMap_medical; + colorElements = 224; + break; + + //Rainbow + case colorScheme_rainbow: + colorMap = colorMap_rainbow; + colorElements = 256; + break; + + //Wheel 1 + case colorScheme_wheel1: + colorMap = colorMap_wheel1; + colorElements = 256; + break; + + //Wheel 2 + case colorScheme_wheel2: + colorMap = colorMap_wheel2; + colorElements = 256; + break; + + //Wheel 3 + case colorScheme_wheel3: + colorMap = colorMap_wheel3; + colorElements = 256; + break; + + //White-Hot + case colorScheme_whiteHot: + colorMap = colorMap_whiteHot; + colorElements = 224; + break; + + //Yellow + case colorScheme_yellow: + colorMap = colorMap_yellow; + colorElements = 224; + break; + } +} + +/* Change the color scheme for the thermal image */ +void changeColorScheme(byte *pos) +{ + //Align position to color scheme + colorScheme = *pos; + //Map to the right color scheme + selectColorScheme(); + //Save to EEPROM + EEPROM.write(eeprom_colorScheme, colorScheme); +} + +/* Show the thermal/visual/combined image on the screen */ +void showImage() +{ + //Draw thermal image on screen if created previously and not in menu nor in video save + if ((!imgSave) && (!showMenu) && (!videoSave)) + displayBuffer(); + + //If the image has been created, set to save + if (imgSave == imgSave_create) + imgSave = imgSave_save; +} + +/* Init procedure for the live mode */ +void liveModeInit() +{ + selectColorScheme(); + + attachInterrupt(pin_button, buttonIRQ, RISING); + attachInterrupt(pin_touch_irq, touchIRQ, FALLING); + + showMenu = showMenu_disabled; + usbConnected = true; + longTouch = false; + + clearTempPoints(); + lepton_startFrame(); +} + +/* Main entry point for the live mode */ +void liveMode() +{ + //Init + liveModeInit(); + + //Main Loop + while (true) + { + //Check for serial connection + checkSerial(); + + //Check for screen sleep + screenOffCheck(); + + //If touch IRQ has been triggered, open menu + if (showMenu == showMenu_desired) + mainMenu(); + + //Start the image save procedure + if (imgSave == imgSave_set) + imgSaveStart(); + + //Create thermal image + createThermalImg(); + + //Display additional information + displayInfos(); + + //Show the content on the screen + showImage(); + + //Save the converted / visual image + if (imgSave == imgSave_save) + imgSaveEnd(); + + //Go into video mode + if (videoSave == videoSave_menu) + videoMode(); + + //Long touch handler + if (longTouch) + longTouchHandler(); + + //Enter mass storage on USB connect + checkMassStorage(); + + //Ready for next frame from Lepton + lepton_startFrame(); + } +} diff --git a/README.MD b/README.MD index c1f7df6..34b3abb 100644 --- a/README.MD +++ b/README.MD @@ -32,24 +32,8 @@ The **enclosure** has been **laser cutted** from 3mm acryl plastic and can be as The **self-assembly KIT** contains **all required components apart from the FLIR Lepton 2.5/3.5 and the Lepton Breakout Board V2** (need to be bought separately from either [Digikey.com](https://www.digikey.com/) or [Groupgets.com](https://store.groupgets.com/)). Please **subscribe to the list below** to **get notified** when the **KIT goes on sale**: -
-
-
-

Subscribe

-
- - -
-
- - -
- -
-
-
-
+#### **[-> Subscribe Now <-](https://mailchi.mp/3e0df5458871/diy-thermocam-v3-kit)** +