Repository: maxritter/diy-thermocam Branch: master Commit: 5e2e0c2ad745 Files: 250 Total size: 2.9 MB Directory structure: gitextract_mg_ibzep/ ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.MD ├── enclosure/ │ ├── 3.0/ │ │ ├── enclosure.skb │ │ └── enclosure.skp │ └── README.MD ├── firmware/ │ ├── 3.0/ │ │ ├── .gitignore │ │ ├── include/ │ │ │ ├── battery.h │ │ │ ├── bitmaps.h │ │ │ ├── buttons.h │ │ │ ├── colorschemes.h │ │ │ ├── connection.h │ │ │ ├── create.h │ │ │ ├── display.h │ │ │ ├── firststart.h │ │ │ ├── fonts.h │ │ │ ├── ft6206_touchscreen.h │ │ │ ├── globaldefines.h │ │ │ ├── globalvariables.h │ │ │ ├── gui.h │ │ │ ├── hardware.h │ │ │ ├── lepton.h │ │ │ ├── livemode.h │ │ │ ├── load.h │ │ │ ├── loadmenu.h │ │ │ ├── mainmenu.h │ │ │ ├── massstorage.h │ │ │ ├── save.h │ │ │ ├── sdcard.h │ │ │ ├── settingsmenu.h │ │ │ ├── temperature.h │ │ │ ├── thermal.h │ │ │ ├── touchscreen.h │ │ │ ├── videomenu.h │ │ │ └── xpt2046_touchscreen.h │ │ ├── lib/ │ │ │ ├── ADC/ │ │ │ │ ├── ADC.cpp │ │ │ │ ├── ADC.h │ │ │ │ ├── ADC_Module.cpp │ │ │ │ ├── ADC_Module.h │ │ │ │ ├── ADC_util.h │ │ │ │ ├── AnalogBufferDMA.cpp │ │ │ │ ├── AnalogBufferDMA.h │ │ │ │ ├── VREF.h │ │ │ │ ├── atomic.h │ │ │ │ └── settings_defines.h │ │ │ ├── Bounce/ │ │ │ │ ├── Bounce.cpp │ │ │ │ └── Bounce.h │ │ │ ├── EEPROM/ │ │ │ │ ├── EEPROM.cpp │ │ │ │ └── EEPROM.h │ │ │ ├── LittleFS/ │ │ │ │ ├── LittleFS.cpp │ │ │ │ ├── LittleFS.h │ │ │ │ ├── LittleFS_NAND.cpp │ │ │ │ └── littlefs/ │ │ │ │ ├── DESIGN.md │ │ │ │ ├── LICENSE.md │ │ │ │ ├── README.md │ │ │ │ ├── SPEC.md │ │ │ │ ├── lfs.c │ │ │ │ ├── lfs.h │ │ │ │ ├── lfs_util.c │ │ │ │ └── lfs_util.h │ │ │ ├── MTP/ │ │ │ │ ├── MTP.cpp │ │ │ │ ├── MTP.h │ │ │ │ ├── Storage.cpp │ │ │ │ └── Storage.h │ │ │ ├── Metro/ │ │ │ │ ├── Metro.cpp │ │ │ │ └── Metro.h │ │ │ ├── SD/ │ │ │ │ ├── library.properties │ │ │ │ └── src/ │ │ │ │ ├── SD.cpp │ │ │ │ └── SD.h │ │ │ ├── SPI/ │ │ │ │ ├── SPI.cpp │ │ │ │ └── SPI.h │ │ │ ├── SdFat/ │ │ │ │ └── src/ │ │ │ │ ├── BufferedPrint.h │ │ │ │ ├── DigitalIO/ │ │ │ │ │ ├── DigitalPin.h │ │ │ │ │ ├── SoftSPI.h │ │ │ │ │ ├── boards/ │ │ │ │ │ │ ├── AvrDevelopersGpioPinMap.h │ │ │ │ │ │ ├── BobuinoGpioPinMap.h │ │ │ │ │ │ ├── GpioPinMap.h │ │ │ │ │ │ ├── LeonardoGpioPinMap.h │ │ │ │ │ │ ├── MegaGpioPinMap.h │ │ │ │ │ │ ├── SleepingBeautyGpioPinMap.h │ │ │ │ │ │ ├── Standard1284GpioPinMap.h │ │ │ │ │ │ ├── Teensy2GpioPinMap.h │ │ │ │ │ │ ├── Teensy2ppGpioPinMap.h │ │ │ │ │ │ └── UnoGpioPinMap.h │ │ │ │ │ └── readme.txt │ │ │ │ ├── ExFatLib/ │ │ │ │ │ ├── ExFatConfig.h │ │ │ │ │ ├── ExFatDbg.cpp │ │ │ │ │ ├── ExFatFile.cpp │ │ │ │ │ ├── ExFatFile.h │ │ │ │ │ ├── ExFatFilePrint.cpp │ │ │ │ │ ├── ExFatFileWrite.cpp │ │ │ │ │ ├── ExFatFormatter.cpp │ │ │ │ │ ├── ExFatFormatter.h │ │ │ │ │ ├── ExFatLib.h │ │ │ │ │ ├── ExFatName.cpp │ │ │ │ │ ├── ExFatPartition.cpp │ │ │ │ │ ├── ExFatPartition.h │ │ │ │ │ ├── ExFatVolume.cpp │ │ │ │ │ ├── ExFatVolume.h │ │ │ │ │ └── upcase.cpp │ │ │ │ ├── FatLib/ │ │ │ │ │ ├── FatDbg.cpp │ │ │ │ │ ├── FatFile.cpp │ │ │ │ │ ├── FatFile.h │ │ │ │ │ ├── FatFileLFN.cpp │ │ │ │ │ ├── FatFilePrint.cpp │ │ │ │ │ ├── FatFileSFN.cpp │ │ │ │ │ ├── FatFormatter.cpp │ │ │ │ │ ├── FatFormatter.h │ │ │ │ │ ├── FatLib.h │ │ │ │ │ ├── FatName.cpp │ │ │ │ │ ├── FatPartition.cpp │ │ │ │ │ ├── FatPartition.h │ │ │ │ │ ├── FatVolume.cpp │ │ │ │ │ └── FatVolume.h │ │ │ │ ├── FreeStack.cpp │ │ │ │ ├── FreeStack.h │ │ │ │ ├── FsLib/ │ │ │ │ │ ├── FsFile.cpp │ │ │ │ │ ├── FsFile.h │ │ │ │ │ ├── FsFormatter.h │ │ │ │ │ ├── FsLib.h │ │ │ │ │ ├── FsNew.cpp │ │ │ │ │ ├── FsNew.h │ │ │ │ │ ├── FsVolume.cpp │ │ │ │ │ └── FsVolume.h │ │ │ │ ├── MinimumSerial.cpp │ │ │ │ ├── MinimumSerial.h │ │ │ │ ├── RingBuf.h │ │ │ │ ├── SdCard/ │ │ │ │ │ ├── CPPLINT.cfg │ │ │ │ │ ├── SdCard.h │ │ │ │ │ ├── SdCardInfo.cpp │ │ │ │ │ ├── SdCardInfo.h │ │ │ │ │ ├── SdCardInterface.h │ │ │ │ │ ├── SdSpiCard.cpp │ │ │ │ │ ├── SdSpiCard.h │ │ │ │ │ ├── SdioCard.h │ │ │ │ │ ├── SdioTeensy.cpp │ │ │ │ │ └── SdioTeensy.h │ │ │ │ ├── SdFat.h │ │ │ │ ├── SdFatConfig.h │ │ │ │ ├── SpiDriver/ │ │ │ │ │ ├── SdSpiArduinoDriver.h │ │ │ │ │ ├── SdSpiArtemis.cpp │ │ │ │ │ ├── SdSpiAvr.h │ │ │ │ │ ├── SdSpiBareUnoDriver.h │ │ │ │ │ ├── SdSpiBaseClass.h │ │ │ │ │ ├── SdSpiChipSelect.cpp │ │ │ │ │ ├── SdSpiDriver.h │ │ │ │ │ ├── SdSpiDue.cpp │ │ │ │ │ ├── SdSpiESP.cpp │ │ │ │ │ ├── SdSpiLibDriver.h │ │ │ │ │ ├── SdSpiParticle.cpp │ │ │ │ │ ├── SdSpiSTM32.cpp │ │ │ │ │ ├── SdSpiSTM32Core.cpp │ │ │ │ │ ├── SdSpiSoftDriver.h │ │ │ │ │ └── SdSpiTeensy3.cpp │ │ │ │ ├── common/ │ │ │ │ │ ├── ArduinoFiles.h │ │ │ │ │ ├── CPPLINT.cfg │ │ │ │ │ ├── CompileDateTime.h │ │ │ │ │ ├── DebugMacros.h │ │ │ │ │ ├── FmtNumber.cpp │ │ │ │ │ ├── FmtNumber.h │ │ │ │ │ ├── FsApiConstants.h │ │ │ │ │ ├── FsBlockDevice.h │ │ │ │ │ ├── FsBlockDeviceInterface.h │ │ │ │ │ ├── FsCache.cpp │ │ │ │ │ ├── FsCache.h │ │ │ │ │ ├── FsDateTime.cpp │ │ │ │ │ ├── FsDateTime.h │ │ │ │ │ ├── FsName.cpp │ │ │ │ │ ├── FsName.h │ │ │ │ │ ├── FsStructs.cpp │ │ │ │ │ ├── FsStructs.h │ │ │ │ │ ├── FsUtf.cpp │ │ │ │ │ ├── FsUtf.h │ │ │ │ │ ├── PrintBasic.cpp │ │ │ │ │ ├── PrintBasic.h │ │ │ │ │ ├── SysCall.h │ │ │ │ │ ├── upcase.cpp │ │ │ │ │ └── upcase.h │ │ │ │ ├── iostream/ │ │ │ │ │ ├── ArduinoStream.h │ │ │ │ │ ├── StdioStream.cpp │ │ │ │ │ ├── StdioStream.h │ │ │ │ │ ├── StreamBaseClass.cpp │ │ │ │ │ ├── bufstream.h │ │ │ │ │ ├── fstream.h │ │ │ │ │ ├── ios.h │ │ │ │ │ ├── iostream.h │ │ │ │ │ ├── istream.cpp │ │ │ │ │ ├── istream.h │ │ │ │ │ ├── ostream.cpp │ │ │ │ │ └── ostream.h │ │ │ │ └── sdios.h │ │ │ ├── Time/ │ │ │ │ ├── DateStrings.cpp │ │ │ │ ├── Time.cpp │ │ │ │ ├── Time.h │ │ │ │ ├── TimeLib.h │ │ │ │ └── library.properties │ │ │ └── Wire/ │ │ │ ├── Wire.cpp │ │ │ ├── Wire.h │ │ │ ├── WireIMXRT.cpp │ │ │ ├── WireIMXRT.h │ │ │ ├── WireKinetis.cpp │ │ │ ├── WireKinetis.h │ │ │ └── utility/ │ │ │ ├── twi.c │ │ │ └── twi.h │ │ ├── platformio.ini │ │ └── src/ │ │ ├── general/ │ │ │ ├── colorschemes.cpp │ │ │ └── globalvariables.cpp │ │ ├── gui/ │ │ │ ├── bitmaps.cpp │ │ │ ├── buttons.cpp │ │ │ ├── firststart.cpp │ │ │ ├── gui.cpp │ │ │ ├── livemode.cpp │ │ │ ├── loadmenu.cpp │ │ │ ├── mainmenu.cpp │ │ │ ├── settingsmenu.cpp │ │ │ └── videomenu.cpp │ │ ├── hardware/ │ │ │ ├── battery.cpp │ │ │ ├── connection.cpp │ │ │ ├── display/ │ │ │ │ ├── display.cpp │ │ │ │ └── fonts.cpp │ │ │ ├── hardware.cpp │ │ │ ├── lepton.cpp │ │ │ ├── massstorage.cpp │ │ │ ├── sdcard.cpp │ │ │ └── touchscreen/ │ │ │ ├── ft6206_touchscreen.cpp │ │ │ ├── touchscreen.cpp │ │ │ └── xpt2046_touchscreen.cpp │ │ ├── main.cpp │ │ └── thermal/ │ │ ├── create.cpp │ │ ├── load.cpp │ │ ├── save.cpp │ │ ├── temperature.cpp │ │ └── thermal.cpp │ └── README.MD ├── pcb/ │ ├── 3.0/ │ │ ├── pcb.brd │ │ └── pcb.sch │ └── README.MD └── software/ ├── README.MD ├── thermal_analysis_software/ │ └── README.MD ├── thermal_data_viewer/ │ └── README.MD ├── thermal_live_viewer/ │ ├── 3.0/ │ │ ├── .gitignore │ │ ├── main.py │ │ ├── requirements.txt │ │ ├── setup.py │ │ └── src/ │ │ ├── __init__.py │ │ ├── colorschemes.py │ │ └── liveviewer.py │ └── README.MD └── video_converter/ └── README.MD ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: maxritter ================================================ FILE: .gitignore ================================================ .DS_Store __pycache__/ *.pyc *.pyo *.pyd *.pyw *.pyz *.pywz ================================================ FILE: LICENSE ================================================ 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. {one line to give the program's name and a brief idea of what it does.} Copyright (C) {year} {name of author} 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: {project} Copyright (C) {year} {fullname} 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 . ================================================ FILE: README.MD ================================================ # DIY-Thermocam The DIY-Thermocam V3 is an open-source thermal imager based on the FLIR Lepton LWIR sensor. Check out the official website for more information about the project: **[www.diy-thermocam.net](https://www.diy-thermocam.net/)** **This repository contains:** - **[Latest firmware releases](https://github.com/maxritter/diy-thermocam/releases)** - **[Open-source firmware](https://github.com/maxritter/diy-thermocam/tree/master/firmware)** - **[Desktop software](https://github.com/maxritter/diy-thermocam/tree/master/software)** - **[Device enclosure](https://github.com/maxritter/diy-thermocam/tree/master/enclosure)** - **[Printed circuit board](https://github.com/maxritter/diy-thermocam/tree/master/pcb)** ================================================ FILE: enclosure/README.MD ================================================ # DIY-Thermocam - Enclosure The enclosure has been designed with Google Sketchup in 3D and converted to 2D files to laser-cut them. The 2D SVG file can be opened with Inkscape. ================================================ FILE: firmware/3.0/.gitignore ================================================ .pio .vscode ================================================ FILE: firmware/3.0/include/battery.h ================================================ /* * * 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 */ ================================================ FILE: firmware/3.0/include/bitmaps.h ================================================ /* * * 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 */ ================================================ FILE: firmware/3.0/include/buttons.h ================================================ /* * * 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 */ ================================================ FILE: firmware/3.0/include/colorschemes.h ================================================ /* * * 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 */ ================================================ FILE: firmware/3.0/include/connection.h ================================================ /* * * 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 0x64 #define CMD_END 0xC8 #define CMD_INVALID 0x00 //Serial terminal commands #define CMD_GET_RAWLIMITS 0x6E #define CMD_GET_RAWDATA 0x6F #define CMD_GET_CONFIGDATA 0x70 #define CMD_GET_CALIBDATA 0x72 #define CMD_GET_SPOTTEMP 0x73 #define CMD_GET_TEMPPOINTS 0x75 #define CMD_SET_SHUTTERRUN 0x78 #define CMD_SET_SHUTTERMODE 0x79 #define CMD_SET_FILTERTYPE 0x7A #define CMD_GET_BATTERYSTATUS 0x7C #define CMD_GET_DIAGNOSTIC 0x7F #define CMD_GET_FWVERSION 0x81 #define CMD_SET_LIMITS 0x82 #define CMD_SET_TEXTCOLOR 0x83 #define CMD_SET_COLORSCHEME 0x84 #define CMD_SET_TEMPFORMAT 0x85 #define CMD_SET_SHOWSPOT 0x86 #define CMD_SET_SHOWCOLORBAR 0x87 #define CMD_SET_SHOWMINMAX 0x88 #define CMD_SET_TEMPPOINTS 0x89 #define CMD_GET_HWVERSION 0x8A #define CMD_SET_ROTATION 0x8B //Serial frame commands #define CMD_FRAME_RAW 0x96 #define CMD_FRAME_COLOR 0x97 #define CMD_FRAME_DISPLAY 0x98 #define CMD_FRAME_SAVE 0x99 //Types of raw frame responses #define FRAME_CAPTURE_THERMAL 0xB4 #define FRAME_CAPTURE_VIDEO 0xB6 #define FRAME_NORMAL 0xB7 /*########################## PUBLIC PROCEDURES ################################*/ void buttonHandler(); 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 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 */ ================================================ FILE: firmware/3.0/include/create.h ================================================ /* * * 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 */ ================================================ FILE: firmware/3.0/include/display.h ================================================ /* * * 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); #endif /* DISPLAY_H */ ================================================ FILE: firmware/3.0/include/firststart.h ================================================ /* * * 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 */ ================================================ FILE: firmware/3.0/include/fonts.h ================================================ /* * * 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 */ ================================================ FILE: firmware/3.0/include/ft6206_touchscreen.h ================================================ /* * * 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 */ ================================================ FILE: firmware/3.0/include/globaldefines.h ================================================ /* * * 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_lepton 4 #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 */ ================================================ FILE: firmware/3.0/include/globalvariables.h ================================================ /* * * 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; extern volatile bool disableSPIIRQ; #endif /* GLOBALVARIABLES_H */ ================================================ FILE: firmware/3.0/include/gui.h ================================================ /* * * 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 */ ================================================ FILE: firmware/3.0/include/hardware.h ================================================ /* * * 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 */ ================================================ FILE: firmware/3.0/include/lepton.h ================================================ /* * * 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 PROCEDURES ################################*/ void lepton_begin(); void lepton_end(); bool lepton_ffc(bool message = false, bool switch_gain = false); void lepton_ffcMode(bool automatic); 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 */ ================================================ FILE: firmware/3.0/include/livemode.h ================================================ /* * * 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 */ ================================================ FILE: firmware/3.0/include/load.h ================================================ /* * * 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 */ ================================================ FILE: firmware/3.0/include/loadmenu.h ================================================ /* * * 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(uint32_t imgCount, char* infoText); void displayVideoFrame(uint32_t imgCount); uint32_t getVideoFrameNumber(); int loadMenu(char* title, int* array, int length); void openImage(char* filename, uint32_t imgCount); void playVideo(char* dirname, uint32_t imgCount); #endif /* LOADMENU_H */ ================================================ FILE: firmware/3.0/include/mainmenu.h ================================================ /* * * 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 */ ================================================ FILE: firmware/3.0/include/massstorage.h ================================================ /* * * 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 */ ================================================ FILE: firmware/3.0/include/save.h ================================================ /* * * 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 createSDName(char* filename, boolean folder = false); void frameFilename(char* filename, uint32_t count); void imgSaveEnd(); void imgSaveStart(); void processVideoFrames(uint32_t framesCaptured, char* dirname); void saveBuffer(char* filename); void saveRawData(bool isImage, char* name, uint32_t framesCaptured = 0); void saveVideoFrame(char* filename); #endif /* SAVE_H */ ================================================ FILE: firmware/3.0/include/sdcard.h ================================================ /* * * 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 */ ================================================ FILE: firmware/3.0/include/settingsmenu.h ================================================ /* * * 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 */ ================================================ FILE: firmware/3.0/include/temperature.h ================================================ /* * * 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 */ ================================================ FILE: firmware/3.0/include/thermal.h ================================================ /* * * 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 */ ================================================ FILE: firmware/3.0/include/touchscreen.h ================================================ /* * * 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 */ ================================================ FILE: firmware/3.0/include/videomenu.h ================================================ /* * * 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, uint32_t* framesCaptured, uint16_t* folderFrames, char* buffer, char* dirName); void videoCaptureNormal(uint32_t* framesCaptured, uint16_t* folderFrames, char* buffer, char* dirName); void videoCapture(); bool videoIntervalChooser(); bool videoIntervalHandler(byte* pos); void videoIntervalString(int pos); void videoMode(); #endif /* VIDEOMENU_H */ ================================================ FILE: firmware/3.0/include/xpt2046_touchscreen.h ================================================ /* * * 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 */ ================================================ FILE: firmware/3.0/lib/ADC/ADC.cpp ================================================ /* 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 ================================================ FILE: firmware/3.0/lib/ADC/ADC.h ================================================ /* 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 ================================================ FILE: firmware/3.0/lib/ADC/ADC_Module.cpp ================================================ /* 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 ================================================ FILE: firmware/3.0/lib/ADC/ADC_Module.h ================================================ /* 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 ================================================ FILE: firmware/3.0/lib/ADC/ADC_util.h ================================================ /* 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 ================================================ FILE: firmware/3.0/lib/ADC/AnalogBufferDMA.cpp ================================================ /* 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->adc[adc_num]->enableDMA(); #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 ================================================ FILE: firmware/3.0/lib/ADC/AnalogBufferDMA.h ================================================ /* 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 ================================================ FILE: firmware/3.0/lib/ADC/VREF.h ================================================ /* 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 ================================================ FILE: firmware/3.0/lib/ADC/atomic.h ================================================ /* 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 ================================================ FILE: firmware/3.0/lib/ADC/settings_defines.h ================================================ /* 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 ================================================ FILE: firmware/3.0/lib/Bounce/Bounce.cpp ================================================ // 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; } ================================================ FILE: firmware/3.0/lib/Bounce/Bounce.h ================================================ /* * 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 ================================================ FILE: firmware/3.0/lib/EEPROM/EEPROM.cpp ================================================ // this file no longer used ================================================ FILE: firmware/3.0/lib/EEPROM/EEPROM.h ================================================ /* 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 #if defined(__has_include) && __has_include() #include #endif #include // need to include this so String is defined /*** 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 ){ #if defined(__has_include) && __has_include() static_assert(std::is_trivially_copyable::value,"You can not use this type with EEPROM.get" ); // the code below only makes sense if you can "memcpy" T #endif 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 ){ #if defined(__has_include) && __has_include() static_assert(std::is_trivially_copyable::value, "You can not use this type with EEPROM.put"); // the code below only makes sense if you can "memcpy" T #endif 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; } }; // put - Specialization for Arduino Strings ------------------------------- // to put an Arduino String to the EEPROM we copy its internal buffer // including the trailing \0 to the eprom template <> inline const String &EEPROMClass::put(int idx, const String &s) { const uint8_t *ptr = (uint8_t *)s.c_str(); #ifdef __arm__ eeprom_write_block(ptr, (void *)idx, s.length() + 1); // length() doesn't account for the trailing \0 #else EEPtr e = idx; for (int count = s.length() + 1; count; --count, ++e) (*e).update(*ptr++); #endif return s; } // get - Specialization for Arduino Strings ------------------------------- // to "get" an Arduino String from the EEPROM we append chars from the EEPROM // into it until we find the delimiting /0. // String.append is not very efficient, code could probably be opitimized if required... template <> inline String &EEPROMClass::get(int idx, String &s){ s = ""; // just in case... EEPtr e = idx; char c = *e; // read in bytes until we find the terminating \0 while (c != '\0') { s.append(c); c = *(++e); } return s; } static EEPROMClass EEPROM __attribute__ ((unused)); #endif ================================================ FILE: firmware/3.0/lib/LittleFS/LittleFS.cpp ================================================ /* 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 uint8_t erasecmd; // command to use for sector erase 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 const char pn[22]; //flash name } known_chips[] = { {{0xEF, 0x40, 0x15}, 24, 256, 32768, 0x52, 2097152, 3000, 1600000, "W25Q16JV*Q/W25Q16FV"}, // Winbond W25Q16JV*Q/W25Q16FV {{0xEF, 0x40, 0x16}, 24, 256, 32768, 0x52, 4194304, 3000, 1600000, "W25Q32JV*Q/W25Q32FV"}, // Winbond W25Q32JV*Q/W25Q32FV {{0xEF, 0x40, 0x17}, 24, 256, 65536, 0xD8, 8388608, 3000, 2000000, "W25Q64JV*Q/W25Q64FV"}, // Winbond W25Q64JV*Q/W25Q64FV {{0xEF, 0x40, 0x18}, 24, 256, 65536, 0xD8, 16777216, 3000, 2000000, "W25Q128JV*Q/W25Q128FV"}, // Winbond W25Q128JV*Q/W25Q128FV {{0xEF, 0x40, 0x19}, 32, 256, 65536, 0xDC, 33554432, 3000, 2000000, "W25Q256JV*Q"}, // Winbond W25Q256JV*Q {{0xEF, 0x40, 0x20}, 32, 256, 65536, 0xDC, 67108864, 3500, 2000000, "W25Q512JV*Q"}, // Winbond W25Q512JV*Q {{0xEF, 0x40, 0x21}, 32, 256, 65536, 0xDC, 134217728, 3500, 2000000, "W25Q01JV*Q"},// Winbond W25Q01JV*Q {{0x62, 0x06, 0x13}, 24, 256, 4096, 0x20, 524288, 5000, 300000, "SST25PF040C"}, // Microchip SST25PF040C //{{0xEF, 0x40, 0x14}, 24, 256, 4096, 0x20, 1048576, 5000, 300000, "W25Q80DV"}, // Winbond W25Q80DV not tested {{0xEF, 0x70, 0x17}, 24, 256, 65536, 0xD8, 8388608, 3000, 2000000, "W25Q64JV*M (DTR)"}, // Winbond W25Q64JV*M (DTR) {{0xEF, 0x70, 0x18}, 24, 256, 65536, 0xD8, 16777216, 3000, 2000000, "W25Q128JV*M (DTR)"}, // Winbond W25Q128JV*M (DTR) {{0xEF, 0x70, 0x19}, 32, 256, 65536, 0xDC, 33554432, 3000, 2000000, "W25Q256JV*M (DTR)"}, // Winbond W25Q256JV*M (DTR) {{0xEF, 0x80, 0x19}, 32, 256, 65536, 0xDC, 33554432, 3000, 2000000, "W25Q256JW*M"}, // Winbond (W25Q256JW*M) {{0xEF, 0x70, 0x20}, 32, 256, 65536, 0xDC, 67108864, 3500, 2000000, "W25Q512JV*M (DTR)"}, // Winbond W25Q512JV*M (DTR) {{0x1F, 0x84, 0x01}, 24, 256, 4096, 0x20, 524288, 2500, 300000, "AT25SF041"}, // Adesto/Atmel AT25SF041 {{0x01, 0x40, 0x14}, 24, 256, 4096, 0x20, 1048576, 5000, 300000, "S25FL208K"}, // Spansion S25FL208K //FRAM {{0x03, 0x2E, 0xC2}, 24, 64, 128, 0, 1048576, 250, 1200, "CY15B108QN"}, //Cypress 8Mb FRAM, CY15B108QN {{0xC2, 0x24, 0x00}, 24, 64, 128, 0, 131072, 250, 1200, "FM25V10-G"}, //Cypress 1Mb FRAM, FM25V10-G {{0xC2, 0x24, 0x01}, 24, 64, 128, 0, 131072, 250, 1200, "FM25V10-G (rev 1)"}, //Cypress 1Mb FRAM, rev1 {{0xAE, 0x83, 0x09}, 24, 64, 128, 0, 131072, 250, 1200, "MR45V100A"}, //ROHM MR45V100A 1 Mbit FeRAM Memory {{0xC2, 0x26, 0x08}, 24, 64, 128, 0, 524288, 250, 1200, "CY15B104Q"}, //Cypress 4Mb FRAM, CY15B104Q {{0x60, 0x2A, 0xC2}, 24, 64, 128, 0, 262144, 250, 1200, "CY15B102Q"}, //Cypress 2Mb FRAM, CY15B102Q {{0x60, 0x2A, 0xC2}, 24, 64, 128, 0, 262144, 250, 1200, "CY15B102Q"}, //Cypress 2Mb FRAM, CY15B102Q {{0x04, 0x7F, 0x48}, 24, 64, 128, 0, 262144, 250, 1200, "MB85RS2MTAPNF"}, //Fujitsu 2Mb FRAM, MB85RS2MTAPNF }; 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; } const char * LittleFS_RAM::getMediaName() { PROGMEM static const char ram_pn_name[] = "MEMORY"; #if defined(__IMXRT1062__) PROGMEM static const char ext_pn_name[] = "EXTMEM"; PROGMEM static const char dma_pn_name[] = "DMAMEM"; if ((uint32_t)config.context >= 0x70000000) return ext_pn_name; if ((uint32_t)config.context >= 0x20200000) return dma_pn_name; #endif return ram_pn_name; } 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; erasecmd = info->erasecmd; 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 const char * LittleFS_SPIFlash::getMediaName(){ const uint8_t cmd_buf[4] = {0x9F, 0, 0, 0}; uint8_t buf[5]; port->beginTransaction(SPICONFIG); digitalWrite(pin, LOW); port->transfer(cmd_buf, buf, 4); digitalWrite(pin, HIGH); port->endTransaction(); const struct chipinfo *info = chip_lookup(buf + 1); return info->pn; } FLASHMEM bool LittleFS_SPIFram::begin(uint8_t cspin, SPIClass &spiport) { pin = cspin; port = &spiport; //Serial.printf("flash begin cs:%u\n", pin); 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 const char * LittleFS_SPIFram::getMediaName(){ 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 ); return info->pn; } 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, Print* pr) { 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 (pr && progressChar && (0 == block%ii) ) pr->write(progressChar); if (!blockIsBlank(&config, block, buffer)) { (*config.erase)(&config, block); } } free(buffer); if (pr && progressChar) pr->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; uint8_t cmdaddr[5]; make_command_and_address(cmdaddr, erasecmd, 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); port->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 port->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, info->erasecmd) | 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, info->erasecmd) | 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 } FLASHMEM const char * LittleFS_QSPIFlash::getMediaName(){ uint8_t buf[4] = {0, 0, 0, 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); return info->pn; } #endif // __IMXRT1062__ #if defined(__IMXRT1062__) #if defined(ARDUINO_TEENSY40) #define FLASH_SIZE 0x1F0000 #define SECTOR_SIZE 32768 #elif defined(ARDUINO_TEENSY41) #define FLASH_SIZE 0x7C0000 #define SECTOR_SIZE 65536 #elif defined(ARDUINO_TEENSY_MICROMOD) #define FLASH_SIZE 0xFC0000 #define SECTOR_SIZE 65536 #endif extern unsigned long _flashimagelen; uint32_t LittleFS_Program::baseaddr = 0; FLASHMEM bool LittleFS_Program::begin(uint32_t size) { //Serial.println("Program flash begin"); //Serial.printf("size in bytes - %u \n", size); configured = false; baseaddr = 0; //size = size & 0xFFFF0000; size = (size + 0xFFFF) & 0xFFFF0000; if (size == 0) return false; const uint32_t program_size = (uint32_t)&_flashimagelen; if (program_size >= FLASH_SIZE) return false; const uint32_t available_space = FLASH_SIZE - program_size; //Serial.printf("FLASH_SIZE - %u, program_size - %u\n", 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("size - %u, baseaddr = %x\n", size, 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 = SECTOR_SIZE; config.block_count = size / SECTOR_SIZE; 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 * SECTOR_SIZE + offset); memcpy(buffer, p, size); return 0; } const char * LittleFS_Program::getMediaName() { PROGMEM static const char prog_pn_name[] = "PROGRAM"; return prog_pn_name; } // 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); extern "C" void eepromemu_flash_erase_32K_block(void *addr); extern "C" void eepromemu_flash_erase_64K_block(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 * SECTOR_SIZE + 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 * SECTOR_SIZE); #if SECTOR_SIZE == 4096 eepromemu_flash_erase_sector(p); #elif SECTOR_SIZE == 32768 eepromemu_flash_erase_32K_block(p); #elif SECTOR_SIZE == 65536 eepromemu_flash_erase_64K_block(p); #else #error "Program SECTOR_SIZE must be 4096, 32768, or 65536" #endif return 0; } #endif // __IMXRT1062__ //----------------------------------------------------------------------------- // Wrapper classes begin methods //----------------------------------------------------------------------------- bool LittleFS_SPI::begin(uint8_t cspin, SPIClass &spiport) { if (cspin != 0xff) csPin_ = cspin; if (flash.begin(csPin_, spiport)) { sprintf(display_name, (const char *)F("Flash_%u"), csPin_); pfs = &flash; return true; } else if (fram.begin(csPin_, spiport)) { sprintf(display_name, (const char *)F("Fram_%u"), csPin_); pfs = &fram; return true; } else if (nand.begin(csPin_, spiport)) { sprintf(display_name, (const char *)F("NAND_%u"), csPin_); pfs = &nand; return true; } // none of the above. pfs = &fsnone; return false; } #ifdef __IMXRT1062__ bool LittleFS_QSPI::begin() { //Serial.printf("Try QSPI"); if (flash.begin()) { //Serial.println(" *** Flash ***"); strcpy(display_name, (const char *)F("QFlash")); pfs = &flash; return true; } else if (nand.begin()) { //Serial.println(" *** Nand ***"); strcpy(display_name, (const char *)F("QNAND")); pfs = &nand; return true; } //Serial.println(" ### Failed ###"); pfs = &fsnone; return false; } #endif // __IMXRT1062__ ================================================ FILE: firmware/3.0/lib/LittleFS/LittleFS.h ================================================ /* 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" //#include class LittleFSFile : public FileImpl { private: // Classes derived from FileImpl 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(); } // These will all return false as only some FS support it. virtual bool getCreateTime(DateTimeFields &tm){ uint32_t mdt = getCreationTime(); if (mdt == 0) { return false;} // did not retrieve a date; breakTime(mdt, tm); return true; } virtual bool getModifyTime(DateTimeFields &tm){ uint32_t mdt = getModifiedTime(); if (mdt == 0) {return false;} // did not retrieve a date; breakTime(mdt, tm); return true; } virtual bool setCreateTime(const DateTimeFields &tm) { if (tm.year < 80 || tm.year > 207) return false; bool success = true; uint32_t mdt = makeTime(tm); int rcode = lfs_setattr(lfs, name(), 'c', (const void *) &mdt, sizeof(mdt)); if(rcode < 0) success = false; return success; } virtual bool setModifyTime(const DateTimeFields &tm) { if (tm.year < 80 || tm.year > 207) return false; bool success = true; uint32_t mdt = makeTime(tm); int rcode = lfs_setattr(lfs, name(), 'm', (const void *) &mdt, sizeof(mdt)); if(rcode < 0) success = false; return success; } 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 bool isOpen() { 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("ONF:: 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); //Serial.print("ONF:: pathname --- "); Serial.println(pathname); 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); } private: lfs_t *lfs; lfs_file_t *file; lfs_dir_t *dir; char *filename; char fullpath[128]; uint32_t getCreationTime() { uint32_t filetime = 0; int rc = lfs_getattr(lfs, fullpath, 'c', (void *)&filetime, sizeof(filetime)); if(rc != sizeof(filetime)) filetime = 0; // Error so clear read value return filetime; } uint32_t getModifiedTime() { uint32_t filetime = 0; int rc = lfs_getattr(lfs, fullpath, 'm', (void *)&filetime, sizeof(filetime)); if(rc != sizeof(filetime)) filetime = 0; // Error so clear read value return filetime; } }; class LittleFS : public FS { public: LittleFS() { configured = false; mounted = false; config.context = nullptr; } virtual bool format(int type=0, char progressChar=0, Print& pr=Serial) { if(type == 0) { return quickFormat(); } if(type == 1) { return lowLevelFormat(progressChar, &pr); } return true; } virtual const char * getMediaName() {return (const char*)F("");} bool quickFormat(); bool lowLevelFormat(char progressChar=0, Print* pr=&Serial); uint32_t formatUnused(uint32_t blockCnt, uint32_t blockStart); File open(const char *filepath, uint8_t mode = FILE_READ) { int rcode; //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) { 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 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) { //attributes get written when the file is closed uint32_t filetime = 0; uint32_t _now = Teensy3Clock.get(); rcode = lfs_getattr(&lfs, filepath, 'c', (void *)&filetime, sizeof(filetime)); if(rcode != sizeof(filetime)) { rcode = lfs_setattr(&lfs, filepath, 'c', (const void *) &_now, sizeof(_now)); if(rcode < 0) Serial.println("FO:: set attribute creation failed"); } rcode = lfs_setattr(&lfs, filepath, 'm', (const void *) &_now, sizeof(_now)); if(rcode < 0) Serial.println("FO:: set attribute modified failed"); if (mode == FILE_WRITE) { 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) { int rcode; if (!mounted) return false; if (lfs_mkdir(&lfs, filepath) < 0) return false; uint32_t _now = Teensy3Clock.get(); rcode = lfs_setattr(&lfs, filepath, 'c', (const void *) &_now, sizeof(_now)); if(rcode < 0) Serial.println("FD:: set attribute creation failed"); rcode = lfs_setattr(&lfs, filepath, 'm', (const void *) &_now, sizeof(_now)); if(rcode < 0) Serial.println("FD:: set attribute modified failed"); return true; } bool rename(const char *oldfilepath, const char *newfilepath) { if (!mounted) return false; if (lfs_rename(&lfs, oldfilepath, newfilepath) < 0) return false; uint32_t _now = Teensy3Clock.get(); int rcode = lfs_setattr(&lfs, newfilepath, 'm', (const void *) &_now, sizeof(_now)); if(rcode < 0) Serial.println("FD:: set attribute modified failed"); 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; } FLASHMEM const char * getMediaName(); 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); const char * getMediaName(); 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; uint8_t erasecmd; uint32_t progtime; uint32_t erasetime; }; class LittleFS_SPIFram : public LittleFS { public: LittleFS_SPIFram() { port = nullptr; } bool begin(uint8_t cspin, SPIClass &spiport=SPI); const char * getMediaName(); 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(); const char * getMediaName(); 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); const char * getMediaName(); 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; } const char * getMediaName() { return (const char *)F("PROGRAM"); } }; #endif 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, Print* pr=&Serial); uint8_t addBBLUT(uint32_t block_address); //temporary for testing const char * getMediaName(); 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 const char * getMediaName(); 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 //---------------------------------------------------------------------------- // Simple SPI wrapper that allows you to specify an IO pin and the // begin method will try the known types of SPI LittleFS file systems // begin methods until it finds one that return true. // you can then query which one using the fs() method. //---------------------------------------------------------------------------- // This FS simply errors out all calls... class FS_NONE : public FS { virtual File open(const char *filename, uint8_t mode = FILE_READ) { return File();} virtual bool exists(const char *filepath) {return false;} virtual bool mkdir(const char *filepath) {return false;} virtual bool rename(const char *oldfilepath, const char *newfilepath) { return false;} virtual bool remove(const char *filepath) { return false;} virtual bool rmdir(const char *filepath) { return false;} virtual uint64_t usedSize() { return 0;} virtual uint64_t totalSize() { return 0;} }; class LittleFS_SPI : public FS { public: LittleFS_SPI(uint8_t pin=0xff) : csPin_(pin) {} bool begin(uint8_t cspin=0xff, SPIClass &spiport=SPI); inline LittleFS * fs() { return (pfs == &fsnone)? nullptr : (LittleFS*)pfs ;} inline const char * displayName() {return display_name;} inline const char * getMediaName() {return (pfs == &fsnone)? (const char *)F("") : ((LittleFS*)pfs)->getMediaName();} // You have full access to internals. uint8_t csPin_; LittleFS_SPIFlash flash; LittleFS_SPIFram fram; LittleFS_SPINAND nand; FS_NONE fsnone; // FS overrides virtual File open(const char *filename, uint8_t mode = FILE_READ) { return pfs->open(filename, mode); } virtual bool exists(const char *filepath) { return pfs->exists(filepath); } virtual bool mkdir(const char *filepath) { return pfs->mkdir(filepath); } virtual bool rename(const char *oldfilepath, const char *newfilepath) { return pfs->rename(oldfilepath, newfilepath); } virtual bool remove(const char *filepath) { return pfs->remove(filepath); } virtual bool rmdir(const char *filepath) { return pfs->rmdir(filepath); } virtual uint64_t usedSize() { return pfs->usedSize(); } virtual uint64_t totalSize() { return pfs->totalSize(); } virtual bool format(int type=0, char progressChar=0, Print& pr=Serial) { return pfs->format(type, progressChar, pr); } private: FS *pfs = &fsnone; char display_name[10]; }; //---------------------------------------------------------------------------- // Simple QSPI wraper for T4.1 //---------------------------------------------------------------------------- #ifdef __IMXRT1062__ class LittleFS_QSPI : public FS { public: LittleFS_QSPI(){} bool begin(); inline LittleFS * fs() { return (pfs == &fsnone)? nullptr : (LittleFS*)pfs ;} inline const char * displayName() {return display_name;} inline const char * getMediaName() {return (pfs == &fsnone)? (const char *)F("") : ((LittleFS*)pfs)->getMediaName();} // You have full access to internals. uint8_t csPin; LittleFS_QSPIFlash flash; LittleFS_QPINAND nand; FS_NONE fsnone; // FS overrides virtual File open(const char *filename, uint8_t mode = FILE_READ) { return pfs->open(filename, mode); } virtual bool exists(const char *filepath) { return pfs->exists(filepath); } virtual bool mkdir(const char *filepath) { return pfs->mkdir(filepath); } virtual bool rename(const char *oldfilepath, const char *newfilepath) { return pfs->rename(oldfilepath, newfilepath); } virtual bool remove(const char *filepath) { return pfs->remove(filepath); } virtual bool rmdir(const char *filepath) { return pfs->rmdir(filepath); } virtual uint64_t usedSize() { return pfs->usedSize(); } virtual uint64_t totalSize() { return pfs->totalSize();} virtual bool format(int type=0, char progressChar=0, Print& pr=Serial) { return pfs->format(type, progressChar, pr); } private: FS *pfs = &fsnone; char display_name[10]; }; #endif ================================================ FILE: firmware/3.0/lib/LittleFS/LittleFS_NAND.cpp ================================================ /* 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 // 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 #define SPICONFIG_NAND 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 uint8_t erasecmd; // command to use for sector erase 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 const char pn[14]; //flash name } 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, 0, 131596288, 2000, 15000, "W25N01GVZEIG"}, //Winbond W25N01G //{{0xEF, 0xAA, 0x22}, 24, 2048, 131072, 134217728*2, 2000, 15000}, //Winbond W25N02G {{0xEF, 0xAA, 0x22}, 24, 2048, 131072, 0, 265289728, 2000, 15000, "W25N02KVZEIR"}, //Winbond W25N02G {{0xEF, 0xBB, 0x21}, 24, 2048, 131072, 0, 265289728, 2000, 15000, "W25M02"}, //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); //Write BBLUT with next sequential block #ifdef LATER uint8_t cmd[5]; uint16_t pba, lba; pba = block_address; lba = LINEAR_TO_BLOCK((firstOpenEntry+1)*blocksize + chipsize); Serial.printf("PBA: %d, LBA: %d\n", pba, lba); cmd[0] = 0xA1; cmd[1] = pba >> 8; cmd[2] = pba; cmd[3] = lba >> 8; cmd[4] = lba; //port->beginTransaction(SPICONFIG_NAND); //digitalWrite(pin, LOW); //port->transfer(cmd, 5); //digitalWrite(pin, HIGH); //port->endTransaction(); #endif 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, Print* pr) { uint32_t eraseAddr; bool val; val = LittleFS::lowLevelFormat(progressChar, pr); for(uint16_t blocks = 0; blocks < reservedBBMBlocks; blocks++) { eraseAddr = (config.block_count + blocks) * config.block_size; eraseSector(eraseAddr); } return val; } const char * LittleFS_SPINAND::getMediaName(){ const uint8_t cmd_buf[5] = {0x9F, 0, 0, 0, 0}; uint8_t buf[5]; SPI.beginTransaction(SPICONFIG_NAND); digitalWrite(pin, LOW); SPI.transfer(cmd_buf, buf, 5); digitalWrite(pin, HIGH); SPI.endTransaction(); const struct chipinfo *info = chip_lookup(buf+2); return info->pn; } #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); #ifdef LATER uint8_t cmd[4]; cmd[0] = pba >> 8; cmd[1] = pba; cmd[2] = lba >> 8; cmd[3] = lba; //FLEXSPI2_LUT44 = LUT0(CMD_SDR, PINS1, 0xA1) | LUT0(WRITE_SDR, PINS1, 1); //flexspi2_ip_write(8, 0x00800000, cmd, 4); #endif 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; } const char * LittleFS_QPINAND::getMediaName(){ uint8_t buf[5] = {0, 0, 0, 0, 0}; flexspi2_ip_read(8, 0x00800000, buf, 4); const struct chipinfo *info = chip_lookup(buf+1); return info->pn; } #endif // __IMXRT1062__ ================================================ FILE: firmware/3.0/lib/LittleFS/littlefs/DESIGN.md ================================================ ## 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 ================================================ FILE: firmware/3.0/lib/LittleFS/littlefs/LICENSE.md ================================================ 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. ================================================ FILE: firmware/3.0/lib/LittleFS/littlefs/README.md ================================================ ## 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](bd/lfs_testbd.h) found in the `bd` 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]. - [littlefs-python] - A Python wrapper for littlefs. The project allows you to create images of the filesystem on your PC. Check if littlefs will fit your needs, create images for a later download to the target memory or inspect the content of a binary image of the target memory. - [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 [littlefs-python]: https://pypi.org/project/littlefs-python/ ================================================ FILE: firmware/3.0/lib/LittleFS/littlefs/SPEC.md ================================================ ## 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. --- ================================================ FILE: firmware/3.0/lib/LittleFS/littlefs/lfs.c ================================================ /* * The little filesystem * * Copyright (c) 2017, Arm Limited. All rights reserved. * SPDX-License-Identifier: BSD-3-Clause */ #include "lfs.h" #include "lfs_util.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; lfs_size_t diff = 0; for (lfs_off_t i = 0; i < size; i += diff) { uint8_t dat[8]; diff = lfs_min(size-i, sizeof(dat)); int res = lfs_bd_read(lfs, pcache, rcache, hint-i, block, off+i, &dat, diff); if (res) { return res; } res = memcmp(dat, data + i, diff); if (res) { return res < 0 ? LFS_CMP_LT : LFS_CMP_GT; } } return LFS_CMP_EQ; } #ifndef LFS_READONLY 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; } #endif #ifndef LFS_READONLY 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; } #endif #ifndef LFS_READONLY 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; } #endif #ifndef LFS_READONLY 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; } #endif /// 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); } #ifndef LFS_READONLY static void lfs_ctz_tole32(struct lfs_ctz *ctz) { ctz->head = lfs_tole32(ctz->head); ctz->size = lfs_tole32(ctz->size); } #endif 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); } #ifndef LFS_NO_ASSERT static bool lfs_mlist_isopen(struct lfs_mlist *head, struct lfs_mlist *node) { for (struct lfs_mlist **p = &head; *p; p = &(*p)->next) { if (*p == (struct lfs_mlist*)node) { return true; } } return false; } #endif static void lfs_mlist_remove(lfs_t *lfs, struct lfs_mlist *mlist) { for (struct lfs_mlist **p = &lfs->mlist; *p; p = &(*p)->next) { if (*p == mlist) { *p = (*p)->next; break; } } } static void lfs_mlist_append(lfs_t *lfs, struct lfs_mlist *mlist) { mlist->next = lfs->mlist; lfs->mlist = mlist; } /// Internal operations predeclared here /// #ifndef LFS_READONLY 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 lfs_ssize_t lfs_file_rawwrite(lfs_t *lfs, lfs_file_t *file, const void *buffer, lfs_size_t size); static int lfs_file_rawsync(lfs_t *lfs, lfs_file_t *file); 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 int 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]); static int lfs_fs_forceconsistency(lfs_t *lfs); #endif #ifdef LFS_MIGRATE static int lfs1_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); #endif static int lfs_dir_rawrewind(lfs_t *lfs, lfs_dir_t *dir); static lfs_ssize_t lfs_file_rawread(lfs_t *lfs, lfs_file_t *file, void *buffer, lfs_size_t size); static int lfs_file_rawclose(lfs_t *lfs, lfs_file_t *file); static lfs_soff_t lfs_file_rawsize(lfs_t *lfs, lfs_file_t *file); static lfs_ssize_t lfs_fs_rawsize(lfs_t *lfs); static int lfs_fs_rawtraverse(lfs_t *lfs, int (*cb)(void *data, lfs_block_t block), void *data, bool includeorphans); static int lfs_deinit(lfs_t *lfs); static int lfs_rawunmount(lfs_t *lfs); /// Block allocator /// #ifndef LFS_READONLY 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; } #endif // indicate allocated blocks have been committed into the filesystem, this // is to prevent blocks from being garbage collected in the middle of a // commit operation static void lfs_alloc_ack(lfs_t *lfs) { lfs->free.ack = lfs->cfg->block_count; } // drop the lookahead buffer, this is done during mounting and failed // traversals in order to avoid invalid lookahead state static void lfs_alloc_drop(lfs_t *lfs) { lfs->free.size = 0; lfs->free.i = 0; lfs_alloc_ack(lfs); } #ifndef LFS_READONLY 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_rawtraverse(lfs, lfs_alloc_lookahead, lfs, true); if (err) { lfs_alloc_drop(lfs); return err; } } } #endif /// 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; } #ifndef LFS_READONLY 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; } #endif #ifndef LFS_READONLY 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; } } } } #endif 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, note we use another crc here // as a collection function because it is sufficiently // random and convenient lfs->seed = lfs_crc(lfs->seed, &crc, sizeof(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; }; #ifndef LFS_READONLY 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; } #endif #ifndef LFS_READONLY 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; } #endif #ifndef LFS_READONLY static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) { // align to program units const lfs_off_t end = lfs_alignup(commit->off + 2*sizeof(uint32_t), lfs->cfg->prog_size); lfs_off_t off1 = 0; uint32_t crc1 = 0; // 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; } // keep track of non-padding checksum to verify if (off1 == 0) { off1 = commit->off + sizeof(uint32_t); crc1 = commit->crc; } 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; 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; } #endif #ifndef LFS_READONLY 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; } // to make sure we don't immediately evict, align the new revision count // to our block_cycles modulus, see lfs_dir_compact for why our modulus // is tweaked this way if (lfs->cfg->block_cycles > 0) { dir->rev = lfs_alignup(dir->rev, ((lfs->cfg->block_cycles+1)|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; } #endif #ifndef LFS_READONLY 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; } #endif #ifndef LFS_READONLY 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; } #endif #ifndef LFS_READONLY 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; } #endif #ifndef LFS_READONLY struct lfs_dir_commit_commit { lfs_t *lfs; struct lfs_commit *commit; }; #endif #ifndef LFS_READONLY 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); } #endif #ifndef LFS_READONLY 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->metadata_max ? lfs->cfg->metadata_max : 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_rawsize(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->metadata_max ? lfs->cfg->metadata_max : 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; } #endif #ifndef LFS_READONLY 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->metadata_max ? lfs->cfg->metadata_max : 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; } #endif /// Top level directory operations /// #ifndef LFS_READONLY static int lfs_rawmkdir(lfs_t *lfs, const char *path) { // deorphan if we haven't yet, needed at most once after poweron int err = lfs_fs_forceconsistency(lfs); if (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)) { return (err < 0) ? err : LFS_ERR_EXIST; } // check that name fits lfs_size_t nlen = strlen(path); if (nlen > lfs->name_max) { return LFS_ERR_NAMETOOLONG; } // build up new directory lfs_alloc_ack(lfs); lfs_mdir_t dir; err = lfs_dir_alloc(lfs, &dir); if (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) { 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) { return err; } // current block end of list? if (cwd.m.split) { // update tails, this creates a desync err = lfs_fs_preporphans(lfs, +1); if (err) { return err; } // 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; return err; } lfs->mlist = cwd.next; err = lfs_fs_preporphans(lfs, -1); if (err) { return err; } } // 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) { return err; } return 0; } #endif static int lfs_dir_rawopen(lfs_t *lfs, lfs_dir_t *dir, const char *path) { lfs_stag_t tag = lfs_dir_find(lfs, &dir->m, &path, NULL); if (tag < 0) { return tag; } if (lfs_tag_type3(tag) != LFS_TYPE_DIR) { 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) { return res; } lfs_pair_fromle32(pair); } // fetch first pair int err = lfs_dir_fetch(lfs, &dir->m, pair); if (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; lfs_mlist_append(lfs, (struct lfs_mlist *)dir); return 0; } static int lfs_dir_rawclose(lfs_t *lfs, lfs_dir_t *dir) { // remove from list of mdirs lfs_mlist_remove(lfs, (struct lfs_mlist *)dir); return 0; } static int lfs_dir_rawread(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *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; return true; } else if (dir->pos == 1) { info->type = LFS_TYPE_DIR; strcpy(info->name, ".."); dir->pos += 1; return true; } while (true) { if (dir->id == dir->m.count) { if (!dir->m.split) { return false; } int err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail); if (err) { return err; } dir->id = 0; } int err = lfs_dir_getinfo(lfs, &dir->m, dir->id, info); if (err && err != LFS_ERR_NOENT) { return err; } dir->id += 1; if (err != LFS_ERR_NOENT) { break; } } dir->pos += 1; return true; } static int lfs_dir_rawseek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) { // simply walk from head dir int err = lfs_dir_rawrewind(lfs, dir); if (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) { return LFS_ERR_INVAL; } err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail); if (err) { return err; } dir->id = 0; } } return 0; } static lfs_soff_t lfs_dir_rawtell(lfs_t *lfs, lfs_dir_t *dir) { (void)lfs; return dir->pos; } static int lfs_dir_rawrewind(lfs_t *lfs, lfs_dir_t *dir) { // reload the head dir int err = lfs_dir_fetch(lfs, &dir->m, dir->head); if (err) { return err; } dir->id = 0; dir->pos = 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; } #ifndef LFS_READONLY 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); } } #endif 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 /// static int lfs_file_rawopencfg(lfs_t *lfs, lfs_file_t *file, const char *path, int flags, const struct lfs_file_config *cfg) { #ifndef LFS_READONLY // deorphan if we haven't yet, needed at most once after poweron if ((flags & LFS_O_WRONLY) == LFS_O_WRONLY) { int err = lfs_fs_forceconsistency(lfs); if (err) { return err; } } #else LFS_ASSERT((flags & LFS_O_RDONLY) == LFS_O_RDONLY); #endif // setup simple file details int err; file->cfg = cfg; file->flags = flags; 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; lfs_mlist_append(lfs, (struct lfs_mlist *)file); #ifdef LFS_READONLY if (tag == LFS_ERR_NOENT) { err = LFS_ERR_NOENT; goto cleanup; #else 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; #endif } else if (lfs_tag_type3(tag) != LFS_TYPE_REG) { err = LFS_ERR_ISDIR; goto cleanup; #ifndef LFS_READONLY } else if (flags & LFS_O_TRUNC) { // truncate if requested tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0); file->flags |= LFS_F_DIRTY; #endif } 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 opened for read / read-write operations if ((file->flags & LFS_O_RDONLY) == LFS_O_RDONLY) { 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; } } #ifndef LFS_READONLY // if opened for write / read-write operations if ((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY) { if (file->cfg->attrs[i].size > lfs->attr_max) { err = LFS_ERR_NOSPC; goto cleanup; } file->flags |= LFS_F_DIRTY; } #endif } // 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; } } } return 0; cleanup: // clean up lingering resources #ifndef LFS_READONLY file->flags |= LFS_F_ERRED; #endif lfs_file_rawclose(lfs, file); return err; } static int lfs_file_rawopen(lfs_t *lfs, lfs_file_t *file, const char *path, int flags) { static const struct lfs_file_config defaults = {0}; int err = lfs_file_rawopencfg(lfs, file, path, flags, &defaults); return err; } static int lfs_file_rawclose(lfs_t *lfs, lfs_file_t *file) { #ifndef LFS_READONLY int err = lfs_file_rawsync(lfs, file); #else int err = 0; #endif // remove from list of mdirs lfs_mlist_remove(lfs, (struct lfs_mlist*)file); // clean up memory if (!file->cfg->buffer) { lfs_free(file->cache.buffer); } return err; } #ifndef LFS_READONLY static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) { 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); } } #endif #ifndef LFS_READONLY 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; } #endif #ifndef LFS_READONLY static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) { 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, .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_rawread(lfs, &orig, &data, 1); if (res < 0) { return res; } res = lfs_file_rawwrite(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; } #endif #ifndef LFS_READONLY static int lfs_file_rawsync(lfs_t *lfs, lfs_file_t *file) { if (file->flags & LFS_F_ERRED) { // it's not safe to do anything if our file errored return 0; } int err = lfs_file_flush(lfs, file); if (err) { file->flags |= LFS_F_ERRED; 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; return err; } file->flags &= ~LFS_F_DIRTY; } return 0; } #endif static lfs_ssize_t lfs_file_rawread(lfs_t *lfs, lfs_file_t *file, void *buffer, lfs_size_t size) { LFS_ASSERT((file->flags & LFS_O_RDONLY) == LFS_O_RDONLY); uint8_t *data = buffer; lfs_size_t nsize = size; #ifndef LFS_READONLY if (file->flags & LFS_F_WRITING) { // flush out any writes int err = lfs_file_flush(lfs, file); if (err) { return err; } } #endif if (file->pos >= file->ctz.size) { // eof if past end 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) { 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) { return err; } } else { int err = lfs_bd_read(lfs, NULL, &file->cache, lfs->cfg->block_size, file->block, file->off, data, diff); if (err) { return err; } } file->pos += diff; file->off += diff; data += diff; nsize -= diff; } return size; } #ifndef LFS_READONLY static lfs_ssize_t lfs_file_rawwrite(lfs_t *lfs, lfs_file_t *file, const void *buffer, lfs_size_t size) { LFS_ASSERT((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY); 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) { 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? 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_rawwrite(lfs, file, &(uint8_t){0}, 1); if (res < 0) { 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->metadata_max ? lfs->cfg->metadata_max : 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; 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; 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; 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; return err; } break; relocate: err = lfs_file_relocate(lfs, file); if (err) { file->flags |= LFS_F_ERRED; return err; } } file->pos += diff; file->off += diff; data += diff; nsize -= diff; lfs_alloc_ack(lfs); } file->flags &= ~LFS_F_ERRED; return size; } #endif static lfs_soff_t lfs_file_rawseek(lfs_t *lfs, lfs_file_t *file, lfs_soff_t off, int whence) { // 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 = lfs_file_rawsize(lfs, file) + off; } if (npos > lfs->file_max) { // file position out of range return LFS_ERR_INVAL; } if (file->pos == npos) { // noop - position has not changed return npos; } #ifndef LFS_READONLY // write out everything beforehand, may be noop if rdonly int err = lfs_file_flush(lfs, file); if (err) { return err; } #endif // update pos file->pos = npos; return npos; } #ifndef LFS_READONLY static int lfs_file_rawtruncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) { LFS_ASSERT((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY); if (size > LFS_FILE_MAX) { return LFS_ERR_INVAL; } lfs_off_t pos = file->pos; lfs_off_t oldsize = lfs_file_rawsize(lfs, file); if (size < oldsize) { // need to flush since directly changing metadata int err = lfs_file_flush(lfs, file); if (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) { return err; } // need to set pos/block/off consistently so seeking back to // the old position does not get confused file->pos = size; 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 lfs_soff_t res = lfs_file_rawseek(lfs, file, 0, LFS_SEEK_END); if (res < 0) { return (int)res; } // fill with zeros while (file->pos < size) { res = lfs_file_rawwrite(lfs, file, &(uint8_t){0}, 1); if (res < 0) { return (int)res; } } } // restore pos lfs_soff_t res = lfs_file_rawseek(lfs, file, pos, LFS_SEEK_SET); if (res < 0) { return (int)res; } return 0; } #endif static lfs_soff_t lfs_file_rawtell(lfs_t *lfs, lfs_file_t *file) { (void)lfs; return file->pos; } static int lfs_file_rawrewind(lfs_t *lfs, lfs_file_t *file) { lfs_soff_t res = lfs_file_rawseek(lfs, file, 0, LFS_SEEK_SET); if (res < 0) { return (int)res; } return 0; } static lfs_soff_t lfs_file_rawsize(lfs_t *lfs, lfs_file_t *file) { (void)lfs; #ifndef LFS_READONLY if (file->flags & LFS_F_WRITING) { return lfs_max(file->pos, file->ctz.size); } #endif return file->ctz.size; } /// General fs operations /// static int lfs_rawstat(lfs_t *lfs, const char *path, struct lfs_info *info) { lfs_mdir_t cwd; lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); if (tag < 0) { return (int)tag; } return lfs_dir_getinfo(lfs, &cwd, lfs_tag_id(tag), info); } #ifndef LFS_READONLY static int lfs_rawremove(lfs_t *lfs, const char *path) { // deorphan if we haven't yet, needed at most once after poweron int err = lfs_fs_forceconsistency(lfs); if (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) { 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) { return (int)res; } lfs_pair_fromle32(pair); err = lfs_dir_fetch(lfs, &dir.m, pair); if (err) { return err; } if (dir.m.count > 0 || dir.m.split) { return LFS_ERR_NOTEMPTY; } // mark fs as orphaned err = lfs_fs_preporphans(lfs, +1); if (err) { return err; } // 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; return err; } lfs->mlist = dir.next; if (lfs_tag_type3(tag) == LFS_TYPE_DIR) { // fix orphan err = lfs_fs_preporphans(lfs, -1); if (err) { return err; } err = lfs_fs_pred(lfs, dir.m.pair, &cwd); if (err) { return err; } err = lfs_dir_drop(lfs, &cwd, &dir.m); if (err) { return err; } } return 0; } #endif #ifndef LFS_READONLY static int lfs_rawrename(lfs_t *lfs, const char *oldpath, const char *newpath) { // deorphan if we haven't yet, needed at most once after poweron int err = lfs_fs_forceconsistency(lfs); if (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) { 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)) { 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) { 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)) { return LFS_ERR_ISDIR; } else if (samepair && newid == newoldid) { // we're renaming to ourselves?? 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) { return (int)res; } lfs_pair_fromle32(prevpair); // must be empty before removal err = lfs_dir_fetch(lfs, &prevdir.m, prevpair); if (err) { return err; } if (prevdir.m.count > 0 || prevdir.m.split) { return LFS_ERR_NOTEMPTY; } // mark fs as orphaned err = lfs_fs_preporphans(lfs, +1); if (err) { return err; } // 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; 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; return err; } } lfs->mlist = prevdir.next; if (prevtag != LFS_ERR_NOENT && lfs_tag_type3(prevtag) == LFS_TYPE_DIR) { // fix orphan err = lfs_fs_preporphans(lfs, -1); if (err) { return err; } err = lfs_fs_pred(lfs, prevdir.m.pair, &newcwd); if (err) { return err; } err = lfs_dir_drop(lfs, &newcwd, &prevdir.m); if (err) { return err; } } return 0; } #endif static lfs_ssize_t lfs_rawgetattr(lfs_t *lfs, const char *path, uint8_t type, 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; } } 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) { return LFS_ERR_NOATTR; } return tag; } return lfs_tag_size(tag); } #ifndef LFS_READONLY 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})); } #endif #ifndef LFS_READONLY static int lfs_rawsetattr(lfs_t *lfs, const char *path, uint8_t type, const void *buffer, lfs_size_t size) { if (size > lfs->attr_max) { return LFS_ERR_NOSPC; } return lfs_commitattr(lfs, path, type, buffer, size); } #endif #ifndef LFS_READONLY static int lfs_rawremoveattr(lfs_t *lfs, const char *path, uint8_t type) { return lfs_commitattr(lfs, path, type, NULL, 0x3ff); } #endif /// 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; } LFS_ASSERT(lfs->cfg->metadata_max <= lfs->cfg->block_size); // 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; } #ifndef LFS_READONLY static int lfs_rawformat(lfs_t *lfs, const struct lfs_config *cfg) { int err = 0; { err = lfs_init(lfs, cfg); if (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; } // 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; } // sanity check that fetch works err = lfs_dir_fetch(lfs, &root, (const lfs_block_t[2]){0, 1}); if (err) { goto cleanup; } } cleanup: lfs_deinit(lfs); return err; } #endif static int lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg) { int err = lfs_init(lfs, cfg); if (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, to distribute allocations uniformly across // boots, we start the allocator at a random location lfs->free.off = lfs->seed % lfs->cfg->block_count; lfs_alloc_drop(lfs); return 0; cleanup: lfs_rawunmount(lfs); return err; } static int lfs_rawunmount(lfs_t *lfs) { return lfs_deinit(lfs); } /// Filesystem filesystem operations /// int lfs_fs_rawtraverse(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; } } } } } #ifndef LFS_READONLY // 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; } } } #endif return 0; } #ifndef LFS_READONLY 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; } #endif #ifndef LFS_READONLY struct lfs_fs_parent_match { lfs_t *lfs; const lfs_block_t pair[2]; }; #endif #ifndef LFS_READONLY 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; } #endif #ifndef LFS_READONLY 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; } #endif #ifndef LFS_READONLY 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 int err = lfs_fs_preporphans(lfs, +1); if (err) { return err; } // 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); 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 err = lfs_fs_preporphans(lfs, -1); if (err) { return err; } } // 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; } #endif #ifndef LFS_READONLY static int 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)); return 0; } #endif #ifndef LFS_READONLY 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; } #endif #ifndef LFS_READONLY 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; } #endif #ifndef LFS_READONLY 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 return lfs_fs_preporphans(lfs, -lfs_gstate_getorphans(&lfs->gstate)); } #endif #ifndef LFS_READONLY 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; } #endif static int lfs_fs_size_count(void *p, lfs_block_t block) { (void)block; lfs_size_t *size = p; *size += 1; return 0; } static lfs_ssize_t lfs_fs_rawsize(lfs_t *lfs) { lfs_size_t size = 0; int err = lfs_fs_rawtraverse(lfs, lfs_fs_size_count, &size, false); if (err) { return 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 /// static int lfs_rawmigrate(lfs_t *lfs, const struct lfs_config *cfg) { struct lfs1 lfs1; int err = lfs1_mount(lfs, &lfs1, cfg); if (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), NULL}, {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), 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, &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); return err; } #endif /// Public API wrappers /// // Here we can add tracing/thread safety easily // Thread-safe wrappers if enabled #ifdef LFS_THREADSAFE #define LFS_LOCK(cfg) cfg->lock(cfg) #define LFS_UNLOCK(cfg) cfg->unlock(cfg) #else #define LFS_LOCK(cfg) ((void)cfg, 0) #define LFS_UNLOCK(cfg) ((void)cfg) #endif // Public API #ifndef LFS_READONLY int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { int err = LFS_LOCK(cfg); if (err) { return err; } 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); err = lfs_rawformat(lfs, cfg); LFS_TRACE("lfs_format -> %d", err); LFS_UNLOCK(cfg); return err; } #endif int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { int err = LFS_LOCK(cfg); if (err) { return err; } 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); err = lfs_rawmount(lfs, cfg); LFS_TRACE("lfs_mount -> %d", err); LFS_UNLOCK(cfg); return err; } int lfs_unmount(lfs_t *lfs) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_unmount(%p)", (void*)lfs); err = lfs_rawunmount(lfs); LFS_TRACE("lfs_unmount -> %d", err); LFS_UNLOCK(lfs->cfg); return err; } #ifndef LFS_READONLY int lfs_remove(lfs_t *lfs, const char *path) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_remove(%p, \"%s\")", (void*)lfs, path); err = lfs_rawremove(lfs, path); LFS_TRACE("lfs_remove -> %d", err); LFS_UNLOCK(lfs->cfg); return err; } #endif #ifndef LFS_READONLY int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_rename(%p, \"%s\", \"%s\")", (void*)lfs, oldpath, newpath); err = lfs_rawrename(lfs, oldpath, newpath); LFS_TRACE("lfs_rename -> %d", err); LFS_UNLOCK(lfs->cfg); return err; } #endif int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_stat(%p, \"%s\", %p)", (void*)lfs, path, (void*)info); err = lfs_rawstat(lfs, path, info); LFS_TRACE("lfs_stat -> %d", err); LFS_UNLOCK(lfs->cfg); return err; } lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, uint8_t type, void *buffer, lfs_size_t size) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_getattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", (void*)lfs, path, type, buffer, size); lfs_ssize_t res = lfs_rawgetattr(lfs, path, type, buffer, size); LFS_TRACE("lfs_getattr -> %"PRId32, res); LFS_UNLOCK(lfs->cfg); return res; } #ifndef LFS_READONLY int lfs_setattr(lfs_t *lfs, const char *path, uint8_t type, const void *buffer, lfs_size_t size) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_setattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", (void*)lfs, path, type, buffer, size); err = lfs_rawsetattr(lfs, path, type, buffer, size); LFS_TRACE("lfs_setattr -> %d", err); LFS_UNLOCK(lfs->cfg); return err; } #endif #ifndef LFS_READONLY int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_removeattr(%p, \"%s\", %"PRIu8")", (void*)lfs, path, type); err = lfs_rawremoveattr(lfs, path, type); LFS_TRACE("lfs_removeattr -> %d", err); LFS_UNLOCK(lfs->cfg); return err; } #endif int lfs_file_open(lfs_t *lfs, lfs_file_t *file, const char *path, int flags) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_file_open(%p, %p, \"%s\", %x)", (void*)lfs, (void*)file, path, flags); LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); err = lfs_file_rawopen(lfs, file, path, flags); LFS_TRACE("lfs_file_open -> %d", err); LFS_UNLOCK(lfs->cfg); return err; } int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, const char *path, int flags, const struct lfs_file_config *cfg) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } 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); LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); err = lfs_file_rawopencfg(lfs, file, path, flags, cfg); LFS_TRACE("lfs_file_opencfg -> %d", err); LFS_UNLOCK(lfs->cfg); return err; } int lfs_file_close(lfs_t *lfs, lfs_file_t *file) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_file_close(%p, %p)", (void*)lfs, (void*)file); LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); err = lfs_file_rawclose(lfs, file); LFS_TRACE("lfs_file_close -> %d", err); LFS_UNLOCK(lfs->cfg); return err; } #ifndef LFS_READONLY int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_file_sync(%p, %p)", (void*)lfs, (void*)file); LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); err = lfs_file_rawsync(lfs, file); LFS_TRACE("lfs_file_sync -> %d", err); LFS_UNLOCK(lfs->cfg); return err; } #endif lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, void *buffer, lfs_size_t size) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_file_read(%p, %p, %p, %"PRIu32")", (void*)lfs, (void*)file, buffer, size); LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); lfs_ssize_t res = lfs_file_rawread(lfs, file, buffer, size); LFS_TRACE("lfs_file_read -> %"PRId32, res); LFS_UNLOCK(lfs->cfg); return res; } #ifndef LFS_READONLY lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, const void *buffer, lfs_size_t size) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_file_write(%p, %p, %p, %"PRIu32")", (void*)lfs, (void*)file, buffer, size); LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); lfs_ssize_t res = lfs_file_rawwrite(lfs, file, buffer, size); LFS_TRACE("lfs_file_write -> %"PRId32, res); LFS_UNLOCK(lfs->cfg); return res; } #endif lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, lfs_soff_t off, int whence) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_file_seek(%p, %p, %"PRId32", %d)", (void*)lfs, (void*)file, off, whence); LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); lfs_soff_t res = lfs_file_rawseek(lfs, file, off, whence); LFS_TRACE("lfs_file_seek -> %"PRId32, res); LFS_UNLOCK(lfs->cfg); return res; } #ifndef LFS_READONLY int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_file_truncate(%p, %p, %"PRIu32")", (void*)lfs, (void*)file, size); LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); err = lfs_file_rawtruncate(lfs, file, size); LFS_TRACE("lfs_file_truncate -> %d", err); LFS_UNLOCK(lfs->cfg); return err; } #endif lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_file_tell(%p, %p)", (void*)lfs, (void*)file); LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); lfs_soff_t res = lfs_file_rawtell(lfs, file); LFS_TRACE("lfs_file_tell -> %"PRId32, res); LFS_UNLOCK(lfs->cfg); return res; } int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_file_rewind(%p, %p)", (void*)lfs, (void*)file); err = lfs_file_rawrewind(lfs, file); LFS_TRACE("lfs_file_rewind -> %d", err); LFS_UNLOCK(lfs->cfg); return err; } lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_file_size(%p, %p)", (void*)lfs, (void*)file); LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); lfs_soff_t res = lfs_file_rawsize(lfs, file); LFS_TRACE("lfs_file_size -> %"PRId32, res); LFS_UNLOCK(lfs->cfg); return res; } #ifndef LFS_READONLY int lfs_mkdir(lfs_t *lfs, const char *path) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_mkdir(%p, \"%s\")", (void*)lfs, path); err = lfs_rawmkdir(lfs, path); LFS_TRACE("lfs_mkdir -> %d", err); LFS_UNLOCK(lfs->cfg); return err; } #endif int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_dir_open(%p, %p, \"%s\")", (void*)lfs, (void*)dir, path); LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)dir)); err = lfs_dir_rawopen(lfs, dir, path); LFS_TRACE("lfs_dir_open -> %d", err); LFS_UNLOCK(lfs->cfg); return err; } int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_dir_close(%p, %p)", (void*)lfs, (void*)dir); err = lfs_dir_rawclose(lfs, dir); LFS_TRACE("lfs_dir_close -> %d", err); LFS_UNLOCK(lfs->cfg); return err; } int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_dir_read(%p, %p, %p)", (void*)lfs, (void*)dir, (void*)info); err = lfs_dir_rawread(lfs, dir, info); LFS_TRACE("lfs_dir_read -> %d", err); LFS_UNLOCK(lfs->cfg); return err; } int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_dir_seek(%p, %p, %"PRIu32")", (void*)lfs, (void*)dir, off); err = lfs_dir_rawseek(lfs, dir, off); LFS_TRACE("lfs_dir_seek -> %d", err); LFS_UNLOCK(lfs->cfg); return err; } lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_dir_tell(%p, %p)", (void*)lfs, (void*)dir); lfs_soff_t res = lfs_dir_rawtell(lfs, dir); LFS_TRACE("lfs_dir_tell -> %"PRId32, res); LFS_UNLOCK(lfs->cfg); return res; } int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_dir_rewind(%p, %p)", (void*)lfs, (void*)dir); err = lfs_dir_rawrewind(lfs, dir); LFS_TRACE("lfs_dir_rewind -> %d", err); LFS_UNLOCK(lfs->cfg); return err; } lfs_ssize_t lfs_fs_size(lfs_t *lfs) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_fs_size(%p)", (void*)lfs); lfs_ssize_t res = lfs_fs_rawsize(lfs); LFS_TRACE("lfs_fs_size -> %"PRId32, res); LFS_UNLOCK(lfs->cfg); return res; } int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void *, lfs_block_t), void *data) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } LFS_TRACE("lfs_fs_traverse(%p, %p, %p)", (void*)lfs, (void*)(uintptr_t)cb, data); err = lfs_fs_rawtraverse(lfs, cb, data, true); LFS_TRACE("lfs_fs_traverse -> %d", err); LFS_UNLOCK(lfs->cfg); return err; } #ifdef LFS_MIGRATE int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) { int err = LFS_LOCK(cfg); if (err) { return err; } 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); err = lfs_rawmigrate(lfs, cfg); LFS_TRACE("lfs_migrate -> %d", err); LFS_UNLOCK(cfg); return err; } #endif ================================================ FILE: firmware/3.0/lib/LittleFS/littlefs/lfs.h ================================================ /* * 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 #include "lfs_util.h" #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 0x00020004 #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 #ifndef LFS_READONLY 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 #endif // internally used flags #ifndef LFS_READONLY LFS_F_DIRTY = 0x010000, // File does not match storage LFS_F_WRITING = 0x020000, // File has been written since last flush #endif LFS_F_READING = 0x040000, // File has been read since last flush #ifndef LFS_READONLY LFS_F_ERRED = 0x080000, // An error occurred during write #endif LFS_F_INLINE = 0x100000, // Currently inlined in directory entry }; // 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); #ifdef LFS_THREADSAFE // Lock the underlying block device. Negative error codes // are propogated to the user. int (*lock)(const struct lfs_config *c); // Unlock the underlying block device. Negative error codes // are propogated to the user. int (*unlock)(const struct lfs_config *c); #endif // 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; // Optional upper limit on total space given to metadata pairs in bytes. On // devices with large blocks (e.g. 128kB) setting this to a low size (2-8kB) // can help bound the metadata compaction time. Must be <= block_size. // Defaults to block_size when zero. lfs_size_t metadata_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 /// #ifndef LFS_READONLY // 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); #endif // 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 /// #ifndef LFS_READONLY // 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); #endif #ifndef LFS_READONLY // 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); #endif // 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); #ifndef LFS_READONLY // 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); #endif #ifndef LFS_READONLY // 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); #endif /// 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); #ifndef LFS_READONLY // 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); #endif // 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); #ifndef LFS_READONLY // 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); #endif // 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 /// #ifndef LFS_READONLY // Create a directory // // Returns a negative error code on failure. int lfs_mkdir(lfs_t *lfs, const char *path); #endif // 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); #ifndef LFS_READONLY #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 #endif #ifdef __cplusplus } /* extern "C" */ #endif #endif ================================================ FILE: firmware/3.0/lib/LittleFS/littlefs/lfs_util.c ================================================ /* * 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 ================================================ FILE: firmware/3.0/lib/LittleFS/littlefs/lfs_util.h ================================================ /* * 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 255 #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 #ifndef LFS_TRACE #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 #endif #ifndef LFS_DEBUG #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 #endif #ifndef LFS_WARN #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 #endif #ifndef LFS_ERROR #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 #endif // Runtime assertions #ifndef LFS_ASSERT #ifndef LFS_NO_ASSERT #define LFS_ASSERT(test) assert(test) #else #define LFS_ASSERT(test) #endif #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 ================================================ FILE: firmware/3.0/lib/MTP/MTP.cpp ================================================ // 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 SD 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; case 0x100C: // SendObjectInfo 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; } } } 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); \ 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 ================================================ FILE: firmware/3.0/lib/MTP/MTP.h ================================================ // 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 ================================================ FILE: firmware/3.0/lib/MTP/Storage.cpp ================================================ // 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); if(f1.isDirectory()) { 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); } else { sd_remove(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) { Serial.println(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) = 0; 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);} 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 ; void close() 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 ================================================ FILE: firmware/3.0/lib/Metro/Metro.cpp ================================================ #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(); } ================================================ FILE: firmware/3.0/lib/Metro/Metro.h ================================================ #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 ================================================ FILE: firmware/3.0/lib/SD/library.properties ================================================ 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=avr,* ================================================ FILE: firmware/3.0/lib/SD/src/SD.cpp ================================================ /* 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; #if defined(ARDUINO_TEENSY41) #define _SD_DAT3 46 #elif defined(ARDUINO_TEENSY40) #define _SD_DAT3 38 #elif defined(ARDUINO_TEENSY_MICROMOD) #define _SD_DAT3 39 #elif defined(ARDUINO_TEENSY35) || defined(ARDUINO_TEENSY36) // #define _SD_DAT3 62 // currently not doing on 3.5/6... #endif #ifdef __arm__ void SDClass::dateTime(uint16_t *date, uint16_t *time) { uint32_t now = Teensy3Clock.get(); if (now < 315532800) { // before 1980 *date = 0; *time = 0; } else { DateTimeFields datetime; breakTime(now, datetime); *date = FS_DATE(datetime.year + 1900, datetime.mon + 1, datetime.mday); *time = FS_TIME(datetime.hour, datetime.min, datetime.sec); } } #endif bool SDClass::format(int type, char progressChar, Print& pr) { SdCard *card = sdfs.card(); if (!card) return false; // no SD card uint32_t sectors = card->sectorCount(); if (sectors <= 12288) return false; // card too small uint8_t *buf = (uint8_t *)malloc(512); if (!buf) return false; // unable to allocate memory bool ret; if (sectors > 67108864) { #ifdef __arm__ ExFatFormatter exFatFormatter; ret = exFatFormatter.format(card, buf, &pr); #else ret = false; #endif } else { FatFormatter fatFormatter; ret = fatFormatter.format(card, buf, &pr); } free(buf); if (ret) { // TODO: Is begin() really necessary? Is a quicker way possible? card->syncDevice(); sdfs.restart(); // TODO: is sdfs.volumeBegin() enough?? } return ret; } bool SDClass::begin(uint8_t csPin) { #ifdef __arm__ FsDateTime::setCallback(dateTime); #endif csPin_ = csPin; // remember which one passed in. #ifdef BUILTIN_SDCARD if (csPin == BUILTIN_SDCARD) { bool ret = sdfs.begin(SdioConfig(FIFO_SDIO)); cardPreviouslyPresent = ret; #if defined(__IMXRT1062__) // start off with just trying on T4.x cdPin_ = _SD_DAT3; if (!ret) pinMode(_SD_DAT3, INPUT_PULLDOWN); #endif return ret; } #endif if (csPin < NUM_DIGITAL_PINS) { bool ret = sdfs.begin(SdSpiConfig(csPin, SHARED_SPI, SD_SCK_MHZ(25))); cardPreviouslyPresent = ret; return ret; } return false; } bool SDClass::mediaPresent() { //Serial.print("mediaPresent: "); bool ret; SdCard *card = sdfs.card(); // Serial.printf("mediaPresent: card:%x cs:%u cd:%u\n", (uint32_t)card, csPin_, cdPin_); if (card) { if (cardPreviouslyPresent) { #ifdef BUILTIN_SDCARD uint32_t s; if (csPin_ == BUILTIN_SDCARD) { #if defined(__MK64FX512__) || defined(__MK66FX1M0__) card->syncDevice(); #endif // defined(__MK64FX512__) || defined(__MK66FX1M0__) s = card->status(); } else s = 0xFFFFFFFF; #else const uint32_t s = 0xFFFFFFFF; #endif if (s == 0xFFFFFFFF) { // see if we have digital pin to bypass... if (cdPin_ < NUM_DIGITAL_PINS) ret = digitalRead(cdPin_); else { // SPI doesn't have 32 bit status, read CID register cid_t cid; ret = card->readCID(&cid); } //Serial.print(ret ? "CID=ok" : "CID=unreadable"); } else if (s == 0) { // assume zero status means card removed // bits 12:9 are card state, which should // normally be 101 = data transfer mode //Serial.print("status=offline"); ret = false; #ifdef _SD_DAT3 if (csPin_ == BUILTIN_SDCARD) pinMode(_SD_DAT3, INPUT_PULLDOWN); #endif } else { //Serial.print("status=present"); ret = true; } } else { // TODO: need a quick test, only call begin if likely present ret = true; // assume we need to check #ifdef _SD_DAT3 if (csPin_ == BUILTIN_SDCARD) ret = digitalReadFast(_SD_DAT3); else #endif { if (cdPin_ < NUM_DIGITAL_PINS) ret = digitalRead(cdPin_); } // now try to restart if (ret) { ret = sdfs.restart(); // bugbug:: if it fails and builtin may need to start pinMode again... } //Serial.print(ret ? "begin ok" : "begin nope"); } } else { //Serial.print("no card"); ret = false; } //Serial.println(); cardPreviouslyPresent = ret; return ret; } bool SDClass::setMediaDetectPin(uint8_t pin) { Serial.printf("SDClass::setMediaDetectPin(%u)", pin); #if defined(BUILTIN_SDCARD) if (pin == BUILTIN_SDCARD) { csPin_ = BUILTIN_SDCARD; // force it in case user did begin using sdCard #if defined(_SD_DAT3) cdPin_ = _SD_DAT3; if (!cardPreviouslyPresent) pinMode(_SD_DAT3, INPUT_PULLDOWN); #else cdPin_ = 0xff; #endif } #endif if (pin < NUM_DIGITAL_PINS) { cdPin_ = pin; pinMode(cdPin_, INPUT_PULLUP); } else { cdPin_ = 0xff; return false; } return true; } ================================================ FILE: firmware/3.0/lib/SD/src/SD.h ================================================ /* 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 #if !defined(SD_FAT_TEENSY_MODIFIED) #error "Teensy's SD library uses a custom modified copy of SdFat. Standard SdFat was mistakenly used. Arduino should print multiple libraries found for SdFat.h. To resolve this error, you will need to move or delete the copy Arduino is using, or otherwise take steps to cause Teensy's special copy of SdFat to be used." #endif // 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 FileImpl { 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) { close(); } 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; } if (sdfatfile.isOpen()) { sdfatfile.close(); } } virtual bool isOpen() { 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(); } virtual bool getCreateTime(DateTimeFields &tm) { uint16_t fat_date, fat_time; if (!sdfatfile.getCreateDateTime(&fat_date, &fat_time)) return false; if ((fat_date == 0) && (fat_time == 0)) return false; tm.sec = FS_SECOND(fat_time); tm.min = FS_MINUTE(fat_time); tm.hour = FS_HOUR(fat_time); tm.mday = FS_DAY(fat_date); tm.mon = FS_MONTH(fat_date) - 1; tm.year = FS_YEAR(fat_date) - 1900; return true; } virtual bool getModifyTime(DateTimeFields &tm) { uint16_t fat_date, fat_time; if (!sdfatfile.getModifyDateTime(&fat_date, &fat_time)) return false; if ((fat_date == 0) && (fat_time == 0)) return false; tm.sec = FS_SECOND(fat_time); tm.min = FS_MINUTE(fat_time); tm.hour = FS_HOUR(fat_time); tm.mday = FS_DAY(fat_date); tm.mon = FS_MONTH(fat_date) - 1; tm.year = FS_YEAR(fat_date) - 1900; return true; } virtual bool setCreateTime(const DateTimeFields &tm) { if (tm.year < 80 || tm.year > 207) return false; return sdfatfile.timestamp(T_CREATE, tm.year + 1900, tm.mon + 1, tm.mday, tm.hour, tm.min, tm.sec); } virtual bool setModifyTime(const DateTimeFields &tm) { if (tm.year < 80 || tm.year > 207) return false; return sdfatfile.timestamp(T_WRITE, tm.year + 1900, tm.mon + 1, tm.mday, tm.hour, tm.min, tm.sec); } private: SDFAT_FILE sdfatfile; char *filename; }; class SDClass : public FS { public: SDClass() { } bool begin(uint8_t csPin = 10); 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() { if (!cardPreviouslyPresent) return (uint64_t)0; return (uint64_t)(sdfs.clusterCount() - sdfs.freeClusterCount()) * (uint64_t)sdfs.bytesPerCluster(); } uint64_t totalSize() { if (!cardPreviouslyPresent) return (uint64_t)0; return (uint64_t)sdfs.clusterCount() * (uint64_t)sdfs.bytesPerCluster(); } bool format(int type=0, char progressChar=0, Print& pr=Serial); bool mediaPresent(); // call to allow you to specify if your SD reader has an IO pin that // can be used to detect if an chip is inserted. On BUILTIN_SDCARD // we will default to use it if you use the begin method, howver // if you bypass this and call directly to SDFat begin, then you // can use this to let us know. bool setMediaDetectPin(uint8_t pin); public: // allow access, so users can mix SD & SdFat APIs SDFAT_BASE sdfs; operator SDFAT_BASE & () { return sdfs; } static void dateTime(uint16_t *date, uint16_t *time); private: bool cardPreviouslyPresent = false; uint8_t csPin_ = 0xff; uint8_t cdPin_ = 0xff; }; 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(); } #if defined(__arm__) operator FsVolume * () __attribute__ ((deprecated("Use SD.begin() to access SD cards"))) { #elif defined(__AVR__) operator FatVolume * () __attribute__ ((deprecated("Use SD.begin() to access SD cards"))) { #endif return SD.sdfs.vol(); } }; #endif ================================================ FILE: firmware/3.0/lib/SPI/SPI.cpp ================================================ /* * 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::transfer16(const void * buf, void * retbuf, size_t count) { if (count == 0) return; uint16_t *p_write = (uint16_t*)buf; uint16_t *p_read = (uint16_t*)retbuf; size_t count_read = count; uint32_t tcr = port().TCR; port().TCR = (tcr & 0xfffff000) | LPSPI_TCR_FRAMESZ(15); // turn on 16 bit mode // 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) { uint16_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) { uint16_t b = port().RDR; // Read any pending RX bytes in if (p_read) *p_read++ = b; count_read--; } } port().TCR = tcr; // restore back } void SPIClass::transfer32(const void * buf, void * retbuf, size_t count) { if (count == 0) return; uint32_t *p_write = (uint32_t*)buf; uint32_t *p_read = (uint32_t*)retbuf; size_t count_read = count; uint32_t tcr = port().TCR; port().TCR = (tcr & 0xfffff000) | LPSPI_TCR_FRAMESZ(31); // turn on 32 bit mode // 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) << 16) | ((*p_write) >> 16) : _transferWriteFill; p_write++; 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) { uint32_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) { uint32_t b = port().RDR; // Read any pending RX bytes in if (p_read) *p_read++ = b; count_read--; } } port().TCR = tcr; // restore back } 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; } // lets clear cache before we update sizes... if ((uint32_t)buf >= 0x20200000u) arm_dcache_flush((uint8_t *)buf, count); if ((uint32_t)retbuf >= 0x20200000u) arm_dcache_delete(retbuf, count); // 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 } 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 } 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 ================================================ FILE: firmware/3.0/lib/SPI/SPI.h ================================================ /* * 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; } uint32_t transfer32(uint32_t data) { uint32_t tcr = port().TCR; port().TCR = (tcr & 0xfffff000) | LPSPI_TCR_FRAMESZ(31); // turn on 32 bit mode port().TDR = data; // output 32 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) { #if 0 // TODO: byte order still needs work to match SPISettings if (__builtin_constant_p(count)) { if (count < 1) return; if (((count & 3) == 0) && (((uint32_t)buf & 3) == 0)) { // size is multiple of 4 and buffer is 32 bit aligned transfer32(buf, buf, count >> 2); return; } if (((count & 1) == 0) && (((uint32_t)buf & 1) == 0)) { // size is multiple of 2 and buffer is 16 bit aligned transfer16(buf, buf, count >> 1); return; } } #endif 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 // Optimized buffer transfer void inline transfer16(void *buf, size_t count) {transfer16(buf, buf, count);} void inline transfer32(void *buf, size_t count) {transfer32(buf, buf, count);} void transfer16(const void * buf, void * retbuf, size_t count); void transfer32(const void * buf, void * retbuf, size_t count); }; #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 ================================================ FILE: firmware/3.0/lib/SdFat/src/BufferedPrint.h ================================================ /** * Copyright (c) 2011-2021 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/DigitalIO/DigitalPin.h ================================================ /* 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 /** @} */ ================================================ FILE: firmware/3.0/lib/SdFat/src/DigitalIO/SoftSPI.h ================================================ /* 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 /** @} */ ================================================ FILE: firmware/3.0/lib/SdFat/src/DigitalIO/boards/AvrDevelopersGpioPinMap.h ================================================ #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 ================================================ FILE: firmware/3.0/lib/SdFat/src/DigitalIO/boards/BobuinoGpioPinMap.h ================================================ #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 ================================================ FILE: firmware/3.0/lib/SdFat/src/DigitalIO/boards/GpioPinMap.h ================================================ /* 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/DigitalIO/boards/LeonardoGpioPinMap.h ================================================ #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 ================================================ FILE: firmware/3.0/lib/SdFat/src/DigitalIO/boards/MegaGpioPinMap.h ================================================ #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 ================================================ FILE: firmware/3.0/lib/SdFat/src/DigitalIO/boards/SleepingBeautyGpioPinMap.h ================================================ #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 ================================================ FILE: firmware/3.0/lib/SdFat/src/DigitalIO/boards/Standard1284GpioPinMap.h ================================================ #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 ================================================ FILE: firmware/3.0/lib/SdFat/src/DigitalIO/boards/Teensy2GpioPinMap.h ================================================ #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 ================================================ FILE: firmware/3.0/lib/SdFat/src/DigitalIO/boards/Teensy2ppGpioPinMap.h ================================================ #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 ================================================ FILE: firmware/3.0/lib/SdFat/src/DigitalIO/boards/UnoGpioPinMap.h ================================================ #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 ================================================ FILE: firmware/3.0/lib/SdFat/src/DigitalIO/readme.txt ================================================ Selected files from the DigitalIO library. https://github.com/greiman/DigitalIO ================================================ FILE: firmware/3.0/lib/SdFat/src/ExFatLib/ExFatConfig.h ================================================ /** * Copyright (c) 2011-2021 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 EXFAT_READ_ONLY #define EXFAT_READ_ONLY 0 #endif // EXFAT_READ_ONLY #endif // ExFatConfig_h ================================================ FILE: firmware/3.0/lib/SdFat/src/ExFatLib/ExFatDbg.cpp ================================================ /** * Copyright (c) 2011-2021 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 "../common/upcase.h" #include "ExFatLib.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 uint16_t hashDir(DirName_t* dir, uint16_t hash) { for (uint8_t i = 0; i < 30; i += 2) { uint16_t u = getLe16(dir->unicode + i); if (!u) { break; } uint16_t c = toUpcase(u); hash = ((hash << 15) | (hash >> 1)) + (c & 0XFF); hash = ((hash << 15) | (hash >> 1)) + (c >> 8); } return hash; } //------------------------------------------------------------------------------ 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) { uint16_t c = getLe16(dir->unicode + i); if (c == 0) break; if (c < 128) { pr->print(static_cast(c)); } else { pr->print("0x"); pr->print(c, HEX); } pr->print(' '); } 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 = dataCachePrepare(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 = dataCachePrepare(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 = dataCachePrepare(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 = dataCachePrepare(sector, FsCache::CACHE_FOR_READ); if (!cache) { pr->println(F("dmpSector failed")); return; } for (uint16_t i = 0; i < m_bytesPerSector; 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[FS_DIR_SIZE]; if (file->read(buf, FS_DIR_SIZE) != FS_DIR_SIZE) { 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 = dataCachePrepare(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 = hashDir(dirName, 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 = dataCachePrepare(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 = dataCachePrepare(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 = dataCachePrepare(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 = dataCachePrepare(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 ================================================ FILE: firmware/3.0/lib/SdFat/src/ExFatLib/ExFatFile.cpp ================================================ /** * Copyright (c) 2011-2021 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 "../common/FsUtf.h" #include "ExFatLib.h" //------------------------------------------------------------------------------ /** test for legal character. * * \param[in] c character to be tested. * * \return true for legal character else false. */ inline bool lfnLegalChar(uint8_t c) { #if USE_UTF8_LONG_NAMES return !lfnReservedChar(c); #else // USE_UTF8_LONG_NAMES return !(lfnReservedChar(c) || c & 0X80); #endif // USE_UTF8_LONG_NAMES } //------------------------------------------------------------------------------ uint8_t* ExFatFile::dirCache(uint8_t set, uint8_t options) { DirPos_t pos = m_dirPos; if (m_vol->dirSeek(&pos, FS_DIR_SIZE*set) != 1) { return nullptr; } return m_vol->dirCache(&pos, options); } //------------------------------------------------------------------------------ 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; } //------------------------------------------------------------------------------ bool ExFatFile::isBusy() { return m_vol->isBusy(); } //------------------------------------------------------------------------------ bool ExFatFile::open(const char* path, oflag_t oflag) { return open(ExFatVolume::cwv(), path, oflag); } //------------------------------------------------------------------------------ bool ExFatFile::open(ExFatVolume* vol, const char* path, oflag_t oflag) { return vol && open(vol->vwd(), path, oflag); } //------------------------------------------------------------------------------ bool ExFatFile::open(ExFatFile* dirFile, const char* 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 (!openPrivate(dirFile, &fname, O_RDONLY)) { DBG_WARN_MACRO; goto fail; } tmpDir = *this; dirFile = &tmpDir; close(); } return openPrivate(dirFile, &fname, oflag); fail: return false; } //------------------------------------------------------------------------------ bool ExFatFile::open(ExFatFile* dirFile, uint32_t index, oflag_t oflag) { if (dirFile->seekSet(FS_DIR_SIZE*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 openPrivate(dir, nullptr, oflag); fail: return false; } //------------------------------------------------------------------------------ bool ExFatFile::openPrivate(ExFatFile* dir, ExName_t* fname, oflag_t oflag) { int n; uint8_t modeFlags; 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[FS_DIR_SIZE]; uint8_t freeCount = 0; uint8_t freeNeed = 3; bool inSet = false; // error if already open, no access mode, or no directory. 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 (fname) { freeNeed = 2 + (fname->nameLength + 14)/15; dir->rewind(); } while (1) { n = dir->read(buf, FS_DIR_SIZE); if (n == 0) { goto create; } if (n != FS_DIR_SIZE) { DBG_FAIL_MACRO; goto fail; } if (!(buf[0] & 0x80)) { // Unused entry. if (freeCount == 0) { freePos.position = dir->curPosition() - FS_DIR_SIZE; freePos.cluster = dir->curCluster(); } if (freeCount < freeNeed) { freeCount++; } if (!buf[0]) { if (fname) { goto create; } // Likely openNext call. DBG_WARN_MACRO; goto fail; } inSet = false; } else if (!inSet) { if (freeCount < freeNeed) { freeCount = 0; } if (buf[0] != EXFAT_TYPE_FILE) { continue; } inSet = true; 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() - FS_DIR_SIZE; m_dirPos.isContiguous = dir->isContiguous(); } else if (buf[0] == EXFAT_TYPE_STREAM) { dirStream = reinterpret_cast(buf); m_flags = modeFlags; if (dirStream->flags & EXFAT_FLAG_CONTIGUOUS) { m_flags |= FILE_FLAG_CONTIGUOUS; } m_validLength = getLe64(dirStream->validLength); m_firstCluster = getLe32(dirStream->firstCluster); m_dataLength = getLe64(dirStream->dataLength); if (!fname) { goto found; } fname->reset(); if (fname->nameLength != dirStream->nameLength || fname->nameHash != getLe16(dirStream->nameHash)) { inSet = false; } } else if (buf[0] == EXFAT_TYPE_NAME) { dirName = reinterpret_cast(buf); if (!cmpName(dirName, fname)) { inSet = false; continue; } if (fname->atEnd()) { goto found; } } else { inSet = false; } } 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() || EXFAT_READ_ONLY) { DBG_FAIL_MACRO; goto fail; } } #if !EXFAT_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 // !EXFAT_READ_ONLY return true; create: #if EXFAT_READ_ONLY DBG_FAIL_MACRO; goto fail; #else // EXFAT_READ_ONLY // don't create unless O_CREAT and write if (!(oflag & O_CREAT) || !(modeFlags & FILE_FLAG_WRITE) || !fname) { DBG_WARN_MACRO; goto fail; } while (freeCount < freeNeed) { n = dir->read(buf, FS_DIR_SIZE); if (n == 0) { curCluster = dir->m_curCluster; if (!dir->addDirCluster()) { DBG_FAIL_MACRO; goto fail; } dir->m_curCluster = curCluster; continue; } if (n != FS_DIR_SIZE) { DBG_FAIL_MACRO; goto fail; } if (freeCount == 0) { freePos.position = dir->curPosition() - FS_DIR_SIZE; 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; fname->reset(); for (uint8_t i = 0; i < freeNeed; i++) { cache = dirCache(i, FsCache::CACHE_FOR_WRITE); if (!cache || (cache[0] & 0x80)) { DBG_FAIL_MACRO; goto fail; } memset(cache, 0 , FS_DIR_SIZE); 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; m_flags = modeFlags | FILE_FLAG_DIR_DIRTY; dirStream->nameLength = fname->nameLength; setLe16(dirStream->nameHash, fname->nameHash); } else { dirName = reinterpret_cast(cache); dirName->type = EXFAT_TYPE_NAME; for (size_t k = 0; k < 15; k++) { if (fname->atEnd()) { break; } uint16_t u = fname->get16(); setLe16(dirName->unicode + 2*k, u); } } } return sync(); #endif // EXFAT_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 char* path, ExName_t* fname, const char** ptr) { // Skip leading spaces. while (*path == ' ') { path++; } fname->begin = path; fname->end = path; while (*path && !isDirSeparator(*path)) { uint8_t c = *path++; if (!lfnLegalChar(c)) { DBG_FAIL_MACRO; goto fail; } if (c != '.' && c != ' ') { // Need to trim trailing dots spaces. fname->end = path; } } // Advance to next path component. for (; *path == ' ' || isDirSeparator(*path); path++) {} *ptr = path; return hashName(fname); fail: return false; } //------------------------------------------------------------------------------ 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->dataCachePrepare(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 char* 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; } ================================================ FILE: firmware/3.0/lib/SdFat/src/ExFatLib/ExFatFile.h ================================================ /** * Copyright (c) 2011-2021 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 "../common/FsDateTime.h" #include "../common/FsApiConstants.h" #include "../common/FmtNumber.h" #include "../common/FsName.h" #include "ExFatPartition.h" class ExFatVolume; //------------------------------------------------------------------------------ /** Expression for path name separator. */ #define isDirSeparator(c) ((c) == '/') //------------------------------------------------------------------------------ /** * \class ExName_t * \brief Internal type for file name - do not use in user apps. */ class ExName_t : public FsName { public: /** Length of UTF-16 name */ size_t nameLength; /** Hash for UTF-16 name */ uint16_t nameHash; }; //------------------------------------------------------------------------------ /** * \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/FS_DIR_SIZE;} /** 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) { 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. * * \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(char* name, size_t size) { #if USE_UTF8_LONG_NAMES return getName8(name, size); #else // USE_UTF8_LONG_NAMES return getName7(name, size); #endif // USE_UTF8_LONG_NAMES } /** * Get a file's ASCII name followed by a zero. * * \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 getName7(char* name, size_t size); /** * Get a file's UTF-8 name followed by a zero. * * \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 getName8(char* name, size_t size); /** \return value of writeError */ bool getWriteError() const { return isOpen() ? m_error & WRITE_ERROR : true; } /** * Check for FsBlockDevice 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 char* 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 char* 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 char* path, oflag_t 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 char*, 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 char* path, oflag_t 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 length for success or zero for failure. */ size_t printName(print_t* pr) { #if USE_UTF8_LONG_NAMES return printName8(pr); #else // USE_UTF8_LONG_NAMES return printName7(pr); #endif // USE_UTF8_LONG_NAMES } /** Print a file's ASCII name * * \param[in] pr Print stream for output. * * \return true for success or false for failure. */ size_t printName7(print_t* pr); /** Print a file's UTF-8 name * * \param[in] pr Print stream for output. * * \return true for success or false for failure. */ size_t printName8(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 char* 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 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(ExFatFile* 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(); /** 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); /** \return directory set count */ uint8_t setCount() const {return m_setCount;} /** 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. If an error occurs, write() returns zero and writeError is set. */ 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 length for success or zero for failure. */ size_t printName() { return ExFatFile::printName(&Serial); } #endif // ENABLE_ARDUINO_SERIAL private: /** ExFatVolume allowed access to private members. */ friend class ExFatVolume; bool addCluster(); bool addDirCluster(); bool cmpName(const DirName_t* dirName, ExName_t* fname); uint8_t* dirCache(uint8_t set, uint8_t options); bool hashName(ExName_t* fname); bool mkdir(ExFatFile* parent, ExName_t* fname); bool openPrivate(ExFatFile* dir, ExName_t* fname, oflag_t oflag); bool parsePathName(const char* path, ExName_t* fname, const char** 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/ExFatLib/ExFatFilePrint.cpp ================================================ /** * Copyright (c) 2011-2021 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 "ExFatLib.h" #include "../common/FsUtf.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::printName7(print_t* pr) { DirName_t* dn; size_t n = 0; uint8_t in; uint8_t buf[15]; if (!isOpen()) { DBG_FAIL_MACRO; goto fail; } for (uint8_t is = 2; is <= m_setCount; is++) { dn = reinterpret_cast (dirCache(is, 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; } //------------------------------------------------------------------------------ size_t ExFatFile::printName8(print_t *pr) { DirName_t* dn; uint16_t hs = 0; uint32_t cp; size_t n = 0; uint8_t in; char buf[5]; if (!isOpen()) { DBG_FAIL_MACRO; goto fail; } for (uint8_t is = 2; is <= m_setCount; is++) { dn = reinterpret_cast (dirCache(is, 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 (hs) { if (!FsUtf::isLowSurrogate(c)) { DBG_FAIL_MACRO; goto fail; } cp = FsUtf::u16ToCp(hs, c); hs = 0; } else if (!FsUtf::isSurrogate(c)) { if (c == 0) { break; } cp = c; } else if (FsUtf::isHighSurrogate(c)) { hs = c; continue; } else { DBG_FAIL_MACRO; goto fail; } char* str = FsUtf::cpToMb(cp, buf, buf + sizeof(buf)); if (!str) { DBG_FAIL_MACRO; goto fail; } n += pr->write(buf, str - buf); } } return n; fail: return 0; } ================================================ FILE: firmware/3.0/lib/SdFat/src/ExFatLib/ExFatFileWrite.cpp ================================================ /** * Copyright (c) 2011-2021 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 "ExFatLib.h" //============================================================================== #if EXFAT_READ_ONLY bool ExFatFile::mkdir(ExFatFile* parent, const char* 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 char* newPath) { (void)newPath; return false; } bool ExFatFile::rename(ExFatFile* dirFile, const char* 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 // EXFAT_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; } sector = m_vol->clusterStartSector(m_curCluster); for (uint32_t i = 0; i < m_vol->sectorsPerCluster(); i++) { cache = m_vol->dataCachePrepare(sector + i, FsCache::CACHE_RESERVE_FOR_WRITE); if (!cache) { DBG_FAIL_MACRO; goto fail; } memset(cache, 0, m_vol->bytesPerSector()); } 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 char* 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 (!openPrivate(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 (!openPrivate(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() { 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 is = 0; is <= m_setCount; is++) { cache = dirCache(is, 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 char* newPath) { return rename(m_vol->vwd(), newPath); } //------------------------------------------------------------------------------ bool ExFatFile::rename(ExFatFile* dirFile, const char* 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[FS_DIR_SIZE]; // must be open subdirectory if (!isSubDir()) { DBG_FAIL_MACRO; goto fail; } rewind(); // make sure directory is empty while (1) { n = read(dir, FS_DIR_SIZE); if (n == 0) { break; } if (n != FS_DIR_SIZE || 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; for (uint8_t is = 0; is <= m_setCount ; is++) { cache = dirCache(is, FsCache::CACHE_FOR_READ); if (!cache) { DBG_FAIL_MACRO; goto fail; } switch (cache[0]) { case EXFAT_TYPE_FILE: df = reinterpret_cast(cache); 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); } 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; uint16_t date; uint16_t time; uint8_t ms10; 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; for (uint8_t is = 0; is <= m_setCount; is++) { cache = dirCache(is, FsCache::CACHE_FOR_READ); if (!cache) { DBG_FAIL_MACRO; goto fail; } switch (cache[0]) { case EXFAT_TYPE_FILE: df = reinterpret_cast(cache); 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); } 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->dataCachePrepare(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 0; } #endif // EXFAT_READ_ONLY ================================================ FILE: firmware/3.0/lib/SdFat/src/ExFatLib/ExFatFormatter.cpp ================================================ /** * Copyright (c) 2011-2021 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 "../common/upcase.h" #include "ExFatLib.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(FsBlockDevice* 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; } ================================================ FILE: firmware/3.0/lib/SdFat/src/ExFatLib/ExFatFormatter.h ================================================ /** * Copyright (c) 2011-2021 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 "../common/FsBlockDevice.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(FsBlockDevice* 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; FsBlockDevice* m_dev; uint8_t* m_secBuf; }; #endif // ExFatFormatter_h ================================================ FILE: firmware/3.0/lib/SdFat/src/ExFatLib/ExFatLib.h ================================================ /** * Copyright (c) 2011-2021 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 "ExFatFormatter.h" #endif // ExFatLib_h ================================================ FILE: firmware/3.0/lib/SdFat/src/ExFatLib/ExFatName.cpp ================================================ /** * Copyright (c) 2011-2021 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 "ExFatName.cpp" #include "../common/DebugMacros.h" #include "../common/upcase.h" #include "../common/FsUtf.h" #include "ExFatLib.h" //------------------------------------------------------------------------------ static char toUpper(char c) { return 'a' <= c && c <= 'z' ? c - 'a' + 'A' : c; } //------------------------------------------------------------------------------ inline uint16_t exFatHash(char c, uint16_t hash) { uint8_t u = toUpper(c); hash = ((hash << 15) | (hash >> 1)) + u; hash = ((hash << 15) | (hash >> 1)); return hash; } //------------------------------------------------------------------------------ inline uint16_t exFatHash(uint16_t u, uint16_t hash) { uint16_t c = toUpcase(u); hash = ((hash << 15) | (hash >> 1)) + (c & 0XFF); hash = ((hash << 15) | (hash >> 1)) + (c >> 8); return hash; } //------------------------------------------------------------------------------ bool ExFatFile::cmpName(const DirName_t* dirName, ExName_t* fname) { for (uint8_t i = 0; i < 15; i++) { uint16_t u = getLe16(dirName->unicode + 2*i); if (fname->atEnd()) { return u == 0; } #if USE_UTF8_LONG_NAMES uint16_t cp = fname->get16(); if (toUpcase(cp) != toUpcase(u)) { return false; } #else // USE_UTF8_LONG_NAMES char c = fname->getch(); if (u >= 0x7F || toUpper(c) != toUpper(u)) { return false; } #endif // USE_UTF8_LONG_NAMES } return true; } //------------------------------------------------------------------------------ size_t ExFatFile::getName7(char* name, size_t count) { DirName_t* dn; size_t n = 0; if (!isOpen()) { DBG_FAIL_MACRO; goto fail; } for (uint8_t is = 2; is <= m_setCount; is++) { dn = reinterpret_cast (dirCache(is, 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) { goto done; } if ((n + 1) >= count) { DBG_FAIL_MACRO; goto fail; } name[n++] = c < 0X7F ? c : '?'; } } done: name[n] = 0; return n; fail: *name = 0; return 0; } //------------------------------------------------------------------------------ size_t ExFatFile::getName8(char* name, size_t count) { char* end = name + count; char* str = name; char* ptr; DirName_t* dn; uint16_t hs = 0; uint32_t cp; if (!isOpen()) { DBG_FAIL_MACRO; goto fail; } for (uint8_t is = 2; is <= m_setCount; is++) { dn = reinterpret_cast (dirCache(is, 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 (hs) { if (!FsUtf::isLowSurrogate(c)) { DBG_FAIL_MACRO; goto fail; } cp = FsUtf::u16ToCp(hs, c); hs = 0; } else if (!FsUtf::isSurrogate(c)) { if (c == 0) { goto done; } cp = c; } else if (FsUtf::isHighSurrogate(c)) { hs = c; continue; } else { DBG_FAIL_MACRO; goto fail; } // Save space for zero byte. ptr = FsUtf::cpToMb(cp, str, end - 1); if (!ptr) { DBG_FAIL_MACRO; goto fail; } str = ptr; } } done: *str = '\0'; return str - name; fail: *name = 0; return 0; } //------------------------------------------------------------------------------ bool ExFatFile::hashName(ExName_t* fname) { uint16_t hash = 0; fname->reset(); #if USE_UTF8_LONG_NAMES fname->nameLength = 0; while (!fname->atEnd()) { uint16_t u = fname->get16(); if (u == 0XFFFF) { DBG_FAIL_MACRO; goto fail; } hash = exFatHash(u, hash); fname->nameLength++; } #else // USE_UTF8_LONG_NAMES while (!fname->atEnd()) { // Convert to byte for smaller exFatHash. char c = fname->getch(); hash = exFatHash(c, hash); } fname->nameLength = fname->end - fname->begin; #endif // USE_UTF8_LONG_NAMES fname->nameHash = hash; if (!fname->nameLength || fname->nameLength > EXFAT_MAX_NAME_LENGTH) { DBG_FAIL_MACRO; goto fail; } return true; fail: return false; } ================================================ FILE: firmware/3.0/lib/SdFat/src/ExFatLib/ExFatPartition.cpp ================================================ /** * Copyright (c) 2011-2021 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 "ExFatLib.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 = bitmapCachePrepare(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 = bitmapCachePrepare(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 = dataCachePrepare(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; } //------------------------------------------------------------------------------ // return -1 error, 0 EOC, 1 OK int8_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 = dataCachePrepare(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 = dataCachePrepare(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; 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 (status == 0 || (cluster + 1) != next) { 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 = dataCachePrepare(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(FsBlockDevice* 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 = dataCachePrepare(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 = dataCachePrepare(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; } //------------------------------------------------------------------------------ bool ExFatPartition::init(FsBlockDevice* dev, uint32_t firstSector, uint32_t numSectors) { uint32_t volStart = firstSector; uint8_t* cache; pbs_t* pbs; BpbExFat_t* bpb; m_fatType = 0; m_blockDev = dev; cacheInit(m_blockDev); cache = dataCachePrepare(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(); } ================================================ FILE: firmware/3.0/lib/SdFat/src/ExFatLib/ExFatPartition.h ================================================ /** * Copyright (c) 2011-2021 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/FsBlockDevice.h" #include "../common/FsCache.h" #include "../common/FsStructs.h" /** Set EXFAT_READ_ONLY non-zero for read only */ #ifndef EXFAT_READ_ONLY #define EXFAT_READ_ONLY 0 #endif // EXFAT_READ_ONLY /** Type for exFAT partition */ const uint8_t FAT_TYPE_EXFAT = 64; class ExFatFile; //------------------------------------------------------------------------------ /** * \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; }; //============================================================================== /** * \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;} /** End access to volume * \return pointer to sector size buffer for format. */ uint8_t* end() { m_fatType = 0; return cacheClear(); } /** \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(FsBlockDevice* dev, uint8_t part); bool init(FsBlockDevice* dev, uint32_t firstSector, uint32_t numberSectors); /** * Check for device 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 uint32_t __attribute__((error("use sectorsPerCluster()"))) blocksPerCluster(); #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* bitmapCachePrepare(uint32_t sector, uint8_t option) { #if USE_EXFAT_BITMAP_CACHE return m_bitmapCache.prepare(sector, option); #else // USE_EXFAT_BITMAP_CACHE return m_dataCache.prepare(sector, option); #endif // USE_EXFAT_BITMAP_CACHE } void cacheInit(FsBlockDevice* 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* dataCachePrepare(uint32_t sector, uint8_t option) { return m_dataCache.prepare(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); int8_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 = 1 << m_bytesPerSectorShift; static const uint16_t m_sectorMask = m_bytesPerSector - 1; //---------------------------------------------------------------------------- #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; FsBlockDevice* m_blockDev; uint8_t m_fatType = 0; uint8_t m_sectorsPerClusterShift; }; #endif // ExFatPartition_h ================================================ FILE: firmware/3.0/lib/SdFat/src/ExFatLib/ExFatVolume.cpp ================================================ /** * Copyright (c) 2011-2021 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 "ExFatVolume.cpp" #include "../common/DebugMacros.h" #include "ExFatLib.h" ExFatVolume* ExFatVolume::m_cwv = nullptr; //----------------------------------------------------------------------------- bool ExFatVolume::chdir(const char* path) { ExFatFile dir; if (!dir.open(vwd(), path, O_RDONLY)) { DBG_FAIL_MACRO; goto fail; } if (!dir.isDir()) { DBG_FAIL_MACRO; goto fail; } m_vwd = dir; return true; fail: return false; } ================================================ FILE: firmware/3.0/lib/SdFat/src/ExFatLib/ExFatVolume.h ================================================ /** * Copyright (c) 2011-2021 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 "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(FsBlockDevice* 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; } bool begin(FsBlockDevice* dev, bool setCwv, uint32_t firstSector, uint32_t numSectors) { if (!init(dev, firstSector, numSectors)) { 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 char* 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 char* 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 char* 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 char* 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 char* 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 char* 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 char* oldPath, const char* 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 char* 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 char* 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 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 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 private: friend ExFatFile; static ExFatVolume* cwv() {return m_cwv;} ExFatFile* vwd() {return &m_vwd;} static ExFatVolume* m_cwv; ExFatFile m_vwd; }; #endif // ExFatVolume_h ================================================ FILE: firmware/3.0/lib/SdFat/src/ExFatLib/upcase.cpp ================================================ // this file no longer used ================================================ FILE: firmware/3.0/lib/SdFat/src/FatLib/FatDbg.cpp ================================================ /** * Copyright (c) 2011-2021 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 "FatLib.h" #ifndef DOXYGEN_SHOULD_SKIP_THIS //------------------------------------------------------------------------------ static uint16_t getLfnChar(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 void printHex(print_t* pr, uint8_t h) { if (h < 16) { pr->write('0'); } pr->print(h, HEX); } //------------------------------------------------------------------------------ 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); } //------------------------------------------------------------------------------ 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); } } } //------------------------------------------------------------------------------ template static void printHexLn(print_t* pr, Uint val) { printHex(pr, val); pr->println(); } //------------------------------------------------------------------------------ static bool printFatDir(print_t* pr, DirFat_t* dir) { DirLfn_t* ldir = reinterpret_cast(dir); if (!dir->name[0]) { pr->println(F("Unused")); return false; } else if (dir->name[0] == FAT_NAME_DELETED) { pr->println(F("Deleted")); } else if (isFileOrSubdir(dir)) { pr->print(F("SFN: ")); for (uint8_t i = 0; i < 11; i++) { printHex(pr, dir->name[i]); pr->write(' '); } pr->write(' '); pr->write(dir->name, 11); pr->println(); pr->print(F("attributes: 0X")); printHexLn(pr, dir->attributes); pr->print(F("caseFlags: 0X")); printHexLn(pr, dir->caseFlags); 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->print(F("LFN: ")); for (uint8_t i = 0; i < 13; i++) { uint16_t c = getLfnChar(ldir, i); if (15 < c && c < 128) { pr->print(static_cast(c)); } else { pr->print("0X"); pr->print(c, HEX); } pr->print(' '); } pr->println(); pr->print(F("order: 0X")); pr->println(ldir->order, HEX); pr->print(F("attributes: 0X")); pr->println(ldir->attributes, HEX); pr->print(F("checksum: 0X")); pr->println(ldir->checksum, HEX); } else { pr->println(F("Other")); } pr->println(); return true; } //------------------------------------------------------------------------------ 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 FatPartition::dmpDirSector(print_t* pr, uint32_t sector) { DirFat_t dir[16]; if (!cacheSafeRead(sector, reinterpret_cast(dir))) { pr->println(F("dmpDir failed")); return false; } for (uint8_t i = 0; i < 16; i++) { if (!printFatDir(pr, dir + i)) { return false; } } return true; } //------------------------------------------------------------------------------ bool FatPartition::dmpRootDir(print_t* pr, uint32_t n) { uint32_t sector; if (fatType() == 16) { sector = rootDirStart(); } else if (fatType() == 32) { sector = clusterStartSector(rootDirStart()); } else { pr->println(F("dmpRootDir failed")); return false; } return dmpDirSector(pr, sector + n); } //------------------------------------------------------------------------------ void FatPartition::dmpSector(print_t* pr, uint32_t sector, uint8_t bits) { uint8_t data[FatPartition::m_bytesPerSector]; if (!cacheSafeRead(sector, data)) { pr->println(F("dmpSector failed")); return; } for (uint16_t i = 0; i < m_bytesPerSector;) { 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++) { uint8_t* pc = fatCachePrepare(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 ? getLe32(pc + 4*k) : getLe16(pc + 2*k); printHex(pr, v); } pr->println(); } } #endif // DOXYGEN_SHOULD_SKIP_THIS ================================================ FILE: firmware/3.0/lib/SdFat/src/FatLib/FatFile.cpp ================================================ /** * Copyright (c) 2011-2021 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 "FatLib.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; uint8_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); for (uint8_t i = 0; i < m_vol->sectorsPerCluster(); i++) { pc = m_vol->dataCachePrepare(sector + i, FsCache::CACHE_RESERVE_FOR_WRITE); if (!pc) { DBG_FAIL_MACRO; goto fail; } memset(pc, 0, m_vol->bytesPerSector()); } // 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) { uint8_t* pc = m_vol->dataCachePrepare(m_dirSector, action); DirFat_t* dir = reinterpret_cast(pc); if (!dir) { DBG_FAIL_MACRO; goto fail; } return 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 FS_DIR_SIZE*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) { FatName_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, FatName_t* fname) { uint32_t sector; DirFat_t dot; DirFat_t* dir; uint8_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->dataCachePrepare(sector, FsCache::CACHE_FOR_WRITE); dir = reinterpret_cast(pc); if (!dir) { DBG_FAIL_MACRO; goto fail; } // copy '.' to sector memcpy(&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(&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; FatName_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_WARN_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++) { ldir = reinterpret_cast(dirFile->cacheDir(index - i)); 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::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::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()/FS_DIR_SIZE; 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; } //------------------------------------------------------------------------------ int FatFile::peek() { uint32_t curPosition = m_curPosition; uint32_t curCluster = m_curCluster; int c = read(); m_curPosition = curPosition; m_curCluster = curCluster; return c; } //------------------------------------------------------------------------------ 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::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 uint8_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 = FS_DIR_SIZE*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->dataCachePrepare(sector, FsCache::CACHE_FOR_READ); if (!pc) { DBG_FAIL_MACRO; goto fail; } uint8_t* src = pc + 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) { DBG_HALT_IF(m_curPosition & 0X1F); 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 += FS_DIR_SIZE - 1; } else { m_curPosition += FS_DIR_SIZE; } // 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; uint8_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->dataCachePrepare(sector, FsCache::CACHE_FOR_READ); dir = reinterpret_cast(pc); if (!dir) { DBG_FAIL_MACRO; goto fail; } memcpy(&entry, &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); uint8_t* pc = m_vol->dataCachePrepare(sector, FsCache::CACHE_FOR_WRITE); dir = reinterpret_cast(pc); if (!dir) { DBG_FAIL_MACRO; goto fail; } memcpy(&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/FS_DIR_SIZE; 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 <= FS_DIR_SIZE*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; return sync(); 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); uint8_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->dataCachePrepare(sector, cacheOption); if (!pc) { DBG_FAIL_MACRO; goto fail; } uint8_t* dst = pc + 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 0; } ================================================ FILE: firmware/3.0/lib/SdFat/src/FatLib/FatFile.h ================================================ /** * Copyright (c) 2011-2021 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 "../common/FmtNumber.h" #include "../common/FsApiConstants.h" #include "../common/FsDateTime.h" #include "../common/FsName.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) == '/') //------------------------------------------------------------------------------ /** * \class FatLfn_t * \brief Internal type for Long File Name - do not use in user apps. */ class FatLfn_t : public FsName { public: /** UTF-16 length of Long File Name */ size_t len; /** Position for sequence number. */ uint8_t seqPos; /** Flags for base and extension character case and LFN. */ uint8_t flags; /** Short File Name */ uint8_t sfn[11]; }; /** * \class FatSfn_t * \brief Internal type for Short 8.3 File Name - do not use in user apps. */ class FatSfn_t { public: /** Flags for base and extension character case and LFN. */ uint8_t flags; /** Short File Name */ uint8_t sfn[11]; }; #if USE_LONG_FILE_NAMES /** Internal class for file names */ typedef FatLfn_t FatName_t; #else // USE_LONG_FILE_NAMES /** Internal class for file names */ typedef FatSfn_t FatName_t; #endif // USE_LONG_FILE_NAMES /** 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; #if FNAME_FLAG_NEED_LFN & (FAT_CASE_LC_BASE || FAT_CASE_LC_EXT) #error FNAME_FLAG_NEED_LFN & (FAT_CASE_LC_BASE || FAT_CASE_LC_EXT) #endif // FNAME_FLAG_NEED_LFN & (FAT_CASE_LC_BASE || 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 ASCII name followed by a zero. * * \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 getName7(char* name, size_t size); /** * Get a file's UTF-8 name followed by a zero. * * \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 getName8(char* name, size_t size); #ifndef DOXYGEN_SHOULD_SKIP_THIS size_t __attribute__((error("use getSFN(name, size)"))) getSFN(char* name); #endif // DOXYGEN_SHOULD_SKIP_THIS /** * 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 should be at least 13 bytes long. * \param[in] size size of name array. * \return true for success or false for failure. */ size_t getSFN(char* name, size_t size); /** \return value of writeError */ bool getWriteError() const { return isOpen() ? m_error & WRITE_ERROR : true; } /** * Check for device 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;} /** 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); /** 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 existing file wih Short 8.3 names. * \param[in] path with short 8.3 names. * * the purpose of this function is to save flash on Uno * and other small boards. * * Directories will be opened O_RDONLY, files O_RDWR. * \return true for success or false for failure. */ bool openExistingSFN(const char* path); /** 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 length for success or zero for failure. */ size_t printName(print_t* pr); /** Print a file's ASCII name * * \param[in] pr Print stream for output. * * \return true for success or false for failure. */ size_t printName7(print_t* pr); /** Print a file's UTF-8 name * * \param[in] pr Print stream for output. * * \return true for success or false for failure. */ size_t printName8(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 zero and writeError is set. * */ 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 length for success or zero 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* cacheDir(uint16_t index) { return seekSet(32UL*index) ? readDirCache() : nullptr; } DirFat_t* cacheDirEntry(uint8_t action); bool cmpName(uint16_t index, FatLfn_t* fname, uint8_t lfnOrd); bool createLFN(uint16_t index, FatLfn_t* fname, uint8_t lfnOrd); uint16_t getLfnChar(DirLfn_t* ldir, uint8_t i); uint8_t lfnChecksum(uint8_t* name) { uint8_t sum = 0; for (uint8_t i = 0; i < 11; i++) { sum = (((sum & 1) << 7) | (sum >> 1)) + name[i]; } return sum; } static bool makeSFN(FatLfn_t* fname); bool makeUniqueSfn(FatLfn_t* fname); bool openCluster(FatFile* file); bool parsePathName(const char* str, FatLfn_t* fname, const char** ptr); bool parsePathName(const char* str, FatSfn_t* fname, const char** ptr); bool mkdir(FatFile* parent, FatName_t* fname); bool open(FatFile* dirFile, FatLfn_t* fname, oflag_t oflag); bool open(FatFile* dirFile, FatSfn_t* fname, oflag_t oflag); bool openSFN(FatSfn_t* fname); 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/FatLib/FatFileLFN.cpp ================================================ /** * Copyright (c) 2011-2021 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 "../common/upcase.h" #include "../common/FsUtf.h" #include "FatLib.h" #if USE_LONG_FILE_NAMES //------------------------------------------------------------------------------ static bool isLower(char c) { return 'a' <= c && c <= 'z'; } //------------------------------------------------------------------------------ static bool isUpper(char c) { return 'A' <= c && c <= 'Z'; } //------------------------------------------------------------------------------ // A bit smaller than toupper in AVR 328. inline char toUpper(char c) { return isLower(c) ? c - 'a' + 'A' : c; } //------------------------------------------------------------------------------ /** * 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 putLfnChar(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); } } //------------------------------------------------------------------------------ // Daniel Bernstein University of Illinois at Chicago. // Original had + instead of ^ __attribute__((unused)) static uint16_t Bernstein(const char* bgn, const char* end, uint16_t hash) { while (bgn < end) { // hash = hash * 33 ^ str[i]; hash = ((hash << 5) + hash) ^ (*bgn++); } return hash; } //============================================================================== bool FatFile::cmpName(uint16_t index, FatLfn_t* fname, uint8_t lfnOrd) { FatFile dir = *this; DirLfn_t* ldir; fname->reset(); for (uint8_t order = 1; order <= lfnOrd; order++) { ldir = reinterpret_cast(dir.cacheDir(index - order)); if (!ldir) { DBG_FAIL_MACRO; goto fail; } // These should be checked in caller. DBG_HALT_IF(ldir->attributes != FAT_ATTRIB_LONG_NAME); DBG_HALT_IF(order != (ldir->order & 0X1F)); for (uint8_t i = 0; i < 13; i++) { uint16_t u = getLfnChar(ldir, i); if (fname->atEnd()) { return u == 0; } #if USE_UTF8_LONG_NAMES uint16_t cp = fname->get16(); // Make sure caller checked for valid UTF-8. DBG_HALT_IF(cp == 0XFFFF); if (toUpcase(u) != toUpcase(cp)) { return false; } #else // USE_UTF8_LONG_NAMES if (u > 0X7F || toUpper(u) != toUpper(fname->getch())) { return false; } #endif // USE_UTF8_LONG_NAMES } } return true; fail: return false; } //------------------------------------------------------------------------------ bool FatFile::createLFN(uint16_t index, FatLfn_t* fname, uint8_t lfnOrd) { FatFile dir = *this; DirLfn_t* ldir; uint8_t checksum = lfnChecksum(fname->sfn); uint8_t fc = 0; fname->reset(); for (uint8_t order = 1; order <= lfnOrd; order++) { ldir = reinterpret_cast(dir.cacheDir(index - order)); if (!ldir) { DBG_FAIL_MACRO; goto fail; } dir.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 = checksum; setLe16(ldir->mustBeZero2, 0); for (uint8_t i = 0; i < 13; i++) { uint16_t cp; if (fname->atEnd()) { cp = fc++ ? 0XFFFF : 0; } else { cp = fname->get16(); // Verify caller checked for valid UTF-8. DBG_HALT_IF(cp == 0XFFFF); } putLfnChar(ldir, i, cp); } } return true; fail: return false; } //------------------------------------------------------------------------------ bool FatFile::makeSFN(FatLfn_t* fname) { bool is83; // char c; uint8_t c; uint8_t bit = FAT_CASE_LC_BASE; uint8_t lc = 0; uint8_t uc = 0; uint8_t i = 0; uint8_t in = 7; const char* dot; const char* end = fname->end; const char* ptr = fname->begin; // Assume not zero length. DBG_HALT_IF(end == ptr); // Assume blanks removed from start and end. DBG_HALT_IF(*ptr == ' ' || *(end - 1) == ' ' || *(end - 1) == '.'); // Blank file short name. for (uint8_t k = 0; k < 11; k++) { fname->sfn[k] = ' '; } // Not 8.3 if starts with dot. is83 = *ptr == '.' ? false : true; // Skip leading dots. for (; *ptr == '.'; ptr++) {} // Find last dot. for (dot = end - 1; dot > ptr && *dot != '.'; dot--) {} for (; ptr < end; ptr++) { c = *ptr; if (c == '.' && ptr == 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 (sfnReservedChar(c)) { is83 = false; // Skip UTF-8 trailing characters. if ((c & 0XC0) == 0X80) { continue; } c = '_'; } if (i > in) { is83 = false; if (in == 10 || ptr > dot) { // Done - extension longer than three characters or no extension. break; } // Skip to dot. ptr = dot - 1; continue; } if (isLower(c)) { c += 'A' - 'a'; lc |= bit; } else if (isUpper(c)) { uc |= bit; } fname->sfn[i++] = c; if (i < 7) { fname->seqPos = i; } } } if (fname->sfn[0] == ' ') { DBG_HALT_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::makeUniqueSfn(FatLfn_t* fname) { const uint8_t FIRST_HASH_SEQ = 2; // min value is 2 uint8_t pos = fname->seqPos; DirFat_t* dir; uint16_t hex = 0; DBG_HALT_IF(!(fname->flags & FNAME_FLAG_LOST_CHARS)); DBG_HALT_IF(fname->sfn[pos] != '~' && fname->sfn[pos + 1] != '1'); for (uint8_t seq = FIRST_HASH_SEQ; seq < 100; seq++) { DBG_WARN_IF(seq > FIRST_HASH_SEQ); #ifdef USE_LFN_HASH hex = Bernstein(fname->begin, fname->end, seq); #else hex += millis(); #endif 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; } //------------------------------------------------------------------------------ bool FatFile::open(FatFile* dirFile, FatLfn_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; uint8_t nameOrd; uint16_t freeIndex = 0; uint16_t curIndex; uint16_t date; uint16_t time; DirFat_t* dir; DirLfn_t* ldir; auto vol = dirFile->m_vol; if (!dirFile->isDir() || isOpen()) { DBG_FAIL_MACRO; goto fail; } // Number of directory entries needed. nameOrd = (fname->len + 12)/13; freeNeed = fname->flags & FNAME_FLAG_NEED_LFN ? 1 + nameOrd : 1; dirFile->rewind(); while (1) { curIndex = dirFile->m_curPosition/FS_DIR_SIZE; dir = dirFile->readDirCache(); 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) { order = ldir->order & 0X1F; if (order != nameOrd || (ldir->order & FAT_ORDER_LAST_LONG_ENTRY) == 0) { continue; } lfnOrd = nameOrd; checksum = ldir->checksum; } else if (ldir->order != --order || checksum != ldir->checksum) { lfnOrd = 0; continue; } if (order == 1) { if (!dirFile->cmpName(curIndex + 1, fname, lfnOrd)) { lfnOrd = 0; } } } 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_WARN_MACRO; goto fail; } // Keep found entries or start at current index if no free entries found. 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; } freeFound += vol->dirEntriesPerCluster(); } if (fnameFound) { if (!dirFile->makeUniqueSfn(fname)) { goto fail; } } lfnOrd = freeNeed - 1; curIndex = freeIndex + lfnOrd; if (!dirFile->createLFN(curIndex, fname, lfnOrd)) { goto fail; } dir = dirFile->cacheDir(curIndex); 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. vol->cacheDirty(); open: // open entry in cache. if (!openCachedEntry(dirFile, curIndex, oflag, lfnOrd)) { DBG_FAIL_MACRO; goto fail; } return true; fail: return false; } //------------------------------------------------------------------------------ bool FatFile::parsePathName(const char* path, FatLfn_t* fname, const char** ptr) { size_t len = 0; // Skip leading spaces. while (*path == ' ') { path++; } fname->begin = path; while (*path && !isDirSeparator(*path)) { #if USE_UTF8_LONG_NAMES uint32_t cp; // Allow end = path + 4 since path is zero terminated. path = FsUtf::mbToCp(path, path + 4, &cp); if (!path) { DBG_FAIL_MACRO; goto fail; } len += cp <= 0XFFFF ? 1 : 2; if (cp < 0X80 && lfnReservedChar(cp)) { DBG_FAIL_MACRO; goto fail; } #else // USE_UTF8_LONG_NAMES uint8_t cp = *path++; if (cp >= 0X80 || lfnReservedChar(cp)) { DBG_FAIL_MACRO; goto fail; } len++; #endif // USE_UTF8_LONG_NAMES if (cp != '.' && cp != ' ') { // Need to trim trailing dots spaces. fname->len = len; fname->end = path; } } if (!fname->len || fname->len > FAT_MAX_LFN_LENGTH) { DBG_FAIL_MACRO; goto fail; } // Advance to next path component. for (; *path == ' ' || isDirSeparator(*path); path++) {} *ptr = path; return makeSFN(fname); fail: return false; } //------------------------------------------------------------------------------ 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++) { ldir = reinterpret_cast(dirFile.cacheDir(m_dirIndex - order)); 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; } #endif // #if USE_LONG_FILE_NAMES ================================================ FILE: firmware/3.0/lib/SdFat/src/FatLib/FatFilePrint.cpp ================================================ /** * Copyright (c) 2011-2021 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 "FatLib.h" //------------------------------------------------------------------------------ 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); } ================================================ FILE: firmware/3.0/lib/SdFat/src/FatLib/FatFileSFN.cpp ================================================ /** * Copyright (c) 2011-2021 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 "FatLib.h" //------------------------------------------------------------------------------ // open with filename in fname #define SFN_OPEN_USES_CHKSUM 0 bool FatFile::open(FatFile* dirFile, FatSfn_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 = 0; uint16_t index = 0; DirFat_t* dir; DirLfn_t* ldir; dirFile->rewind(); while (true) { dir = 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_DELETED || dir->name[0] == FAT_NAME_FREE) { if (!emptyFound) { emptyIndex = index; emptyFound = true; } if (dir->name[0] == FAT_NAME_FREE) { break; } lfnOrd = 0; } 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; } } dir = reinterpret_cast(dirFile->cacheDir(index)); 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; } //------------------------------------------------------------------------------ bool FatFile::openExistingSFN(const char* path) { FatSfn_t fname; auto vol = FatVolume::cwv(); while (*path == '/') { path++; } if (*path == 0) { return openRoot(vol); } *this = *vol->vwd(); do { if (!parsePathName(path, &fname, &path)) { DBG_FAIL_MACRO; goto fail; } if (!openSFN(&fname)) { DBG_FAIL_MACRO; goto fail; } } while (*path); return true; fail: return false; } //------------------------------------------------------------------------------ bool FatFile::openSFN(FatSfn_t* fname) { DirFat_t dir; DirLfn_t* ldir; auto vol = m_vol; uint8_t lfnOrd = 0; if (!isDir()) { DBG_FAIL_MACRO; goto fail; } while (true) { if (read(&dir, 32) != 32) { DBG_FAIL_MACRO; goto fail; } if (dir.name[0] == 0) { DBG_FAIL_MACRO; goto fail; } if (isFileOrSubdir(&dir) && memcmp(fname->sfn, dir.name, 11) == 0) { uint16_t dirIndex = (m_curPosition - 32) >> 5; uint32_t dirCluster = m_firstCluster; memset(this, 0 , sizeof(FatFile)); m_attributes = dir.attributes & FILE_ATTR_COPY; if (isFileDir(&dir)) { m_attributes |= FILE_ATTR_FILE; } m_lfnOrd = lfnOrd; m_firstCluster = (uint32_t)getLe16(dir.firstClusterHigh) << 16; m_firstCluster |= getLe16(dir.firstClusterLow); m_fileSize = getLe32(dir.fileSize); m_flags = isFile() ? FILE_FLAG_READ | FILE_FLAG_WRITE : FILE_FLAG_READ; m_vol = vol; m_dirCluster = dirCluster; m_dirSector = m_vol->cacheSectorNumber(); m_dirIndex = dirIndex; return true; } else if (isLongName(&dir)) { ldir = reinterpret_cast(&dir); if (ldir->order & FAT_ORDER_LAST_LONG_ENTRY) { lfnOrd = ldir->order & 0X1F; } } else { lfnOrd = 0; } } fail: return false; } //------------------------------------------------------------------------------ // format directory name field from a 8.3 name string bool FatFile::parsePathName(const char* path, FatSfn_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 (sfnReservedChar(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; } #if !USE_LONG_FILE_NAMES //------------------------------------------------------------------------------ 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 = 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/FatLib/FatFormatter.cpp ================================================ /** * Copyright (c) 2011-2021 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 "FatLib.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(FsBlockDevice* 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; } //------------------------------------------------------------------------------ struct initFatDirState { uint8_t * buffer; print_t * pr; uint16_t count; uint16_t dotcount; }; static const uint8_t * initFatDirCallback(uint32_t sector, void *context) { struct initFatDirState * state = (struct initFatDirState *)context; if (state->pr && ++state->count >= state->dotcount) { state->pr->write("."); state->count = 0; } return state->buffer; } bool FatFormatter::initFatDir(uint8_t fatType, uint32_t sectorCount) { size_t n; memset(m_secBuf, 0, BYTES_PER_SECTOR); writeMsg("Writing FAT "); struct initFatDirState state; state.buffer = m_secBuf; state.pr = m_pr; state.count = 0; state.dotcount = sectorCount/32; if (!m_dev->writeSectorsCallback(m_fatStart + 1, sectorCount - 1, initFatDirCallback, &state)) { return false; } 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); } ================================================ FILE: firmware/3.0/lib/SdFat/src/FatLib/FatFormatter.h ================================================ /** * Copyright (c) 2011-2021 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 "../common/SysCall.h" #include "../common/FsBlockDevice.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(FsBlockDevice* 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; FsBlockDevice* 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/FatLib/FatLib.h ================================================ /** * Copyright (c) 2011-2021 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 "FatFormatter.h" #endif // FatLib_h ================================================ FILE: firmware/3.0/lib/SdFat/src/FatLib/FatName.cpp ================================================ /** * Copyright (c) 2011-2021 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 "FatName.cpp" #include "../common/DebugMacros.h" #include "../common/FsUtf.h" #include "FatLib.h" //------------------------------------------------------------------------------ uint16_t FatFile::getLfnChar(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); } DBG_HALT_IF(i >= 13); return 0; } //------------------------------------------------------------------------------ size_t FatFile::getName(char* name, size_t size) { #if !USE_LONG_FILE_NAMES return getSFN(name, size); #elif USE_UTF8_LONG_NAMES return getName8(name, size); #else return getName7(name, size); #endif // !USE_LONG_FILE_NAMES } //------------------------------------------------------------------------------ size_t FatFile::getName7(char* name, size_t size) { FatFile dir; DirLfn_t* ldir; size_t n = 0; if (!isOpen()) { DBG_FAIL_MACRO; goto fail; } if (!isLFN()) { return getSFN(name, size); } if (!dir.openCluster(this)) { DBG_FAIL_MACRO; goto fail; } for (uint8_t order = 1; order <= m_lfnOrd; order++) { ldir = reinterpret_cast(dir.cacheDir(m_dirIndex - order)); if (!ldir) { DBG_FAIL_MACRO; goto fail; } if (ldir->attributes != FAT_ATTRIB_LONG_NAME || order != (ldir->order & 0X1F)) { DBG_FAIL_MACRO; goto fail; } for (uint8_t i = 0; i < 13; i++) { uint16_t c = getLfnChar(ldir, i); if (c == 0) { goto done; } if ((n + 1) >= size) { DBG_FAIL_MACRO; goto fail; } name[n++] = c >= 0X7F ? '?' : c; } } done: name[n] = 0; return n; fail: name[0] = '\0'; return 0; } //------------------------------------------------------------------------------ size_t FatFile::getName8(char* name, size_t size) { char* end = name + size; char* str = name; char* ptr; FatFile dir; DirLfn_t* ldir; uint16_t hs = 0; uint32_t cp; if (!isOpen()) { DBG_FAIL_MACRO; goto fail; } if (!isLFN()) { return getSFN(name, size); } if (!dir.openCluster(this)) { DBG_FAIL_MACRO; goto fail; } for (uint8_t order = 1; order <= m_lfnOrd; order++) { ldir = reinterpret_cast(dir.cacheDir(m_dirIndex - order)); if (!ldir) { DBG_FAIL_MACRO; goto fail; } if (ldir->attributes != FAT_ATTRIB_LONG_NAME || order != (ldir->order & 0X1F)) { DBG_FAIL_MACRO; goto fail; } for (uint8_t i = 0; i < 13; i++) { uint16_t c = getLfnChar(ldir, i); if (hs) { if (!FsUtf::isLowSurrogate(c)) { DBG_FAIL_MACRO; goto fail; } cp = FsUtf::u16ToCp(hs, c); hs = 0; } else if (!FsUtf::isSurrogate(c)) { if (c == 0) { goto done; } cp = c; } else if (FsUtf::isHighSurrogate(c)) { hs = c; continue; } else { DBG_FAIL_MACRO; goto fail; } // Save space for zero byte. ptr = FsUtf::cpToMb(cp, str, end - 1); if (!ptr) { DBG_FAIL_MACRO; goto fail; } str = ptr; } } done: *str = '\0'; return str - name; fail: *name = 0; return 0; } //------------------------------------------------------------------------------ size_t FatFile::getSFN(char* name, size_t size) { char c; uint8_t j = 0; uint8_t lcBit = FAT_CASE_LC_BASE; uint8_t* ptr; DirFat_t* dir; if (!isOpen()) { DBG_FAIL_MACRO; goto fail; } if (isRoot()) { if (size < 2) { DBG_FAIL_MACRO; goto fail; } name[0] = '/'; name[1] = '\0'; return 1; } // cache entry dir = cacheDirEntry(FsCache::CACHE_FOR_READ); if (!dir) { DBG_FAIL_MACRO; goto fail; } ptr = dir->name; // format name for (uint8_t i = 0; i < 12; i++) { if (i == 8) { if (*ptr == ' ') { break; } lcBit = FAT_CASE_LC_EXT; c = '.'; } else { c = *ptr++; if ('A' <= c && c <= 'Z' && (lcBit & dir->caseFlags)) { c += 'a' - 'A'; } if (c == ' ') { continue; } } if ((j + 1u) >= size) { DBG_FAIL_MACRO; goto fail; } name[j++] = c; } name[j] = '\0'; return j; fail: name[0] = '\0'; return 0; } //------------------------------------------------------------------------------ size_t FatFile::printName(print_t* pr) { #if !USE_LONG_FILE_NAMES return printSFN(pr); #elif USE_UTF8_LONG_NAMES return printName8(pr); # else // USE_LONG_FILE_NAMES return printName7(pr); #endif // !USE_LONG_FILE_NAMES } //------------------------------------------------------------------------------ size_t FatFile::printName7(print_t* pr) { FatFile dir; DirLfn_t* ldir; size_t n = 0; uint8_t buf[13]; uint8_t i; if (!isOpen()) { DBG_FAIL_MACRO; goto fail; } if (!isLFN()) { return printSFN(pr); } if (!dir.openCluster(this)) { DBG_FAIL_MACRO; goto fail; } for (uint8_t order = 1; order <= m_lfnOrd; order++) { ldir = reinterpret_cast(dir.cacheDir(m_dirIndex - order)); 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++) { uint16_t u = getLfnChar(ldir, i); if (u == 0) { // End of name. break; } buf[i] = u < 0X7F ? u : '?'; n++; } pr->write(buf, i); } return n; fail: return 0; } //------------------------------------------------------------------------------ size_t FatFile::printName8(print_t *pr) { FatFile dir; DirLfn_t* ldir; uint16_t hs = 0; uint32_t cp; size_t n = 0; char buf[5]; char* end = buf + sizeof(buf); if (!isOpen()) { DBG_FAIL_MACRO; goto fail; } if (!isLFN()) { return printSFN(pr); } if (!dir.openCluster(this)) { DBG_FAIL_MACRO; goto fail; } for (uint8_t order = 1; order <= m_lfnOrd; order++) { ldir = reinterpret_cast(dir.cacheDir(m_dirIndex - order)); if (!ldir) { DBG_FAIL_MACRO; goto fail; } if (ldir->attributes != FAT_ATTRIB_LONG_NAME || order != (ldir->order & 0X1F)) { DBG_FAIL_MACRO; goto fail; } for (uint8_t i = 0; i < 13; i++) { uint16_t c = getLfnChar(ldir, i);; if (hs) { if (!FsUtf::isLowSurrogate(c)) { DBG_FAIL_MACRO; goto fail; } cp = FsUtf::u16ToCp(hs, c); hs = 0; } else if (!FsUtf::isSurrogate(c)) { if (c == 0) { break; } cp = c; } else if (FsUtf::isHighSurrogate(c)) { hs = c; continue; } else { DBG_FAIL_MACRO; goto fail; } char* str = FsUtf::cpToMb(cp, buf, end); if (!str) { DBG_FAIL_MACRO; goto fail; } n += pr->write(buf, str - buf); } } return n; fail: return 0; } //------------------------------------------------------------------------------ size_t FatFile::printSFN(print_t* pr) { char name[13]; if (!getSFN(name, sizeof(name))) { DBG_FAIL_MACRO; goto fail; } return pr->write(name); fail: return 0; } ================================================ FILE: firmware/3.0/lib/SdFat/src/FatLib/FatPartition.cpp ================================================ /** * Copyright (c) 2011-2021 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 "FatLib.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; uint8_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 = fatCachePrepare(sector, FsCache::CACHE_FOR_READ); if (!pc) { DBG_FAIL_MACRO; goto fail; } uint16_t offset = (cluster << 2) & m_sectorMask; next = getLe32(pc + offset); } else if (fatType() == 16) { cluster &= 0XFFFF; sector = m_fatStartSector + (cluster >> (m_bytesPerSectorShift - 1) ); pc = fatCachePrepare(sector, FsCache::CACHE_FOR_READ); if (!pc) { DBG_FAIL_MACRO; goto fail; } uint16_t offset = (cluster << 1) & m_sectorMask; next = getLe16(pc + offset); } else if (FAT12_SUPPORT && fatType() == 12) { uint16_t index = cluster; index += index >> 1; sector = m_fatStartSector + (index >> m_bytesPerSectorShift); pc = fatCachePrepare(sector, FsCache::CACHE_FOR_READ); if (!pc) { DBG_FAIL_MACRO; goto fail; } index &= m_sectorMask; uint16_t tmp = pc[index]; index++; if (index == m_bytesPerSector) { pc = fatCachePrepare(sector + 1, FsCache::CACHE_FOR_READ); if (!pc) { DBG_FAIL_MACRO; goto fail; } index = 0; } tmp |= pc[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; uint8_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 = fatCachePrepare(sector, FsCache::CACHE_FOR_WRITE); if (!pc) { DBG_FAIL_MACRO; goto fail; } uint16_t offset = (cluster << 2) & m_sectorMask; setLe32(pc + offset, value); return true; } if (fatType() == 16) { cluster &= 0XFFFF; sector = m_fatStartSector + (cluster >> (m_bytesPerSectorShift - 1) ); pc = fatCachePrepare(sector, FsCache::CACHE_FOR_WRITE); if (!pc) { DBG_FAIL_MACRO; goto fail; } uint16_t offset = (cluster << 1) & m_sectorMask; setLe16(pc + offset, value); return true; } if (FAT12_SUPPORT && fatType() == 12) { uint16_t index = cluster; index += index >> 1; sector = m_fatStartSector + (index >> m_bytesPerSectorShift); pc = fatCachePrepare(sector, FsCache::CACHE_FOR_WRITE); if (!pc) { DBG_FAIL_MACRO; goto fail; } index &= m_sectorMask; uint8_t tmp = value; if (cluster & 1) { tmp = (pc[index] & 0XF) | tmp << 4; } pc[index] = tmp; index++; if (index == m_bytesPerSector) { sector++; index = 0; pc = fatCachePrepare(sector, FsCache::CACHE_FOR_WRITE); if (!pc) { DBG_FAIL_MACRO; goto fail; } } tmp = value >> 4; if (!(cluster & 1)) { tmp = ((pc[index] & 0XF0)) | tmp >> 4; } pc[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; 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; } // Structure to use for doing free cluster count using callbacks struct FreeClusterCountStruct { uint32_t clusters_to_do; uint32_t free_count; }; //------------------------------------------------------------------------------ void FatPartition::freeClusterCount_cb_fat16(uint32_t sector, uint8_t *buf, void *context) { struct FreeClusterCountStruct *state = (struct FreeClusterCountStruct *)context; uint16_t *p = (uint16_t *)buf; unsigned int n = state->clusters_to_do; if (n > 256) n = 256; uint16_t *e = p + n; while (p < e) { if (*p++ == 0) state->free_count++; } state->clusters_to_do -= n; } //------------------------------------------------------------------------------ void FatPartition::freeClusterCount_cb_fat32(uint32_t sector, uint8_t *buf, void *context) { struct FreeClusterCountStruct *state = (struct FreeClusterCountStruct *)context; uint32_t *p = (uint32_t *)buf; unsigned int n = state->clusters_to_do; if (n > 128) n = 128; uint32_t *e = p + n; while (p < e) { if (*p++ == 0) state->free_count++; } state->clusters_to_do -= n; } //------------------------------------------------------------------------------ int32_t FatPartition::freeClusterCount() { #if MAINTAIN_FREE_CLUSTER_COUNT if (m_freeClusterCount >= 0) { return m_freeClusterCount; } #endif // MAINTAIN_FREE_CLUSTER_COUNT if (FAT12_SUPPORT && fatType() == 12) { uint32_t free = 0; uint32_t todo = m_lastCluster + 1; for (unsigned i = 2; i < todo; i++) { uint32_t c; int8_t fg = fatGet(i, &c); if (fg < 0) { DBG_FAIL_MACRO; return -1; } if (fg && c == 0) { free++; } } return free; } struct FreeClusterCountStruct state; state.free_count = 0; state.clusters_to_do = m_lastCluster + 1; uint32_t num_sectors; //num_sectors = SD.sdfs.m_fVol->sectorsPerFat(); // edit FsVolume.h for public //Serial.printf(" num_sectors = %u\n", num_sectors); num_sectors = m_sectorsPerFat; //Serial.printf(" num_sectors = %u\n", num_sectors); #if USE_SEPARATE_FAT_CACHE uint8_t *buf = m_fatCache.clear(); // will clear out anything and return buffer #else uint8_t *buf = m_cache.clear(); // will clear out anything and return buffer #endif // USE_SEPARATE_FAT_CACHE if (buf == nullptr) return -1; if (fatType() == FAT_TYPE_FAT32) { if (!m_blockDev->readSectorsCallback(m_fatStartSector, buf, num_sectors, freeClusterCount_cb_fat32, &state)) return -1; } else { if (!m_blockDev->readSectorsCallback(m_fatStartSector, buf, num_sectors, freeClusterCount_cb_fat16, &state)) return -1; } setFreeClusterCount(state.free_count); return state.free_count; } //------------------------------------------------------------------------------ bool FatPartition::init(FsBlockDevice* dev, uint8_t part) { // Serial.printf(" FatPartition::init(%x %u)\n", (uint32_t)dev, 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 (dataCachePrepare(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 (dataCachePrepare(volumeStartSector, FsCache::CACHE_FOR_READ)); bpb = reinterpret_cast(pbs->bpb); if (!pbs || getLe16(bpb->bytesPerSector) != m_bytesPerSector) { DBG_FAIL_MACRO; goto fail; } // handle fat counts 1 or 2... m_fatCount = bpb->fatCount; if ((m_fatCount != 1) && (m_fatCount != 2)) { 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 + bpb->fatCount * m_sectorsPerFat; // data start for FAT16 and FAT32 m_dataStartSector = m_rootDirStart + ((FS_DIR_SIZE*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; } //------------------------------------------------------------------------------ bool FatPartition::init(FsBlockDevice* dev, uint32_t firstSector, uint32_t numSectors) { // Serial.printf(" FatPartition::init(%x %u %u)\n", (uint32_t)dev, firstSector, numSectors); uint32_t clusterCount; uint32_t totalSectors; uint32_t volumeStartSector = firstSector; m_blockDev = dev; pbs_t* pbs; BpbFat32_t* bpb; 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 pbs = reinterpret_cast (dataCachePrepare(volumeStartSector, FsCache::CACHE_FOR_READ)); bpb = reinterpret_cast(pbs->bpb); if (!pbs || getLe16(bpb->bytesPerSector) != m_bytesPerSector) { DBG_FAIL_MACRO; goto fail; } m_fatCount = bpb->fatCount; // handle fat counts 1 or 2... if ((m_fatCount != 1) && (m_fatCount != 2)) { 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 + bpb->fatCount * m_sectorsPerFat; // data start for FAT16 and FAT32 m_dataStartSector = m_rootDirStart + ((FS_DIR_SIZE*m_rootDirEntryCount + m_bytesPerSector - 1)/m_bytesPerSector); // total sectors for FAT16 or FAT32 totalSectors = getLe16(bpb->totalSectors16); if (totalSectors == 0) { totalSectors = getLe32(bpb->totalSectors32); } if (totalSectors > numSectors) { DBG_FAIL_MACRO; goto fail; } // 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; } ================================================ FILE: firmware/3.0/lib/SdFat/src/FatLib/FatPartition.h ================================================ /** * Copyright (c) 2011-2021 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 "../common/SysCall.h" #include "../common/FsBlockDevice.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; //============================================================================== /** * \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 Number of directory entries per sector. */ uint16_t dirEntriesPerCluster() const { return m_sectorsPerCluster*(m_bytesPerSector/FS_DIR_SIZE); } /** \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 uint8_t __attribute__((error("use sectorsPerCluster()"))) blocksPerCluster(); #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; } /** End access to volume * \return pointer to sector size buffer for format. */ uint8_t* end() { m_fatType = 0; return cacheClear(); } /** \return The number of File Allocation Tables. */ uint8_t fatCount() const { return m_fatCount; } /** \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 FsBlockDevice 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(FsBlockDevice* dev, uint8_t part = 1); bool init(FsBlockDevice* dev, uint32_t firstSector, uint32_t numSectors); /** \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 FsBlockDevice busy. * * \return true if busy else false. */ bool isBusy() {return m_blockDev->isBusy();} //---------------------------------------------------------------------------- #ifndef DOXYGEN_SHOULD_SKIP_THIS bool dmpDirSector(print_t* pr, uint32_t sector); void dmpFat(print_t* pr, uint32_t start, uint32_t count); bool dmpRootDir(print_t* pr, uint32_t n = 0); 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 = 1 << m_bytesPerSectorShift; static const uint16_t m_sectorMask = m_bytesPerSector - 1; //---------------------------------------------------------------------------- FsBlockDevice* 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). uint8_t m_fatCount = 2; // How many fats mostly 2 will support 1 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 syncDevice() { return m_blockDev->syncDevice(); } #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; bool cachePrepare(uint32_t sector, uint8_t option) { return m_cache.prepare(sector, option); } FsCache* dataCache() {return &m_cache;} #if USE_SEPARATE_FAT_CACHE FsCache m_fatCache; uint8_t* fatCachePrepare(uint32_t sector, uint8_t options) { if (m_fatCount == 2) options |= FsCache::CACHE_STATUS_MIRROR_FAT; return m_fatCache.prepare(sector, options); } bool cacheSync() { return m_cache.sync() && m_fatCache.sync() && syncDevice(); } #else // USE_SEPARATE_FAT_CACHE uint8_t* fatCachePrepare(uint32_t sector, uint8_t options) { if (m_fatCount == 2) options |= FsCache::CACHE_STATUS_MIRROR_FAT; return dataCachePrepare(sector, options); } bool cacheSync() { return m_cache.sync() && syncDevice(); } #endif // USE_SEPARATE_FAT_CACHE uint8_t* dataCachePrepare(uint32_t sector, uint8_t options) { return m_cache.prepare(sector, options); } void cacheInvalidate() { m_cache.invalidate(); } bool cacheSyncData() { return m_cache.sync(); } uint8_t* cacheAddress() { return 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; } // freeClusterCount static helper functions static void freeClusterCount_cb_fat16(uint32_t sector, uint8_t *buf, void *context); static void freeClusterCount_cb_fat32(uint32_t sector, uint8_t *buf, void *context); }; #endif // FatPartition ================================================ FILE: firmware/3.0/lib/SdFat/src/FatLib/FatVolume.cpp ================================================ /** * Copyright (c) 2011-2021 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 "FatVolume.cpp" #include "../common/DebugMacros.h" #include "FatLib.h" FatVolume* FatVolume::m_cwv = nullptr; //------------------------------------------------------------------------------ bool FatVolume::chdir(const char *path) { FatFile dir; if (!dir.open(vwd(), path, O_RDONLY)) { DBG_FAIL_MACRO; goto fail; } if (!dir.isDir()) { DBG_FAIL_MACRO; goto fail; } m_vwd = dir; return true; fail: return false; } ================================================ FILE: firmware/3.0/lib/SdFat/src/FatLib/FatVolume.h ================================================ /** * Copyright (c) 2011-2021 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 "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(FsBlockDevice* 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; } bool begin(FsBlockDevice* dev, bool setCwv, uint32_t firstSector, uint32_t numSectors) { if (!init(dev, firstSector, numSectors)) { 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/FreeStack.cpp ================================================ /** * Copyright (c) 2011-2021 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/FreeStack.h ================================================ /** * Copyright (c) 2011-2021 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/FsLib/FsFile.cpp ================================================ /** * Copyright (c) 2011-2021 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; } ================================================ FILE: firmware/3.0/lib/SdFat/src/FsLib/FsFile.h ================================================ /** * Copyright (c) 2011-2021 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 FsBlockDevice 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); /** 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 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 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 zero and writeError is set. */ 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/FsLib/FsFormatter.h ================================================ /** * Copyright (c) 2011-2021 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 FsFormatter_h #define FsFormatter_h #include "FatLib/FatLib.h" #include "ExFatLib/ExFatLib.h" /** * \class FsFormatter * \brief Format a exFAT/FAT volume. */ class FsFormatter { 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(FsBlockDevice* dev, uint8_t* secBuffer, print_t* pr = nullptr) { uint32_t sectorCount = dev->sectorCount(); if (sectorCount == 0) { return false; } return sectorCount <= 67108864 ? m_fFmt.format(dev, secBuffer, pr) : m_xFmt.format(dev, secBuffer, pr); } private: FatFormatter m_fFmt; ExFatFormatter m_xFmt; }; #endif // FsFormatter_h ================================================ FILE: firmware/3.0/lib/SdFat/src/FsLib/FsLib.h ================================================ /** * Copyright (c) 2011-2021 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" #include "FsFormatter.h" #endif // FsLib_h ================================================ FILE: firmware/3.0/lib/SdFat/src/FsLib/FsNew.cpp ================================================ /** * Copyright (c) 2011-2021 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; } ================================================ FILE: firmware/3.0/lib/SdFat/src/FsLib/FsNew.h ================================================ /** * Copyright (c) 2011-2021 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/FsLib/FsVolume.cpp ================================================ /** * Copyright (c) 2011-2021 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(FsBlockDevice* blockDev, bool setCwv, uint8_t part) { m_blockDev = blockDev; m_fVol = nullptr; m_xVol = new (m_volMem) ExFatVolume; if (m_xVol && m_xVol->begin(m_blockDev, false, part)) { goto done; } m_xVol = nullptr; m_fVol = new (m_volMem) FatVolume; if (m_fVol && m_fVol->begin(m_blockDev, false, part)) { goto done; } m_cwv = nullptr; m_fVol = nullptr; return false; done: if (setCwv || !m_cwv) { m_cwv = this; } return true; } //------------------------------------------------------------------------------ bool FsVolume::begin(FsBlockDevice* blockDev, bool setCwv, uint32_t firstSector, uint32_t numSectors) { m_blockDev = blockDev; m_fVol = nullptr; m_xVol = new (m_volMem) ExFatVolume; if (m_xVol && m_xVol->begin(m_blockDev, false, firstSector, numSectors)) { goto done; } m_xVol = nullptr; m_fVol = new (m_volMem) FatVolume; if (m_fVol && m_fVol->begin(m_blockDev, false, firstSector, numSectors)) { goto done; } m_cwv = nullptr; m_fVol = nullptr; return false; done: if (setCwv || !m_cwv) { 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 // getVolumeLabel & setVolumeLabel from UsbMscFat by Warren Watson bool FsVolume::getVolumeLabel(char *volume_label, size_t cb) { uint8_t buf[512]; if (!volume_label || (cb < 12)) return false; // don't want to deal with it *volume_label = 0; // make sure if we fail later we return empty string as well. uint8_t fat_type = fatType(); uint32_t root_dir; // Lets go hard core here. if (m_fVol) { FatFile rootFat; if (!rootFat.openRoot(m_fVol)) return false; uint32_t root_dir_size = rootFat.dirSize(); // how big is this directory... rootFat.close(); if (fat_type == FAT_TYPE_FAT32) { root_dir = m_fVol->dataStartSector(); } else { root_dir = m_fVol->rootDirStart(); } //Serial.printf("\n$$$ PFsVolume::getVolumeLabel(%u): %u %u\n", fat_type, root_dir, root_dir_size); uint16_t index_in_sector=0; m_blockDev->readSector(root_dir, buf); while (root_dir_size) { DirFat_t *dir; dir = reinterpret_cast(&buf[index_in_sector]); if (dir->name[0] == FAT_NAME_FREE) break; // at end of list... if (dir->attributes == 0x08) { size_t i; for (i = 0; i < 11; i++) { volume_label[i] = dir->name[i]; } while ((i > 0) && (volume_label[i - 1] == ' ')) i--; // trim off trailing blanks volume_label[i] = 0; return true; } index_in_sector += 32; // increment to next entry... root_dir_size-=32; if (index_in_sector >= 512 && root_dir_size) { root_dir++; m_blockDev->readSector(root_dir, buf); index_in_sector = 0; } } } else if (m_xVol) { uint32_t chs = m_xVol->clusterHeapStartSector(); uint32_t rdc = m_xVol->rootDirectoryCluster(); uint32_t root_dir_size = m_xVol->rootLength(); uint32_t spc = m_xVol->sectorsPerCluster(); //Serial.printf("\n$$$ PFsVolume::getVolumeLabel(Ex): %u %x %x %u\n", root_dir_size, chs, rdc, spc); uint32_t root_dir = chs + (rdc-2)*spc; //Serial.printf(" $$$ Guess sector: %x\n", root_dir); uint16_t index_in_sector=0; m_blockDev->readSector(root_dir, buf); while (root_dir_size) { DirLabel_t *dir; dir = reinterpret_cast(&buf[index_in_sector]); //if (dir->name[0] == 0) break; // at end of list... if (dir->type == EXFAT_TYPE_LABEL) { size_t i; for (i = 0; i < dir->labelLength; i++) { volume_label[i] = dir->unicode[2 * i]; } volume_label[i] = 0; return true; } else if (dir->type == 0) break; // I believe this marks the end... index_in_sector += 32; // increment to next entry... root_dir_size-=32; if (index_in_sector >= 512 && root_dir_size) { root_dir++; m_blockDev->readSector(root_dir, buf); index_in_sector = 0; } } } return false; // no volume label was found } bool FsVolume::setVolumeLabel(const char *volume_label) { uint8_t buf[512]; uint8_t fat_type = fatType(); uint32_t root_dir; bool label_found = false; // Lets go hard core here. if (m_fVol) { FatFile rootFat; DirFat_t *dir = nullptr; if (!rootFat.openRoot(m_fVol)) return false; uint32_t root_dir_size = rootFat.dirSize(); // how big is this directory... rootFat.close(); if (fat_type == FAT_TYPE_FAT32) { root_dir = m_fVol->dataStartSector(); } else { root_dir = m_fVol->rootDirStart(); } //Serial.printf("\n$$$ PFsVolume::setVolumeLabel(%u): %u %u\n", fat_type, root_dir, root_dir_size); uint16_t index_in_sector=0; uint32_t first_deleted_entry_sector = 0; uint16_t first_deleted_entry_index = 0; m_blockDev->readSector(root_dir, buf); //dump_hexbytes(buf, 512); while (root_dir_size) { dir = reinterpret_cast(&buf[index_in_sector]); if (dir->name[0] == FAT_NAME_DELETED) { if (!first_deleted_entry_sector) { first_deleted_entry_sector = root_dir; first_deleted_entry_index = index_in_sector; } } else if (dir->name[0] == FAT_NAME_FREE) break; // at end of list... else if (dir->attributes == 0x08) { label_found = true; break; } index_in_sector += 32; // increment to next entry... root_dir_size-=32; if (index_in_sector >= 512 && root_dir_size) { root_dir++; m_blockDev->readSector(root_dir, buf); //Serial.printf(">> %x\n", root_dir); //dump_hexbytes(buf, 512); index_in_sector = 0; } } // Lets see if we found something... if (!volume_label || !*volume_label) { if (label_found) { Serial.printf("Found volume label - deleted\n"); dir->name[0] = FAT_NAME_DELETED; // mark item as deleted dir->attributes = 0; m_blockDev->writeSector(root_dir, buf); m_blockDev->syncDevice(); } return true; } // Lets see where we should write... if (!label_found) { if (first_deleted_entry_sector) { if (first_deleted_entry_sector != root_dir) { root_dir = first_deleted_entry_sector; m_blockDev->readSector(root_dir, buf); } index_in_sector = first_deleted_entry_index; dir = reinterpret_cast(&buf[index_in_sector]); label_found = true; } else if (dir->name[0] == FAT_NAME_FREE) label_found = true; } if (label_found) { // or found a spot for it. memset((void*)dir, 0, 32); // clear it out. if (FsDateTime::callback) { uint16_t cur_date; uint16_t cur_time; uint8_t cur_ms10; FsDateTime::callback(&cur_date, &cur_time, &cur_ms10); setLe16(dir->modifyTime, cur_time); setLe16(dir->modifyDate, cur_date); } for (size_t i = 0; i < 11; i++) { dir->name[i] = *volume_label? *volume_label++ : ' '; // fill in the 11 trailing blanks } dir->attributes = 8; // mark as a volume label. m_blockDev->writeSector(root_dir, buf); m_blockDev->syncDevice(); return true; } } else if (m_xVol) { DirLabel_t *dir = nullptr; uint32_t chs = m_xVol->clusterHeapStartSector(); uint32_t rdc = m_xVol->rootDirectoryCluster(); uint32_t root_dir_size = m_xVol->rootLength(); uint32_t spc = m_xVol->sectorsPerCluster(); //Serial.printf("\n$$$ PFsVolume::setVolumeLabel(Ex): %u %x %x %u\n", root_dir_size, chs, rdc, spc); uint32_t root_dir = chs + (rdc-2)*spc; //Serial.printf(" $$$ Guess sector: %x\n", root_dir); uint16_t index_in_sector=0; m_blockDev->readSector(root_dir, buf); //m_xVol->cacheSafeRead(root_dir, buf); //dump_hexbytes(buf, 512); while (root_dir_size) { dir = reinterpret_cast(&buf[index_in_sector]); //if (dir->name[0] == 0) break; // at end of list... if (dir->type == EXFAT_TYPE_LABEL) { label_found = true; break; } else if (dir->type == 0) break; index_in_sector += 32; // increment to next entry... root_dir_size-=32; if (index_in_sector >= 512 && root_dir_size) { root_dir++; m_blockDev->readSector(root_dir, buf); //m_xVol->cacheSafeRead(root_dir, buf); index_in_sector = 0; //Serial.println("---"); //dump_hexbytes(buf, 512); } } // Lets see if we found something... if (!volume_label || !*volume_label) { if (label_found) { Serial.printf("Found volume label - deleted\n"); dir->type &= 0x7f; // mark item as deleted m_blockDev->writeSector(root_dir, buf); //m_xVol->cacheSafeWrite(root_dir, buf); m_xVol->cacheClear(); m_blockDev->syncDevice(); } return true; } // Lets see where we should write... // if (label_found || (dir->type == 0)) { // or found a spot for it. uint8_t cb = strlen(volume_label); if (cb > 11) cb = 11; // truncate off. dir->type = EXFAT_TYPE_LABEL; dir->labelLength = cb; uint8_t *puni = dir->unicode; while (cb--) { *puni = *volume_label++; puni += 2; } //m_xVol->cacheSafeWrite(root_dir, buf); m_blockDev->writeSector(root_dir, buf); m_xVol->cacheClear(); m_blockDev->syncDevice(); return true; } } return false; // no volume label was found } ================================================ FILE: firmware/3.0/lib/SdFat/src/FsLib/FsVolume.h ================================================ /** * Copyright (c) 2011-2021 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. * \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(FsBlockDevice* blockDev, bool setCwv = true, uint8_t part = 1); bool begin(FsBlockDevice* blockDev, bool setCwv, uint32_t firstSector, uint32_t numSectors); #ifndef DOXYGEN_SHOULD_SKIP_THIS uint32_t __attribute__((error("use sectorsPerCluster()"))) blocksPerCluster(); #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; } /** End access to volume * \return pointer to sector size buffer for format. */ uint8_t* end() { m_fVol = nullptr; m_xVol = nullptr; static_assert(sizeof(m_volMem) >= 512, "m_volMem too small"); return reinterpret_cast(m_volMem); } /** 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 device 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 // TODO: #if ENABLE_VOLUME_LABEL so this isn't compiled on limited memory boards /** Retrieve a volume label name. * * \param[in] character buffer to re receive the name * * \param[in] size of buffer * * \return true for success or false for failure. */ bool getVolumeLabel(char *volume_label, size_t cb); /** set a volume label name. * * \param[in] Null terminated string with new volume label * * \return true for success or false for failure. */ bool setVolumeLabel(const char *volume_label); 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; FsBlockDevice* m_blockDev; }; #endif // FsVolume_h ================================================ FILE: firmware/3.0/lib/SdFat/src/MinimumSerial.cpp ================================================ /** * Copyright (c) 2011-2021 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) ================================================ FILE: firmware/3.0/lib/SdFat/src/MinimumSerial.h ================================================ /** * Copyright (c) 2011-2021 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/RingBuf.h ================================================ /** * Copyright (c) 2011-2021 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 "common/SysCall.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. * \return true for success. */ 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); } /** * Copy str to RingBuf. * * \param[in] str Location of data to be written. * \return Number of bytes actually written. */ size_t write(const char* str) { return Print::write(str); } /** * 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/SdCard/CPPLINT.cfg ================================================ exclude_files=SdioTeensy.h ================================================ FILE: firmware/3.0/lib/SdFat/src/SdCard/SdCard.h ================================================ /** * Copyright (c) 2011-2021 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/SdCard/SdCardInfo.cpp ================================================ /** * Copyright (c) 2011-2021 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")); } } ================================================ FILE: firmware/3.0/lib/SdFat/src/SdCard/SdCardInfo.h ================================================ /** * Copyright (c) 2011-2021 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/SdCard/SdCardInterface.h ================================================ /** * Copyright (c) 2011-2021 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/FsBlockDeviceInterface.h" #include "SdCardInfo.h" /** * \class SdCardInterface * \brief Abstract interface for an SD card. */ class SdCardInterface : public FsBlockDeviceInterface { public: /** end use of card */ virtual void end() = 0; /** 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; /** \return false by default */ virtual bool hasDedicatedSpi() {return false;} /** \return false by default */ bool virtual isDedicatedSpi() {return false;} /** Set SPI sharing state * \param[in] value desired state. * \return false by default. */ virtual bool setDedicatedSpi(bool value) { (void)value; return false; } /** * 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/SdCard/SdSpiCard.cpp ================================================ /** * Copyright (c) 2011-2021 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" //============================================================================== class Timeout { public: Timeout() {} explicit Timeout(uint16_t ms) {set(ms);} uint16_t millis16() {return millis();} void set(uint16_t ms) { m_endTime = ms + millis16(); } bool timedOut() { return (int16_t)(m_endTime - millis16()) < 0; } private: uint16_t m_endTime; }; //============================================================================== #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 //============================================================================== // SharedSpiCard member functions //------------------------------------------------------------------------------ bool SharedSpiCard::begin(SdSpiConfig spiConfig) { Timeout timeout; 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; m_state = IDLE_STATE; spiStart(); // must supply min of 74 clock cycles with CS high. spiUnselect(); for (uint8_t i = 0; i < 10; i++) { spiSend(0XFF); } spiSelect(); // command to go idle in SPI mode for (uint8_t i = 1;; i++) { if (cardCommand(CMD0, 0) == R1_IDLE_STATE) { break; } if (i == SD_CMD0_RETRY) { 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 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; timeout.set(SD_INIT_TIMEOUT); while (cardAcmd(ACMD41, arg) != R1_READY_STATE) { // check for timeout if (timeout.timedOut()) { 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(); } } spiStop(); spiSetSckSpeed(spiConfig.maxSck); return true; fail: spiStop(); return false; } //------------------------------------------------------------------------------ // send command and return error code. Return zero for OK uint8_t SharedSpiCard::cardCommand(uint8_t cmd, uint32_t arg) { if (!syncDevice()) { return 0XFF; } // select card if (!m_spiActive) { spiStart(); } if (cmd != CMD12) { if (!waitReady(SD_CMD_TIMEOUT) && cmd != CMD0) { return 0XFF; } } #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. uint16_t n = 0; do { m_status = spiReceive(); } while (m_status & 0X80 && ++n < 10); return m_status; } //------------------------------------------------------------------------------ bool SharedSpiCard::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; } if (!waitReady(SD_ERASE_TIMEOUT)) { error(SD_CARD_ERROR_ERASE_TIMEOUT); goto fail; } spiStop(); return true; fail: spiStop(); return false; } //------------------------------------------------------------------------------ bool SharedSpiCard::eraseSingleSectorEnable() { csd_t csd; return readCSD(&csd) ? csd.v1.erase_blk_en : false; } //------------------------------------------------------------------------------ bool SharedSpiCard::isBusy() { if (m_state == READ_STATE) { return false; } bool spiActive = m_spiActive; if (!spiActive) { spiStart(); } bool rtn = 0XFF != spiReceive(); if (!spiActive) { spiStop(); } return rtn; } //------------------------------------------------------------------------------ bool SharedSpiCard::readData(uint8_t* dst) { return readData(dst, 512); } //------------------------------------------------------------------------------ bool SharedSpiCard::readData(uint8_t* dst, size_t count) { #if USE_SD_CRC uint16_t crc; #endif // USE_SD_CRC // wait for start sector token Timeout timeout(SD_READ_TIMEOUT); while ((m_status = spiReceive()) == 0XFF) { if (timeout.timedOut()) { error(SD_CARD_ERROR_READ_TIMEOUT); goto fail; } } 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 SharedSpiCard::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 SharedSpiCard::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 SharedSpiCard::readSector(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 SharedSpiCard::readSectors(uint32_t sector, uint8_t* dst, size_t ns) { if (!readStart(sector)) { goto fail; } for (size_t i = 0; i < ns; i++, dst += 512) { if (!readData(dst, 512)) { goto fail; } } return readStop(); fail: return false; } //------------------------------------------------------------------------------ bool SharedSpiCard::readSectorsCallback(uint32_t sector, uint8_t* dst, size_t ns, void (*callback)(uint32_t sector, uint8_t *buf, void *context), void *context) { if (!readStart(sector)) { goto fail; } for (size_t i = 0; i < ns; i++) { if (readData(dst, 512)) { callback(sector + i, dst, context); } else { goto fail; } } return readStop(); fail: return false; } //------------------------------------------------------------------------------ bool SharedSpiCard::readStart(uint32_t sector) { if (type() != SD_CARD_TYPE_SDHC) { sector <<= 9; } if (cardCommand(CMD18, sector)) { error(SD_CARD_ERROR_CMD18); goto fail; } m_state = READ_STATE; return true; fail: spiStop(); return false; } //------------------------------------------------------------------------------ bool SharedSpiCard::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 SharedSpiCard::readStop() { m_state = IDLE_STATE; if (cardCommand(CMD12, 0)) { error(SD_CARD_ERROR_CMD12); goto fail; } spiStop(); return true; fail: spiStop(); return false; } //------------------------------------------------------------------------------ uint32_t SharedSpiCard::sectorCount() { csd_t csd; return readCSD(&csd) ? sdCardCapacity(&csd) : 0; } //------------------------------------------------------------------------------ void SharedSpiCard::spiStart() { if (!m_spiActive) { spiActivate(); spiSelect(); // Dummy byte to drive MISO busy status. spiSend(0XFF); m_spiActive = true; } } //------------------------------------------------------------------------------ void SharedSpiCard::spiStop() { if (m_spiActive) { spiUnselect(); // Insure MISO goes to low Z. spiSend(0XFF); spiDeactivate(); m_spiActive = false; } } //------------------------------------------------------------------------------ bool SharedSpiCard::syncDevice() { if (m_state == WRITE_STATE) { return writeStop(); } if (m_state == READ_STATE) { return readStop(); } return true; } //------------------------------------------------------------------------------ bool SharedSpiCard::waitReady(uint16_t ms) { Timeout timeout(ms); while (spiReceive() != 0XFF) { if (timeout.timedOut()) { return false; } } return true; } //------------------------------------------------------------------------------ bool SharedSpiCard::writeData(const uint8_t* src) { // wait for previous write to finish if (!waitReady(SD_WRITE_TIMEOUT)) { error(SD_CARD_ERROR_WRITE_TIMEOUT); goto fail; } 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 SharedSpiCard::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 SharedSpiCard::writeSector(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 if (!waitReady(SD_WRITE_TIMEOUT)) { error(SD_CARD_ERROR_WRITE_PROGRAMMING); goto fail; } // 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 SharedSpiCard::writeSectors(uint32_t sector, const uint8_t* src, size_t ns) { if (!writeStart(sector)) { goto fail; } for (size_t i = 0; i < ns; i++, src += 512) { if (!writeData(src)) { goto fail; } } return writeStop(); fail: spiStop(); return false; } //------------------------------------------------------------------------------ bool SharedSpiCard::writeSectorsCallback(uint32_t sector, size_t ns, const uint8_t * (*callback)(uint32_t sector, void *context), void *context) { if (!writeStart(sector)) { goto fail; } for (size_t i = 0; i < ns; i++) { const uint8_t *src = callback(sector + i, context); if (!writeData(src)) { goto fail; } } return writeStop(); fail: spiStop(); return false; } //------------------------------------------------------------------------------ bool SharedSpiCard::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; } m_state = WRITE_STATE; return true; fail: spiStop(); return false; } //------------------------------------------------------------------------------ bool SharedSpiCard::writeStop() { if (!waitReady(SD_WRITE_TIMEOUT)) { goto fail; } spiSend(STOP_TRAN_TOKEN); spiStop(); m_state = IDLE_STATE; return true; fail: error(SD_CARD_ERROR_STOP_TRAN); spiStop(); return false; } //============================================================================== bool DedicatedSpiCard::begin(SdSpiConfig spiConfig) { if (!SharedSpiCard::begin(spiConfig)) { return false; } m_dedicatedSpi = spiOptionDedicated(spiConfig.options); return true; } //------------------------------------------------------------------------------ bool DedicatedSpiCard::readSector(uint32_t sector, uint8_t* dst) { return readSectors(sector, dst, 1); } //------------------------------------------------------------------------------ bool DedicatedSpiCard::readSectors( uint32_t sector, uint8_t* dst, size_t ns) { if (sdState() != READ_STATE || sector != m_curSector) { if (!readStart(sector)) { goto fail; } m_curSector = sector; } for (size_t i = 0; i < ns; i++, dst += 512) { if (!readData(dst)) { goto fail; } } m_curSector += ns; return m_dedicatedSpi ? true : readStop(); fail: return false; } //------------------------------------------------------------------------------ bool DedicatedSpiCard::readSectorsCallback(uint32_t sector, uint8_t* dst, size_t ns, void (*callback)(uint32_t sector, uint8_t *buf, void *context), void *context) { if (sdState() != READ_STATE || sector != m_curSector) { if (!readStart(sector)) { goto fail; } m_curSector = sector; } for (size_t i = 0; i < ns; i++) { if (readData(dst)) { callback(sector + i, dst, context); } else { goto fail; } } m_curSector += ns; return m_dedicatedSpi ? true : readStop(); fail: return false; } //------------------------------------------------------------------------------ bool DedicatedSpiCard::setDedicatedSpi(bool value) { if (!syncDevice()) { return false; } m_dedicatedSpi = value; return true; } //------------------------------------------------------------------------------ bool DedicatedSpiCard::writeSector(uint32_t sector, const uint8_t* src) { if (m_dedicatedSpi) { return writeSectors(sector, src, 1); } return SharedSpiCard::writeSector(sector, src); } //------------------------------------------------------------------------------ bool DedicatedSpiCard::writeSectors( uint32_t sector, const uint8_t* src, size_t ns) { if (sdState() != WRITE_STATE || m_curSector != sector) { if (!writeStart(sector)) { goto fail; } m_curSector = sector; } for (size_t i = 0; i < ns; i++, src += 512) { if (!writeData(src)) { goto fail; } } m_curSector += ns; return m_dedicatedSpi ? true : writeStop(); fail: return false; } //------------------------------------------------------------------------------ bool DedicatedSpiCard::writeSectorsCallback(uint32_t sector, size_t ns, const uint8_t * (*callback)(uint32_t sector, void *context), void *context) { if (sdState() != WRITE_STATE || m_curSector != sector) { if (!writeStart(sector)) { goto fail; } m_curSector = sector; } for (size_t i = 0; i < ns; i++) { const uint8_t *src = callback(sector + i, context); if (!writeData(src)) { goto fail; } } m_curSector += ns; return m_dedicatedSpi ? true : writeStop(); fail: return false; } ================================================ FILE: firmware/3.0/lib/SdFat/src/SdCard/SdSpiCard.h ================================================ /** * Copyright (c) 2011-2021 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 SharedSpiCard * \brief Raw access to SD and SDHC flash memory cards via shared SPI port. */ #if HAS_SDIO_CLASS class SharedSpiCard : public SdCardInterface { #elif USE_BLOCK_DEVICE_INTERFACE class SharedSpiCard : public FsBlockDeviceInterface { #else // HAS_SDIO_CLASS class SharedSpiCard { #endif // HAS_SDIO_CLASS public: /** SD is in idle state */ static const uint8_t IDLE_STATE = 0; /** SD is in multi-sector read state. */ static const uint8_t READ_STATE = 1; /** SD is in multi-sector write state. */ static const uint8_t WRITE_STATE = 2; /** Construct an instance of SharedSpiCard. */ SharedSpiCard() {} /** Initialize the SD card. * \param[in] spiConfig SPI card configuration. * \return true for success or false for failure. */ bool begin(SdSpiConfig spiConfig); /** End use of card */ void end() { spiEnd(); } /** 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) { // (void)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; } /** \return false for shared class. */ bool hasDedicatedSpi() {return false;} /** * Check for busy. MISO low indicates the card is busy. * * \return true if busy else false. */ bool isBusy(); /** \return false, can't be in dedicated state. */ bool isDedicatedSpi() {return false;} /** * 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); /** * 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 multiple sectors with callback as each sector's data * * \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. * \param[in] callback Function to be called with each sector's data * \param[in] context Pointer to be passed to the callback function * \return true for success or false for failure. */ bool readSectorsCallback(uint32_t sector, uint8_t* dst, size_t ns, void (*callback)(uint32_t sector, uint8_t *buf, void *context), void *context); /** 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 SD multi-sector read/write state */ uint8_t sdState() {return m_state;} /** * 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 __attribute__((error("use sectorCount()"))) cardSize(); #endif // DOXYGEN_SHOULD_SKIP_THIS /** Set SPI sharing state * \param[in] value desired state. * \return false for shared card */ bool setDedicatedSpi(bool value) { (void)value; return false; } /** end a mult-sector transfer. * * \return true for success or false for failure. */ bool stopTransfer(); /** \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; } /** * Write 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 multiple sectors to SD card with callback to prep data. * * \param[in] sector Logical sector to be written. * \param[in] ns Number of sectors to be written. * \param[in] callback Function to be called for each sector's data * \param[in] context to pass to callback function * \return true for success or false for failure. */ bool writeSectorsCallback(uint32_t sector, size_t ns, const uint8_t * (*callback)(uint32_t sector, void *context), void *context); /** 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); /** End a write multiple sectors sequence. * * \return true for success or false 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 spiSelect() { sdCsWrite(m_csPin, false); } void spiStart(); void spiStop(); void spiUnselect() { sdCsWrite(m_csPin, true); } void type(uint8_t value) { m_type = value; } bool waitReady(uint16_t ms); 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(); } void spiEnd() { m_spiDriver.end(); } 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(); } void spiEnd() { m_spiDriverPtr->end(); } 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 SdCsPin_t m_csPin; uint8_t m_errorCode = SD_CARD_ERROR_INIT_NOT_CALLED; bool m_spiActive; uint8_t m_state; uint8_t m_status; uint8_t m_type = 0; }; //============================================================================== /** * \class DedicatedSpiCard * \brief Raw access to SD and SDHC flash memory cards via dedicate SPI port. */ class DedicatedSpiCard : public SharedSpiCard { public: /** Construct an instance of DedicatedSpiCard. */ DedicatedSpiCard() {} /** Initialize the SD card. * \param[in] spiConfig SPI card configuration. * \return true for success or false for failure. */ bool begin(SdSpiConfig spiConfig); /** \return true, can be in dedicaded state. */ bool hasDedicatedSpi() {return true;} /** \return true if in dedicated SPI state. */ bool isDedicatedSpi() {return m_dedicatedSpi;} /** * 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 multiple sectors with callback as each sector's data * * \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. * \param[in] callback Function to be called with each sector's data * \param[in] context Pointer to be passed to the callback function * \return true for success or false for failure. */ bool readSectorsCallback(uint32_t sector, uint8_t* dst, size_t ns, void (*callback)(uint32_t sector, uint8_t *buf, void *context), void *context); /** Set SPI sharing state * \param[in] value desired state. * \return true for success else false; */ bool setDedicatedSpi(bool value); /** * Write 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 multiple sectors to SD card with callback to prep data. * * \param[in] sector Logical sector to be written. * \param[in] ns Number of sectors to be written. * \param[in] callback Function to be called for each sector's data * \param[in] context to pass to callback function * \return true for success or false for failure. */ bool writeSectorsCallback(uint32_t sector, size_t ns, const uint8_t * (*callback)(uint32_t sector, void *context), void *context); private: uint32_t m_curSector; bool m_dedicatedSpi = false; }; //============================================================================== #if ENABLE_DEDICATED_SPI /** typedef for dedicated SPI. */ typedef DedicatedSpiCard SdSpiCard; #else /** typedef for shared SPI. */ typedef SharedSpiCard SdSpiCard; #endif #endif // SdSpiCard_h ================================================ FILE: firmware/3.0/lib/SdFat/src/SdCard/SdioCard.h ================================================ /** * Copyright (c) 2011-2021 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. * not implemented. */ void end() {} #ifndef DOXYGEN_SHOULD_SKIP_THIS uint32_t __attribute__((error("use sectorCount()"))) cardSize(); #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 ================================================ FILE: firmware/3.0/lib/SdFat/src/SdCard/SdioTeensy.cpp ================================================ /** * Copyright (c) 2011-2021 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; static bool m_transferActive = false; 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(__IMXRT1062__) 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(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; } 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. } //------------------------------------------------------------------------------ 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; } //============================================================================== // 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 (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; } } //------------------------------------------------------------------------------ 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 (!waitTransferComplete()) { return false; } 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 (!waitTransferComplete()) { return false; } if (m_curState != IDLE_STATE) { return stopTransmission(true); } 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 (!waitTransferComplete()) { return false; } 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; } m_transferActive = true; return true; } //------------------------------------------------------------------------------ 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 (!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__) 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++; } 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__) ================================================ FILE: firmware/3.0/lib/SdFat/src/SdCard/SdioTeensy.h ================================================ #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 ================================================ FILE: firmware/3.0/lib/SdFat/src/SdFat.h ================================================ /** * Copyright (c) 2011-2021 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 /** This copy of SdFat has special modifications for Teensy. */ #define SD_FAT_TEENSY_MODIFIED 1 /** * \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 20102 /** SdFat version as string. */ #define SD_FAT_VERSION_STR "2.1.2" //============================================================================== /** * \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) { spiConfigBackupPin = spiConfig.csPin; spiConfigBackupOptions = spiConfig.options; spiConfigBackupClock = spiConfig.maxSck; spiConfigBackupPort = spiConfig.spiPort; 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) { spiConfigBackupPin = 255; sdioConfigBackup = sdioConfig; return cardBegin(sdioConfig) && Vol::begin(m_card); } //---------------------------------------------------------------------------- /** Restart library with same config, used after media removed and replaced */ bool restart() { if (spiConfigBackupPin == 255) { return begin(sdioConfigBackup); } else { SdSpiConfig spiConfig(spiConfigBackupPin, spiConfigBackupOptions, spiConfigBackupClock, spiConfigBackupPort); return begin(spiConfig); } } //---------------------------------------------------------------------------- /** \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(); } //---------------------------------------------------------------------------- /** End use of card. */ void end() { Vol::end(); if (m_card) { m_card->end(); } } //---------------------------------------------------------------------------- /** %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.")); } while (true) {} } //---------------------------------------------------------------------------- /** %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); } //---------------------------------------------------------------------------- /** Format SD card * * \param[in] pr Print destination. * \return true for success else false. */ bool format(print_t* pr = nullptr) { Fmt fmt; uint8_t* mem = Vol::end(); if (!mem) { return false; } bool switchSpi = hasDedicatedSpi() && !isDedicatedSpi(); if (switchSpi && !setDedicatedSpi(true)) { return 0; } bool rtn = fmt.format(card(), mem, pr); if (switchSpi && !setDedicatedSpi(false)) { return 0; } return rtn; } //---------------------------------------------------------------------------- /** \return the free cluster count. */ uint32_t freeClusterCount() { bool switchSpi = hasDedicatedSpi() && !isDedicatedSpi(); if (switchSpi && !setDedicatedSpi(true)) { return 0; } uint32_t rtn = Vol::freeClusterCount(); if (switchSpi && !setDedicatedSpi(false)) { return 0; } return rtn; } //---------------------------------------------------------------------------- /** \return true if can be in dedicated SPI state */ bool hasDedicatedSpi() {return m_card ? m_card->hasDedicatedSpi() : false;} //---------------------------------------------------------------------------- /** %Print error info and halt. * * \param[in] pr Print destination. */ void initErrorHalt(print_t* pr) { initErrorPrint(pr); while (true) {} } //---------------------------------------------------------------------------- /** %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_t* pr, const __FlashStringHelper* msg) { pr->println(msg); initErrorHalt(pr); } //---------------------------------------------------------------------------- /** Print error details after begin() fails. * * \param[in] pr Print destination. */ void initErrorPrint(print_t* 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); } //---------------------------------------------------------------------------- /** \return true if in dedicated SPI state. */ bool isDedicatedSpi() {return m_card ? m_card->isDedicatedSpi() : false;} //---------------------------------------------------------------------------- /** %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_t* 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;} //---------------------------------------------------------------------------- /** Set SPI sharing state * \param[in] value desired state. * \return true for success else false; */ bool setDedicatedSpi(bool value) { if (m_card) { return m_card->setDedicatedSpi(value); } return false; } //---------------------------------------------------------------------------- /** \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 = nullptr; SdCardFactory m_cardFactory; SdCsPin_t spiConfigBackupPin; uint8_t spiConfigBackupOptions; uint32_t spiConfigBackupClock; SpiPort_t* spiConfigBackupPort; SdioConfig sdioConfigBackup; }; //------------------------------------------------------------------------------ /** * \class SdFat32 * \brief SD file system class for FAT volumes. */ class SdFat32 : public SdBase { public: }; //------------------------------------------------------------------------------ /** * \class SdExFat * \brief SD file system class for exFAT volumes. */ class SdExFat : public SdBase { public: }; //------------------------------------------------------------------------------ /** * \class SdFs * \brief SD file system class for FAT16, FAT32, and exFAT volumes. */ class SdFs : public SdBase { public: }; //------------------------------------------------------------------------------ #if SDFAT_FILE_TYPE == 1 || defined(DOXYGEN) /** Select type for SdFat. */ typedef SdFat32 SdFat; /** Select type for SdBaseFile. */ typedef FatFile SdBaseFile; #elif SDFAT_FILE_TYPE == 2 typedef SdExFat SdFat; typedef ExFatFile SdBaseFile; #elif SDFAT_FILE_TYPE == 3 typedef SdFs SdFat; typedef FsBaseFile SdBaseFile; #else // SDFAT_FILE_TYPE #error Invalid SDFAT_FILE_TYPE #endif // SDFAT_FILE_TYPE // // Only define File if FS.h is not included. // Line with test for __has_include must not have operators or parentheses. #if defined __has_include #if __has_include() #define HAS_INCLUDE_FS_H #ifndef TEENSYDUINO #warning File not defined because __has_include(FS.h) #endif #endif // __has_include() #endif // defined __has_include #ifndef HAS_INCLUDE_FS_H #if SDFAT_FILE_TYPE == 1 || defined(DOXYGEN) /** Select type for File. */ typedef File32 File; #elif SDFAT_FILE_TYPE == 2 typedef ExFile File; #elif SDFAT_FILE_TYPE == 3 typedef FsFile File; #endif // SDFAT_FILE_TYPE #endif // HAS_INCLUDE_FS_H /** * \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 ================================================ FILE: firmware/3.0/lib/SdFat/src/SdFatConfig.h ================================================ /** * Copyright (c) 2011-2021 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__ // // To try UTF-8 encoded filenames. // #define USE_UTF8_LONG_NAMES 1 // // For minimum flash size use these settings: // #define USE_FAT_FILE_FLAG_CONTIGUOUS 0 // #define ENABLE_DEDICATED_SPI 0 // #define USE_LONG_FILE_NAMES 0 // #define SDFAT_FILE_TYPE 1 // // Options can be set in a makefile or an IDE like platformIO // if they are in a #ifndef/#endif block below. //------------------------------------------------------------------------------ /** 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 //------------------------------------------------------------------------------ #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 //------------------------------------------------------------------------------ /** * 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. */ #ifndef SDFAT_FILE_TYPE #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 #define USE_UTF8_LONG_NAMES 1 #else // defined(__AVR__) && FLASHEND < 0X8000 // All other boards. #define SDFAT_FILE_TYPE 3 #endif // defined(__AVR__) && FLASHEND < 0X8000 #endif // SDFAT_FILE_TYPE //------------------------------------------------------------------------------ /** * Set USE_FAT_FILE_FLAG_CONTIGUOUS nonzero to optimize access to * contiguous files. A small amount of flash is flash is used. */ #ifndef USE_FAT_FILE_FLAG_CONTIGUOUS #define USE_FAT_FILE_FLAG_CONTIGUOUS 1 #endif // USE_FAT_FILE_FLAG_CONTIGUOUS //------------------------------------------------------------------------------ /** * 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 extra flash and RAM. */ #ifndef ENABLE_DEDICATED_SPI #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 #endif // ENABLE_DEDICATED_SPI //------------------------------------------------------------------------------ // Driver options /** * 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. */ #ifndef SPI_DRIVER_SELECT #define SPI_DRIVER_SELECT 0 #endif // SPI_DRIVER_SELECT /** * 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. */ #ifndef USE_SPI_ARRAY_TRANSFER #define USE_SPI_ARRAY_TRANSFER 0 #endif // USE_SPI_ARRAY_TRANSFER /** * SD maximum initialization clock rate. */ #ifndef SD_MAX_INIT_RATE_KHZ #define SD_MAX_INIT_RATE_KHZ 400 #endif // SD_MAX_INIT_RATE_KHZ /** * Set USE_BLOCK_DEVICE_INTERFACE nonzero to use a generic block device. * This allow use of an external FsBlockDevice driver that is derived from * the FsBlockDeviceInterface like this: * * class UsbMscDriver : public FsBlockDeviceInterface { * ... code for USB mass storage class driver. * }; * * UsbMscDriver usbMsc; * FsVolume key; * ... * * // Init USB MSC driver. * if (!usbMsc.begin()) { * ... handle driver init failure. * } * // Init FAT/exFAT volume. * if (!key.begin(&usbMsc)) { * ... handle FAT/exFAT failure. * } */ #ifndef USE_BLOCK_DEVICE_INTERFACE #define USE_BLOCK_DEVICE_INTERFACE 1 #endif // USE_BLOCK_DEVICE_INTERFACE /** * 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. */ #ifndef SD_CHIP_SELECT_MODE #define SD_CHIP_SELECT_MODE 0 #endif // SD_CHIP_SELECT_MODE /** Type for card chip select pin. */ typedef uint8_t SdCsPin_t; //------------------------------------------------------------------------------ /** * 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) * */ #ifndef USE_LONG_FILE_NAMES #define USE_LONG_FILE_NAMES 1 #endif // USE_LONG_FILE_NAMES /** * Set USE_UTF8_LONG_NAMES nonzero to use UTF-8 file names. Use of UTF-8 names * will require significantly more flash memory and a small amount of extra * RAM. * * UTF-8 filenames allow encoding of 1,112,064 code points in Unicode using * one to four one-byte (8-bit) code units. * * As of Version 13.0, the Unicode Standard defines 143,859 characters. * * getName() will return UTF-8 strings and printName() will write UTF-8 strings. */ #ifndef USE_UTF8_LONG_NAMES #define USE_UTF8_LONG_NAMES 0 #endif // USE_UTF8_LONG_NAMES #if USE_UTF8_LONG_NAMES && !USE_LONG_FILE_NAMES #error "USE_UTF8_LONG_NAMES requires USE_LONG_FILE_NAMES to be non-zero." #endif // USE_UTF8_LONG_NAMES && !USE_LONG_FILE_NAMES //------------------------------------------------------------------------------ /** * 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. */ #ifndef MAINTAIN_FREE_CLUSTER_COUNT #if defined(__arm__) #define MAINTAIN_FREE_CLUSTER_COUNT 1 #else #define MAINTAIN_FREE_CLUSTER_COUNT 0 #endif #endif // MAINTAIN_FREE_CLUSTER_COUNT //------------------------------------------------------------------------------ /** * 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. */ #ifndef CHECK_FLASH_PROGRAMMING #define CHECK_FLASH_PROGRAMMING 0 #endif // CHECK_FLASH_PROGRAMMING //------------------------------------------------------------------------------ /** * 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. */ #ifndef USE_SD_CRC #define USE_SD_CRC 0 #endif // USE_SD_CRC //------------------------------------------------------------------------------ /** 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. */ #ifndef USE_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__) #endif // USE_FCNTL_H //------------------------------------------------------------------------------ /** * Set INCLUDE_SDIOS nonzero to include sdios.h in SdFat.h. * sdios.h provides C++ style IO Streams. */ #ifndef INCLUDE_SDIOS #define INCLUDE_SDIOS 0 #endif // INCLUDE_SDIOS //------------------------------------------------------------------------------ /** * Set FAT12_SUPPORT nonzero to enable use if FAT12 volumes. * FAT12 has not been well tested and requires additional flash. */ #ifndef FAT12_SUPPORT #if defined(__MK64FX512__) || defined(__MK66FX1M0__) || defined(__IMXRT1062__) // mainly defined for support of USBHost builds... But included T3.5... #define FAT12_SUPPORT 1 #else #define FAT12_SUPPORT 0 #endif #endif // FAT12_SUPPORT //------------------------------------------------------------------------------ /** * Set DESTRUCTOR_CLOSES_FILE nonzero 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 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. */ #ifndef ENDL_CALLS_FLUSH #define ENDL_CALLS_FLUSH 0 #endif // ENDL_CALLS_FLUSH //------------------------------------------------------------------------------ /** * Set USE_SIMPLE_LITTLE_ENDIAN nonzero for little endian processors * with no memory alignment restrictions. */ #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__\ && (defined(__AVR__) || defined(__ARM_FEATURE_UNALIGNED) || defined(__ARM_ARCH_7EM__)) #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 SDFAT_SDCARD_SPI #define SDFAT_SDCARD_SPI SPI1 #define SDFAT_SDCARD_MISO_PIN 59 #define SDFAT_SDCARD_MOSI_PIN 61 #define SDFAT_SDCARD_SCK_PIN 60 #define SDFAT_SDCARD_SS_PIN 62 #endif // SDFAT_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(SPDR) && defined(SPSR) && defined(SPIF))\ || (defined(__AVR__) && defined(SPI0) && defined(SPI_RXCIF_bm))\ || defined(ESP8266) || defined(ESP32)\ || defined(PLATFORM_ID)\ || defined(ARDUINO_SAM_DUE)\ || defined(STM32_CORE_VERSION)\ || 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/SpiDriver/SdSpiArduinoDriver.h ================================================ /** * Copyright (c) 2011-2021 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/SpiDriver/SdSpiArtemis.cpp ================================================ /** * Copyright (c) 2011-2021 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(); } //------------------------------------------------------------------------------ void SdSpiArduinoDriver::end() { m_spi->end(); } //------------------------------------------------------------------------------ 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) ================================================ FILE: firmware/3.0/lib/SdFat/src/SpiDriver/SdSpiAvr.h ================================================ /** * Copyright (c) 2011-2021 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::activate() { SPI.beginTransaction(m_spiSettings); } //------------------------------------------------------------------------------ inline void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) { (void)spiConfig; SPI.begin(); } //------------------------------------------------------------------------------ inline void SdSpiArduinoDriver::deactivate() { SPI.endTransaction(); } //------------------------------------------------------------------------------ inline void SdSpiArduinoDriver::end() { SPI.end(); } //------------------------------------------------------------------------------ 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; } #ifdef SPSR SPDR = 0XFF; while (--count) { // nops optimize loop for 16MHz CPU 8 MHz SPI nop; nop; while (!(SPSR & _BV(SPIF))) {} uint8_t in = SPDR; SPDR = 0XFF; *buf++ = in; } while (!(SPSR & _BV(SPIF))) {} *buf = SPDR; #elif defined(SPI_RXCIF_bm) SPI0.DATA = 0XFF; while (--count) { // nops optimize loop for ATmega4809 16MHz CPU 8 MHz SPI nop; nop; nop; nop; while (!(SPI0.INTFLAGS & SPI_RXCIF_bm)) {} uint8_t in = SPI0.DATA; SPI0.DATA = 0XFF; *buf++ = in; } while (!(SPI0.INTFLAGS & SPI_RXCIF_bm)) {} *buf = SPI0.DATA; #else // SPSR #error Unsupported AVR CPU - edit SdFatConfig.h to use standard SPI library. #endif // SPSR 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; } #ifdef SPSR SPDR = *buf++; while (--count) { uint8_t b = *buf++; // nops optimize loop for 16MHz CPU 8 MHz SPI nop; nop; while (!(SPSR & (1 << SPIF))) {} SPDR = b; } while (!(SPSR & (1 << SPIF))) {} #elif defined(SPI_RXCIF_bm) SPI0.DATA = *buf++; while (--count) { uint8_t b = *buf++; // nops optimize loop for ATmega4809 16MHz CPU 8 MHz SPI nop; nop; nop; while (!(SPI0.INTFLAGS & SPI_RXCIF_bm)) {} SPI0.DATA = b; } while (!(SPI0.INTFLAGS & SPI_RXCIF_bm)) {} #else // SPSR #error Unsupported AVR CPU - edit SdFatConfig.h to use standard SPI library. #endif // SPSR } #endif // SdSpiAvr_h ================================================ FILE: firmware/3.0/lib/SdFat/src/SpiDriver/SdSpiBareUnoDriver.h ================================================ /** * Copyright (c) 2011-2021 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() {} /** 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); } /** Deactivate SPI hardware. */ void deactivate() {} /** deactivate SPI driver. */ void end() {} /** 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/SpiDriver/SdSpiBaseClass.h ================================================ /** * Copyright (c) 2011-2021 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() {} /** deactivate SPI driver. */ virtual void end() {} /** 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/SpiDriver/SdSpiChipSelect.cpp ================================================ /** * Copyright (c) 2011-2021 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/SpiDriver/SdSpiDriver.h ================================================ /** * Copyright (c) 2011-2021 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 dedicated SPI. */ inline bool spiOptionDedicated(uint8_t opt) {return opt & DEDICATED_SPI;} #else // ENABLE_DEDICATED_SPI /** * \param[in] opt option field of SdSpiConfig. * \return true for dedicated SPI. */ inline bool spiOptionDedicated(uint8_t opt) {(void)opt; return false;} #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 = SHARED_SPI; /** 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/SpiDriver/SdSpiDue.cpp ================================================ /** * Copyright (c) 2011-2021 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; } //------------------------------------------------------------------------------ // 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::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 } //------------------------------------------------------------------------------ void SdSpiArduinoDriver::deactivate() { SPI.endTransaction(); } //------------------------------------------------------------------------------ void SdSpiArduinoDriver::end() { SPI.end(); } //------------------------------------------------------------------------------ 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) ================================================ FILE: firmware/3.0/lib/SdFat/src/SpiDriver/SdSpiESP.cpp ================================================ /** * Copyright (c) 2011-2021 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(SDFAT_SDCARD_SPI) && defined(SDFAT_SDCARD_SS_PIN) } else if (spiConfig.csPin == SDFAT_SDCARD_SS_PIN) { m_spi = &SDFAT_SDCARD_SPI; #endif // defined(SDFAT_SDCARD_SPI) && defined(SDFAT_SDCARD_SS_PIN) } else { m_spi = &SPI; } m_spi->begin(); } //------------------------------------------------------------------------------ void SdSpiArduinoDriver::deactivate() { m_spi->endTransaction(); } //------------------------------------------------------------------------------ void SdSpiArduinoDriver::end() { m_spi->end(); } //------------------------------------------------------------------------------ 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)) ================================================ FILE: firmware/3.0/lib/SdFat/src/SpiDriver/SdSpiLibDriver.h ================================================ /** * Copyright (c) 2011-2021 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(SDFAT_SDCARD_SPI) && defined(SDFAT_SDCARD_SS_PIN) } else if (spiConfig.csPin == SDFAT_SDCARD_SS_PIN) { m_spi = &SDFAT_SDCARD_SPI; #endif // defined(SDFAT_SDCARD_SPI) && defined(SDFAT_SDCARD_SS_PIN) } else { m_spi = &SPI; } m_spi->begin(); } //------------------------------------------------------------------------------ inline void SdSpiArduinoDriver::end() { m_spi->end(); } //------------------------------------------------------------------------------ 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[512]; memcpy(tmp, buf, count); m_spi->transfer(tmp, count); } #else // USE_SPI_ARRAY_TRANSFER for (size_t i = 0; i < count; i++) { m_spi->transfer(buf[i]); } #endif // USE_SPI_ARRAY_TRANSFER } #endif // SdSpiLibDriver_h ================================================ FILE: firmware/3.0/lib/SdFat/src/SpiDriver/SdSpiParticle.cpp ================================================ /** * Copyright (c) 2011-2021 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(); } //------------------------------------------------------------------------------ void SdSpiArduinoDriver::end() { m_spi->end(); } //------------------------------------------------------------------------------ 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) ================================================ FILE: firmware/3.0/lib/SdFat/src/SpiDriver/SdSpiSTM32.cpp ================================================ /** * Copyright (c) 2011-2021 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. */ // Driver for: https://github.com/rogerclarkmelbourne/Arduino_STM32 #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(); } //------------------------------------------------------------------------------ void SdSpiArduinoDriver::end() { m_spi->end(); } //------------------------------------------------------------------------------ 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__) ================================================ FILE: firmware/3.0/lib/SdFat/src/SpiDriver/SdSpiSTM32Core.cpp ================================================ /** * Copyright (c) 2011-2021 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. */ // Driver for: https://github.com/stm32duino/Arduino_Core_STM32 #include "SdSpiDriver.h" #if defined(SD_USE_CUSTOM_SPI) && defined(STM32_CORE_VERSION) //------------------------------------------------------------------------------ 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(); } //------------------------------------------------------------------------------ void SdSpiArduinoDriver::end() { m_spi->end(); } //------------------------------------------------------------------------------ uint8_t SdSpiArduinoDriver::receive() { return m_spi->transfer(0XFF); } //------------------------------------------------------------------------------ uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) { // Must send 0XFF - SD looks at send data for command. 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) { // Avoid stack overflow if bad count. This should cause a write error. if (count > 512) { return; } // Not easy to avoid receive so use tmp RX buffer. uint8_t rxBuf[512]; // Discard const - STM32 not const correct. m_spi->transfer(const_cast(buf), rxBuf, count); } #endif // defined(SD_USE_CUSTOM_SPI) && defined(STM32_CORE_VERSION) ================================================ FILE: firmware/3.0/lib/SdFat/src/SpiDriver/SdSpiSoftDriver.h ================================================ /** * Copyright (c) 2011-2021 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() {} /** deactivate SPI driver. */ void end() {} /** 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/SpiDriver/SdSpiTeensy3.cpp ================================================ /** * Copyright (c) 2011-2021 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(SDFAT_SDCARD_SPI) && defined(SDFAT_SDCARD_SS_PIN) } else if (spiConfig.csPin == SDFAT_SDCARD_SS_PIN) { m_spi = &SDFAT_SDCARD_SPI; m_spi->setMISO(SDFAT_SDCARD_MISO_PIN); m_spi->setMOSI(SDFAT_SDCARD_MOSI_PIN); m_spi->setSCK(SDFAT_SDCARD_SCK_PIN); #endif // defined(SDFAT_SDCARD_SPI) && defined(SDFAT_SDCARD_SS_PIN) } else { m_spi = &SPI; } m_spi->begin(); } //------------------------------------------------------------------------------ void SdSpiArduinoDriver::deactivate() { m_spi->endTransaction(); } //------------------------------------------------------------------------------ void SdSpiArduinoDriver::end() { m_spi->end(); } //------------------------------------------------------------------------------ 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) ================================================ FILE: firmware/3.0/lib/SdFat/src/common/ArduinoFiles.h ================================================ /** * Copyright (c) 2011-2021 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 "SysCall.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. */ #ifndef DOXYGEN_SHOULD_SKIP_THIS char* __attribute__((error("use getName(name, size)"))) name(); #endif // DOXYGEN_SHOULD_SKIP_THIS /** 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/common/CPPLINT.cfg ================================================ exclude_files=PrintBasic.cpp exclude_files=PrintBasic.h exclude_files=PrintTemplates.h ================================================ FILE: firmware/3.0/lib/SdFat/src/common/CompileDateTime.h ================================================ /** * Copyright (c) 2011-2021 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/common/DebugMacros.h ================================================ /** * Copyright (c) 2011-2021 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 "SysCall.h" // 0 - disable, 1 - fail, halt 2 - fail, halt, warn #define USE_DBG_MACROS 0 #if USE_DBG_MACROS #include "Arduino.h" #ifndef DBG_FILE #error DBG_FILE not defined #endif // DBG_FILE __attribute__((unused)) static void dbgFail(uint16_t line) { Serial.print(F("DBG_FAIL: ")); Serial.print(F(DBG_FILE)); Serial.write('.'); Serial.println(line); } __attribute__((unused)) static void dbgHalt(uint16_t line) { Serial.print(F("DBG_HALT: ")); Serial.print(F(DBG_FILE)); Serial.write('.'); Serial.println(line); while (true) {} } #define DBG_FAIL_MACRO dbgFail(__LINE__) #define DBG_HALT_MACRO dbgHalt(__LINE__) #define DBG_HALT_IF(b) if (b) {dbgHalt(__LINE__);} #else // USE_DBG_MACROS #define DBG_FAIL_MACRO #define DBG_HALT_MACRO #define DBG_HALT_IF(b) #endif // USE_DBG_MACROS #if USE_DBG_MACROS > 1 __attribute__((unused)) static void dbgWarn(uint16_t line) { Serial.print(F("DBG_WARN: ")); Serial.print(F(DBG_FILE)); Serial.write('.'); Serial.println(line); } #define DBG_WARN_MACRO dbgWarn(__LINE__) #define DBG_WARN_IF(b) if (b) {dbgWarn(__LINE__);} #else // USE_DBG_MACROS > 1 #define DBG_WARN_MACRO #define DBG_WARN_IF(b) #endif // USE_DBG_MACROS > 1 #endif // DebugMacros_h ================================================ FILE: firmware/3.0/lib/SdFat/src/common/FmtNumber.cpp ================================================ /** * Copyright (c) 2011-2021 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; } // See: https://github.com/hcs0/Hackers-Delight // 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; } ================================================ FILE: firmware/3.0/lib/SdFat/src/common/FmtNumber.h ================================================ /** * Copyright (c) 2011-2021 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/common/FsApiConstants.h ================================================ /** * Copyright (c) 2011-2021 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 "SysCall.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 ================================================ FILE: firmware/3.0/lib/SdFat/src/common/FsBlockDevice.h ================================================ /** * Copyright (c) 2011-2021 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 FsBlockDevice_h #define FsBlockDevice_h #include "SdCard/SdCard.h" #if HAS_SDIO_CLASS || USE_BLOCK_DEVICE_INTERFACE typedef FsBlockDeviceInterface FsBlockDevice; #else typedef SdCard FsBlockDevice; #endif #endif // FsBlockDevice_h ================================================ FILE: firmware/3.0/lib/SdFat/src/common/FsBlockDeviceInterface.h ================================================ /** * Copyright (c) 2011-2021 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 FsBlockDeviceInterface include file. */ #ifndef FsBlockDeviceInterface_h #define FsBlockDeviceInterface_h #include #include /** * \class FsBlockDeviceInterface * \brief FsBlockDeviceInterface class. */ class FsBlockDeviceInterface { public: virtual ~FsBlockDeviceInterface() {} /** end use of device */ virtual void end() {} /** * Check for FsBlockDevice 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; /** * Read multiple sectors with callback as each sector's data * * \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. * \param[in] callback Function to be called with each sector's data * \param[in] context Pointer to be passed to the callback function * \return true for success or false for failure. */ virtual bool readSectorsCallback(uint32_t sector, uint8_t* dst, size_t ns, void (*callback)(uint32_t sector, uint8_t *buf, void *context), void *context) { for (size_t i = 0; i < ns; i++) { if (!readSector(sector + i, dst)) return false; callback(sector + i, dst, context); } return true; } /** \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; /** * Write multiple sectors with callback for each sector's data * * \param[in] sector Logical sector to be written. * \param[in] ns Number of sectors to be written. * \param[in] callback Function to be called for each sector's data * \param[in] context Context to pass to callback function * \return true for success or false for failure. */ virtual bool writeSectorsCallback(uint32_t sector, size_t ns, const uint8_t * (*callback)(uint32_t sector, void *context), void *context) { for (size_t i = 0; i < ns; i++) { if (!writeSector(sector + i, callback(sector + i, context))) return false; } return true; } }; #endif // FsBlockDeviceInterface_h ================================================ FILE: firmware/3.0/lib/SdFat/src/common/FsCache.cpp ================================================ /** * Copyright (c) 2011-2021 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::prepare(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; } ================================================ FILE: firmware/3.0/lib/SdFat/src/common/FsCache.h ================================================ /** * Copyright (c) 2011-2021 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 "FsBlockDevice.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(FsBlockDevice* 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; } /** Prepare cache to access sector. * \param[in] sector Sector to read. * \param[in] option mode for cached sector. * \return Address of cached sector. */ uint8_t* prepare(uint32_t sector, uint8_t option); /** \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; } /** Write current sector if dirty. * \return true for success or false for failure. */ bool sync(); private: uint8_t m_status; FsBlockDevice* m_blockDev; uint32_t m_mirrorOffset; uint32_t m_sector; uint8_t m_buffer[512]; }; #endif // FsCache_h ================================================ FILE: firmware/3.0/lib/SdFat/src/common/FsDateTime.cpp ================================================ /** * Copyright (c) 2011-2021 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); } ================================================ FILE: firmware/3.0/lib/SdFat/src/common/FsDateTime.h ================================================ /** * Copyright (c) 2011-2021 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/common/FsName.cpp ================================================ /** * Copyright (c) 2011-2021 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 "FsName.h" #include "FsUtf.h" #if USE_UTF8_LONG_NAMES uint16_t FsName::get16() { uint16_t rtn; if (ls) { rtn = ls; ls = 0; } else if (next >= end) { rtn = 0; } else { uint32_t cp; const char* ptr = FsUtf::mbToCp(next, end, &cp); if (!ptr) { goto fail; } next = ptr; if (cp <= 0XFFFF) { rtn = cp; } else { ls = FsUtf::lowSurrogate(cp); rtn = FsUtf::highSurrogate(cp); } } return rtn; fail: return 0XFFFF; } #endif // USE_UTF8_LONG_NAMES ================================================ FILE: firmware/3.0/lib/SdFat/src/common/FsName.h ================================================ /** * Copyright (c) 2011-2021 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 FsName_h #define FsName_h #include "SysCall.h" #include /** * \file * \brief FsName class. */ /** * \class FsName * \brief Handle UTF-8 file names. */ class FsName { public: /** Beginning of LFN. */ const char* begin; /** Next LFN character of end. */ const char* next; /** Position one beyond last LFN character. */ const char* end; #if !USE_UTF8_LONG_NAMES /** \return true if at end. */ bool atEnd() {return next == end;} /** Reset to start of LFN. */ void reset() {next = begin;} /** \return next char of LFN. */ char getch() {return atEnd() ? 0 : *next++;} /** \return next UTF-16 unit of LFN. */ uint16_t get16() {return atEnd() ? 0 : *next++;} #else // !USE_UTF8_LONG_NAMES uint16_t ls = 0; bool atEnd() { return !ls && next == end; } void reset() { next = begin; ls = 0; // lowSurrogate } uint16_t get16(); #endif // !USE_UTF8_LONG_NAMES }; #endif // FsName_h ================================================ FILE: firmware/3.0/lib/SdFat/src/common/FsStructs.cpp ================================================ /** * Copyright (c) 2011-2021 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; } ================================================ FILE: firmware/3.0/lib/SdFat/src/common/FsStructs.h ================================================ /** * Copyright (c) 2011-2021 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 #include #include "SdFatConfig.h" //----------------------------------------------------------------------------- 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 //------------------------------------------------------------------------------ // Size of FAT and exFAT directory structures. const size_t FS_DIR_SIZE = 32; //------------------------------------------------------------------------------ // Reserved characters for exFAT names and FAT LFN. inline bool lfnReservedChar(uint8_t c) { return c < 0X20 || c == '"' || c == '*' || c == '/' || c == ':' || c == '<' || c == '>' || c == '?' || c == '\\'|| c == '|'; } //------------------------------------------------------------------------------ // Reserved characters for FAT short 8.3 names. inline bool sfnReservedChar(uint8_t c) { if (c == '"' || c == '|' || c == '[' || c == '\\' || c == ']') { return true; } // *+,./ or :;<=>? if ((0X2A <= c && c <= 0X2F && c != 0X2D) || (0X3A <= c && c <= 0X3F)) { return true; } // Reserved if not in range (0X20, 0X7F). return !(0X20 < c && c < 0X7F); } //------------------------------------------------------------------------------ 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; /** Max long file name length */ const uint8_t FAT_MAX_LFN_LENGTH = 255; 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; //----------------------------------------------------------------------------- // WIP GPT support for now just assume 16 bytes... typedef struct { uint8_t data[16]; } Guid_t; typedef struct { uint8_t signature[8]; uint8_t revision[4]; uint8_t headerSize[4]; uint8_t crc32[4]; uint8_t reserved[4]; uint8_t currentLBA[8]; uint8_t backupLBA[8]; uint8_t firstLBA[8]; uint8_t lastLBA[8]; uint8_t diskGUID[16]; uint8_t startLBAArray[8]; uint8_t numberPartitions[4]; uint8_t sizePartitionEntry[4]; uint8_t crc32PartitionEntries[4]; uint8_t unused[420]; // should be 0; } GPTPartitionHeader_t; typedef struct { uint8_t partitionTypeGUID[16]; uint8_t uniqueGUID[16]; uint8_t firstLBA[8]; uint8_t lastLBA[8]; uint8_t attributeFlags[8]; uint16_t name[36]; } GPTPartitionEntryItem_t; typedef struct { GPTPartitionEntryItem_t items[4]; } GPTPartitionEntrySector_t; #endif // FsStructs_h ================================================ FILE: firmware/3.0/lib/SdFat/src/common/FsUtf.cpp ================================================ /** * Copyright (c) 2011-2021 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 "FsUtf.h" namespace FsUtf { //---------------------------------------------------------------------------- char* cpToMb(uint32_t cp, char* str, char* end) { size_t n = end - str; if (cp < 0X80) { if (n < 1) goto fail; *(str++) = static_cast(cp); } else if (cp < 0X800) { if (n < 2) goto fail; *(str++) = static_cast((cp >> 6) | 0XC0); *(str++) = static_cast((cp & 0X3F) | 0X80); } else if (cp < 0X10000) { if (n < 3) goto fail; *(str++) = static_cast((cp >> 12) | 0XE0); *(str++) = static_cast(((cp >> 6) & 0X3F) | 0X80); *(str++) = static_cast((cp & 0X3F) | 0X80); } else { if (n < 4) goto fail; *(str++) = static_cast((cp >> 18) | 0XF0); *(str++) = static_cast(((cp >> 12) & 0X3F)| 0X80); *(str++) = static_cast(((cp >> 6) & 0X3F) | 0X80); *(str++) = static_cast((cp & 0X3F) | 0X80); } return str; fail: return nullptr; } //---------------------------------------------------------------------------- // to do? improve error check const char* mbToCp(const char* str, const char* end, uint32_t* rtn) { size_t n; uint32_t cp; if (str >= end) { return nullptr; } uint8_t ch = str[0]; if ((ch & 0X80) == 0) { *rtn = ch; return str + 1; } if ((ch & 0XE0) == 0XC0) { cp = ch & 0X1F; n = 2; } else if ((ch & 0XF0) == 0XE0) { cp = ch & 0X0F; n = 3; } else if ((ch & 0XF8) == 0XF0) { cp = ch & 0X07; n = 4; } else { return nullptr; } if ((str + n) > end) { return nullptr; } for (size_t i = 1; i < n; i++) { ch = str[i]; if ((ch & 0XC0) != 0X80) { return nullptr; } cp <<= 6; cp |= ch & 0X3F; } // Don't allow over long as ASCII. if (cp < 0X80 || !isValidCp(cp)) { return nullptr; } *rtn = cp; return str + n; } //---------------------------------------------------------------------------- const char* mbToU16(const char* str, const char* end, uint16_t* hs, uint16_t* ls) { uint32_t cp; const char* ptr = mbToCp(str, end, &cp); if (!ptr) { return nullptr; } if (cp <= 0XFFFF) { *hs = cp; *ls = 0; } else { *hs = highSurrogate(cp); *ls = lowSurrogate(cp); } return ptr; } } // namespace FsUtf ================================================ FILE: firmware/3.0/lib/SdFat/src/common/FsUtf.h ================================================ /** * Copyright (c) 2011-2021 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 FsUtf_h #define FsUtf_h /** * \file * \brief Unicode Transformation Format functions. */ #include #include namespace FsUtf { /** High surrogate for a code point. * \param{in} cp code point. * \return high surrogate. */ inline uint16_t highSurrogate(uint32_t cp) { return (cp >> 10) + (0XD800 - (0X10000 >> 10)); } /** Low surrogate for a code point. * \param{in} cp code point. * \return low surrogate. */ inline uint16_t lowSurrogate(uint32_t cp) { return (cp & 0X3FF) + 0XDC00; } /** Check for a valid code point. * \param[in] cp code point. * \return true if valid else false. */ inline bool isValidCp(uint32_t cp) { return cp <= 0x10FFFF && (cp < 0XD800 || cp > 0XDFFF); } /** Check for UTF-16 surrogate. * \param[in] c UTF-16 unit. * \return true if c is a surrogate else false. */ inline bool isSurrogate(uint16_t c) { return 0XD800 <= c && c <= 0XDFFF; } /** Check for UTF-16 high surrogate. * \param[in] c UTF-16 unit.. * \return true if c is a high surrogate else false. */ inline bool isHighSurrogate(uint16_t c) { return 0XD800 <= c && c <= 0XDBFF; } /** Check for UTF-16 low surrogate. * \param[in] c UTF-16 unit.. * \return true if c is a low surrogate else false. */ inline bool isLowSurrogate(uint16_t c) { return 0XDC00 <= c && c <= 0XDFFF; } /** Convert UFT-16 surrogate pair to code point. * \param[in] hs high surrogate. * \param[in] ls low surrogate. * \return code point. */ inline uint32_t u16ToCp(uint16_t hs, uint16_t ls) { return 0X10000 + (((hs & 0X3FF) << 10) | (ls & 0X3FF)); } /** Encodes a 32 bit code point as a UTF-8 sequence. * \param[in] cp code point to encode. * \param[out] str location for UTF-8 sequence. * \param[in] end location following last character of str. * \return location one beyond last encoded character. */ char* cpToMb(uint32_t cp, char* str, char* end); /** Get next code point from a UTF-8 sequence. * \param[in] str location for UTF-8 sequence. * \param[in] end location following last character of str. * May be nullptr if str is zero terminated. * \param[out] rtn location for the code point. * \return location of next UTF-8 character in str of nullptr for error. */ const char* mbToCp(const char* str, const char* end, uint32_t* rtn); /** Get next code point from a UTF-8 sequence as UTF-16. * \param[in] str location for UTF-8 sequence. * \param[in] end location following last character of str. * \param[out] hs location for the code point or high surrogate. * \param[out] ls location for zero or high surrogate. * \return location of next UTF-8 character in str of nullptr for error. */ const char* mbToU16(const char* str, const char* end, uint16_t* hs, uint16_t* ls); } // namespace FsUtf #endif // FsUtf_h ================================================ FILE: firmware/3.0/lib/SdFat/src/common/PrintBasic.cpp ================================================ /** * 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 "PrintBasic.h" #if ENABLE_ARDUINO_FEATURES == 0 #include size_t PrintBasic::print(long n, uint8_t base) { if (n < 0 && base == 10) { return print('-') + printNum(-n, base); } return printNum(n, base); } size_t PrintBasic::printNum(unsigned long n, uint8_t base) { const uint8_t DIM = 8*sizeof(long); char buf[DIM]; char *str = &buf[DIM]; if (base < 2) return 0; do { char c = n%base; n /= base; *--str = c + (c < 10 ? '0' : 'A' - 10); } while (n); return write(str, &buf[DIM] - str); } size_t PrintBasic::printDouble(double n, uint8_t prec) { // Max printable 32-bit floating point number. AVR uses 32-bit double. const double maxfp = static_cast(0XFFFFFF00UL); size_t rtn = 0; if (isnan(n)) { return write("NaN"); } if (n < 0) { n = -n; rtn += print('-'); } if (isinf(n)) { return rtn + write("Inf"); } if (n > maxfp) { return rtn + write("Ovf"); } double round = 0.5; for (uint8_t i = 0; i < prec; ++i) { round *= 0.1; } n += round; uint32_t whole = (uint32_t)n; rtn += print(whole); if (prec) { rtn += print('.'); double fraction = n - static_cast(whole); for (uint8_t i = 0; i < prec; i++) { fraction *= 10.0; uint8_t digit = fraction; rtn += print(digit); fraction -= digit; } } return rtn; } #endif // ENABLE_ARDUINO_FEATURES == 0 ================================================ FILE: firmware/3.0/lib/SdFat/src/common/PrintBasic.h ================================================ /** * 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 PrintBasic_h #define PrintBasic_h /** * \file * \brief Stream/Print like replacement for non-Arduino systems. */ #include #include #include #include "../SdFatConfig.h" #ifndef F #if defined(__AVR__) #include class __FlashStringHelper; #define F(string_literal) (reinterpret_cast(PSTR(string_literal))) #else // defined(__AVR__) #define F(str) (str) #endif // defined(__AVR__) #endif // F #ifdef BIN #undef BIN #endif // BIN #define BIN 2 #define OCT 8 #define DEC 10 #define HEX 16 class PrintBasic { public: PrintBasic() : m_error(0) {} void clearWriteError() { setWriteError(0); } int getWriteError() { return m_error; } size_t print(char c) { return write(c); } size_t print(const char* str) { return write(str); } size_t print(const __FlashStringHelper *str) { #ifdef __AVR__ PGM_P p = reinterpret_cast(str); size_t n = 0; for (uint8_t c; (c = pgm_read_byte(p + n)) && write(c); n++) {} return n; #else // __AVR__ return print(reinterpret_cast(str)); #endif // __AVR__ } size_t println(const __FlashStringHelper *str) { #ifdef __AVR__ return print(str) + println(); #else // __AVR__ return println(reinterpret_cast(str)); #endif // __AVR__ } size_t print(double n, uint8_t prec = 2) { return printDouble(n, prec); } size_t print(signed char n, uint8_t base = 10) { return print((long)n, base); } size_t print(unsigned char n, uint8_t base = 10) { return print((unsigned long)n, base); } size_t print(int n, uint8_t base = 10) { return print((long)n, base); } size_t print(unsigned int n, uint8_t base = 10) { return print((unsigned long)n, base); } size_t print(long n, uint8_t base = 10); size_t print(unsigned long n, uint8_t base = 10) { return printNum(n, base); } size_t println() { return write("\r\n"); } size_t println(char c) { return write(c) + println(); } size_t println(const char* str) { return print(str) + println(); } size_t println(double n, uint8_t prec = 2) { return print(n, prec) + println(); } size_t println(signed char n, uint8_t base = 10) { return print(n, base) + println(); } size_t println(unsigned char n, uint8_t base = 10) { return print(n, base) + println(); } size_t println(int n, uint8_t base = 10) { return print(n, base) + println(); } size_t println(unsigned int n, uint8_t base = 10) { return print(n, base) + println(); } size_t println(long n, uint8_t base = 10) { return print(n, base) + println(); } size_t println(unsigned long n, uint8_t base = 10) { return print(n, base) + println(); } size_t write(const char *str) { return write(str, strlen(str)); } virtual size_t write(uint8_t b) = 0; virtual size_t write(const uint8_t* buffer, size_t size) { size_t i; for (i = 0; i < size; i++) { if (!write(buffer[i])) break; } return i; } size_t write(const char *buffer, size_t size) { return write((const uint8_t*)buffer, size); } protected: void setWriteError(int err = 1) { m_error = err; } private: size_t printDouble(double n, uint8_t prec); size_t printNum(unsigned long n, uint8_t base); int m_error; }; //------------------------------------------------------------------------------ class StreamBasic : public PrintBasic { public: virtual int available() = 0; virtual int peek() = 0; virtual int read() = 0; }; #endif // PrintBasic_h ================================================ FILE: firmware/3.0/lib/SdFat/src/common/SysCall.h ================================================ /** * Copyright (c) 2011-2021 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 //------------------------------------------------------------------------------ #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 //------------------------------------------------------------------------------ #else // ENABLE_ARDUINO_FEATURES #include "PrintBasic.h" /** If not Arduino */ typedef PrintBasic print_t; /** If not Arduino */ typedef PrintBasic stream_t; #endif // ENABLE_ARDUINO_FEATURES #endif // SysCall_h ================================================ FILE: firmware/3.0/lib/SdFat/src/common/upcase.cpp ================================================ /** * Copyright (c) 2011-2021 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 "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; } //------------------------------------------------------------------------------ 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; } ================================================ FILE: firmware/3.0/lib/SdFat/src/common/upcase.h ================================================ /** * Copyright (c) 2011-2021 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 uint16_t toUpcase(uint16_t chr); uint32_t upcaseChecksum(uint16_t unicode, uint32_t checksum); #endif // upcase_h ================================================ FILE: firmware/3.0/lib/SdFat/src/iostream/ArduinoStream.h ================================================ /** * Copyright (c) 2011-2021 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 "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()) { 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_t& 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_t* m_pr; }; #endif // ArduinoStream_h ================================================ FILE: firmware/3.0/lib/SdFat/src/iostream/StdioStream.cpp ================================================ /** * Copyright (c) 2011-2021 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); } ================================================ FILE: firmware/3.0/lib/SdFat/src/iostream/StdioStream.h ================================================ /** * Copyright (c) 2011-2021 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/iostream/StreamBaseClass.cpp ================================================ /** * Copyright (c) 2011-2021 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); } ================================================ FILE: firmware/3.0/lib/SdFat/src/iostream/bufstream.h ================================================ /** * Copyright (c) 2011-2021 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/iostream/fstream.h ================================================ /** * Copyright (c) 2011-2021 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/iostream/ios.h ================================================ /** * Copyright (c) 2011-2021 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 || defined(DOXYGEN) /** 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/iostream/iostream.h ================================================ /** * Copyright (c) 2011-2021 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/iostream/istream.cpp ================================================ /** * Copyright (c) 2011-2021 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); } ================================================ FILE: firmware/3.0/lib/SdFat/src/iostream/istream.h ================================================ /** * Copyright (c) 2011-2021 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/iostream/ostream.cpp ================================================ /** * Copyright (c) 2011-2021 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); } ================================================ FILE: firmware/3.0/lib/SdFat/src/iostream/ostream.h ================================================ /** * Copyright (c) 2011-2021 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 ================================================ FILE: firmware/3.0/lib/SdFat/src/sdios.h ================================================ /** * Copyright (c) 2011-2021 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 ================================================ FILE: firmware/3.0/lib/Time/DateStrings.cpp ================================================ /* 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; } ================================================ FILE: firmware/3.0/lib/Time/Time.cpp ================================================ /* 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; } ================================================ FILE: firmware/3.0/lib/Time/Time.h ================================================ #include "TimeLib.h" ================================================ FILE: firmware/3.0/lib/Time/TimeLib.h ================================================ /* 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 */ ================================================ FILE: firmware/3.0/lib/Time/library.properties ================================================ name=Time version=1.6.1 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=* ================================================ FILE: firmware/3.0/lib/Wire/Wire.cpp ================================================ /* 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::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 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; } // // 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; } // 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__ ================================================ FILE: firmware/3.0/lib/Wire/Wire.h ================================================ /* 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 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); 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, bool sendStop) { return requestFrom(address, quantity, (uint8_t)(sendStop ? 1 : 0)); } 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); 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 ================================================ FILE: firmware/3.0/lib/Wire/WireIMXRT.cpp ================================================ #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) | IOMUXC_PAD_HYS) //*************************************************** // Master Mode //*************************************************** FLASHMEM 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); // setSDA() & setSCL() may be called before or after begin() configSDApin(sda_pin_index_); // Setup SDA register configSCLpin(scl_pin_index_); // setup SCL register } void TwoWire::end() { } 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); } 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; } //*************************************************** // Slave Mode //*************************************************** // registers start on page 2835 void TwoWire::begin(uint8_t address) { CCM_CSCDR2 = (CCM_CSCDR2 & ~CCM_CSCDR2_LPI2C_CLK_PODF(63)) | CCM_CSCDR2_LPI2C_CLK_SEL; hardware.clock_gate_register |= hardware.clock_gate_mask; // setSDA() & setSCL() may be called before or after begin() configSDApin(sda_pin_index_); // Setup SDA register configSCLpin(scl_pin_index_); // setup SCL register port->SCR = LPI2C_SCR_RST; port->SCR = 0; port->SCFGR1 = LPI2C_SCFGR1_TXDSTALL | LPI2C_SCFGR1_RXSTALL; // page 2841 port->SCFGR2 = 0; // page 2843; port->SAMR = LPI2C_SAMR_ADDR0(address); attachInterruptVector(hardware.irq_number, hardware.irq_function); NVIC_SET_PRIORITY(hardware.irq_number, 144); NVIC_ENABLE_IRQ(hardware.irq_number); port->SIER = LPI2C_SIER_TDIE | LPI2C_SIER_RDIE | LPI2C_SIER_SDIE; transmitting = 0; slave_mode = 1; port->SCR = LPI2C_SCR_SEN; } void TwoWire::isr(void) { uint32_t status = port->SSR; uint32_t w1c_bits = status & 0xF00; if (w1c_bits) port->SSR = w1c_bits; //Serial.print("isr "); //Serial.println(status, HEX); if (status & LPI2C_SSR_RDF) { // Receive Data Flag int rx = port->SRDR; if (rx & 0x8000) { rxBufferIndex = 0; rxBufferLength = 0; } if (rxBufferLength < BUFFER_LENGTH) { rxBuffer[rxBufferLength++] = rx & 255; } //Serial.print("rx = "); //Serial.println(rx, HEX); } if (status & LPI2C_SSR_TDF) { // Transmit Data Flag if (!transmitting) { if (user_onRequest != nullptr) { (*user_onRequest)(); } txBufferIndex = 0; transmitting = 1; } if (txBufferIndex < txBufferLength) { port->STDR = txBuffer[txBufferIndex++]; } else { port->STDR = 0; } //Serial.println("tx"); } if (status & LPI2C_SSR_SDF) { // Stop //Serial.println("Stop"); if (rxBufferLength > 0 && user_onReceive != nullptr) { (*user_onReceive)(rxBufferLength); } rxBufferIndex = 0; rxBufferLength = 0; txBufferIndex = 0; txBufferLength = 0; transmitting = 0; } } //*************************************************** // Pins Configuration //*************************************************** FLASHMEM 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)) { // disable old pin, hard to know what to go back to? *(portConfigRegister(hardware.sda_pins[sda_pin_index_].pin)) = 5; // setup new one... configSDApin(newindex); } sda_pin_index_ = newindex; } FLASHMEM void TwoWire::configSDApin(uint8_t i) { *(portControlRegister(hardware.sda_pins[i].pin)) = PINCONFIG; *(portConfigRegister(hardware.sda_pins[i].pin)) = hardware.sda_pins[i].mux_val; if (hardware.sda_pins[i].select_input_register) { *(hardware.sda_pins[i].select_input_register) = hardware.sda_pins[i].select_val; } } FLASHMEM 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)) { // disable old pin, hard to know what to go back to? *(portConfigRegister(hardware.scl_pins[scl_pin_index_].pin)) = 5; // setup new one... configSCLpin(newindex); } scl_pin_index_ = newindex; } FLASHMEM void TwoWire::configSCLpin(uint8_t i) { *(portControlRegister(hardware.scl_pins[i].pin)) = PINCONFIG; *(portConfigRegister(hardware.scl_pins[i].pin)) = hardware.scl_pins[i].mux_val; if (hardware.scl_pins[i].select_input_register) { *(hardware.scl_pins[i].select_input_register) = hardware.scl_pins[i].select_val; } } #if defined(ARDUINO_TEENSY_MICROMOD) void lpi2c1_isr(void) { Wire.isr(); } void lpi2c3_isr(void) { Wire2.isr(); } void lpi2c4_isr(void) { Wire1.isr(); } void lpi2c2_isr(void) { Wire3.isr(); } #else void lpi2c1_isr(void) { Wire.isr(); } void lpi2c3_isr(void) { Wire1.isr(); } void lpi2c4_isr(void) { Wire2.isr(); } #endif 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, &lpi2c1_isr }; TwoWire Wire(&IMXRT_LPI2C1, TwoWire::i2c1_hardware); PROGMEM constexpr TwoWire::I2C_Hardware_t TwoWire::i2c3_hardware = { CCM_CCGR2, CCM_CCGR2_LPI2C3(CCM_CCGR_ON), #if defined(ARDUINO_TEENSY41) {{17, 1 | 0x10, &IOMUXC_LPI2C3_SDA_SELECT_INPUT, 2}, {44, 2 | 0x10, &IOMUXC_LPI2C3_SDA_SELECT_INPUT, 1}}, {{16, 1 | 0x10, &IOMUXC_LPI2C3_SCL_SELECT_INPUT, 2}, {45, 2 | 0x10, &IOMUXC_LPI2C3_SCL_SELECT_INPUT, 1}}, #else // T4 and ARDUINO_TEENSY_MICROMOD {{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}}, #endif IRQ_LPI2C3, &lpi2c3_isr }; //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, &lpi2c4_isr }; //TwoWire Wire2(&IMXRT_LPI2C4, TwoWire::i2c4_hardware); #if defined(ARDUINO_TEENSY_MICROMOD) TwoWire Wire2(&IMXRT_LPI2C3, TwoWire::i2c3_hardware); TwoWire Wire1(&IMXRT_LPI2C4, TwoWire::i2c4_hardware); #else TwoWire Wire1(&IMXRT_LPI2C3, TwoWire::i2c3_hardware); TwoWire Wire2(&IMXRT_LPI2C4, TwoWire::i2c4_hardware); #endif #if defined(ARDUINO_TEENSY_MICROMOD) PROGMEM constexpr TwoWire::I2C_Hardware_t TwoWire::i2c2_hardware = { CCM_CCGR2, CCM_CCGR2_LPI2C2(CCM_CCGR_ON), {{41, 2 | 0x10, &IOMUXC_LPI2C2_SDA_SELECT_INPUT, 1}, {0xff, 0xff, nullptr, 0}}, {{40, 2 | 0x10, &IOMUXC_LPI2C2_SCL_SELECT_INPUT, 1}, {0xff, 0xff, nullptr, 0}}, IRQ_LPI2C2, &lpi2c2_isr }; TwoWire Wire3(&IMXRT_LPI2C2, TwoWire::i2c2_hardware); #endif // 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 ================================================ FILE: firmware/3.0/lib/Wire/WireIMXRT.h ================================================ /* 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 136 //#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_number; void (*irq_function)(void); } 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, bool sendStop) { return requestFrom(address, quantity, (uint8_t)(sendStop ? 1 : 0)); } 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); void configSDApin(uint8_t index); void configSCLpin(uint8_t index); 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 lpi2c1_isr(void); friend void lpi2c2_isr(void); friend void lpi2c3_isr(void); friend void lpi2c4_isr(void); friend void sda_rising_isr0(void); friend void sda_rising_isr1(void); }; extern TwoWire Wire; extern TwoWire Wire1; extern TwoWire Wire2; #if defined(ARDUINO_TEENSY_MICROMOD) 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 ================================================ FILE: firmware/3.0/lib/Wire/WireKinetis.cpp ================================================ /* 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); #define CLOCK_GATE_REG(addr) (*(volatile uint32_t *)(addr)) 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; CLOCK_GATE_REG(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 (!(CLOCK_GATE_REG(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 ((CLOCK_GATE_REG(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 ((CLOCK_GATE_REG(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 (!(CLOCK_GATE_REG(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; CLOCK_GATE_REG(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_ADDRESS, 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_ADDRESS, 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_ADDRESS, 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_ADDRESS, 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 = KINETIS_I2C0_ADDRESS; TwoWire Wire(i2c0_addr, TwoWire::i2c0_hardware); void i2c0_isr(void) { Wire.isr(); } #endif #ifdef WIRE_IMPLEMENT_WIRE1 constexpr uintptr_t i2c1_addr = KINETIS_I2C1_ADDRESS; TwoWire Wire1(i2c1_addr, TwoWire::i2c1_hardware); void i2c1_isr(void) { Wire1.isr(); } #endif #ifdef WIRE_IMPLEMENT_WIRE2 constexpr uintptr_t i2c2_addr = KINETIS_I2C2_ADDRESS; TwoWire Wire2(i2c2_addr, TwoWire::i2c2_hardware); void i2c2_isr(void) { Wire2.isr(); } #endif #ifdef WIRE_IMPLEMENT_WIRE3 constexpr uintptr_t i2c3_addr = KINETIS_I2C3_ADDRESS; TwoWire Wire3(i2c3_addr, TwoWire::i2c3_hardware); void i2c3_isr(void) { Wire3.isr(); } #endif #endif // __arm__ && TEENSYDUINO ================================================ FILE: firmware/3.0/lib/Wire/WireKinetis.h ================================================ /* 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 #if defined(__MKL26Z64__) || defined(__MK20DX128__) #define BUFFER_LENGTH 40 #elif defined(__MK20DX256__) #define BUFFER_LENGTH 72 #else #define BUFFER_LENGTH 136 #endif #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 { uintptr_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, bool sendStop) { return requestFrom(address, quantity, (uint8_t)(sendStop ? 1 : 0)); } 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 ================================================ FILE: firmware/3.0/lib/Wire/utility/twi.c ================================================ /* 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__ ================================================ FILE: firmware/3.0/lib/Wire/utility/twi.h ================================================ /* 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 ================================================ FILE: firmware/3.0/platformio.ini ================================================ [env:default] platform = teensy board = teensy41 framework = arduino build_flags = -D USB_MTPDISK_SERIAL -D TEENSY_OPT_FASTEST board_build.f_cpu = 450000000L lib_deps = ADC Bounce EEPROM LittleFS Metro SD SdFat SPI Time MTP Wire ================================================ FILE: firmware/3.0/src/general/colorschemes.cpp ================================================ /* * * 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 }; ================================================ FILE: firmware/3.0/src/general/globalvariables.cpp ================================================ /* * * 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.0.4 from 20.10.2023"; uint16_t fwVersion = 304; //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; //Display is currently updated, do not use SPI in IRQ volatile bool disableSPIIRQ; ================================================ FILE: firmware/3.0/src/gui/bitmaps.cpp ================================================ /* * * 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 }; ================================================ FILE: firmware/3.0/src/gui/buttons.cpp ================================================ /* * * 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); } ================================================ FILE: firmware/3.0/src/gui/firststart.cpp ================================================ /* * * 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 while (!touch_touched()) ; //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 while (!touch_touched()) ; //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(); } ================================================ FILE: firmware/3.0/src/gui/gui.cpp ================================================ /* * * 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", 50, 50); display_print((char *)"Touch", 50, 85); display_print((char *)"Battery Gauge", 50, 120); display_print((char *)"FLIR Lepton", 50, 155); 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, 85); else display_print((char *)"Failed", 220, 85); //Check battery gauge if (checkDiagnostic(diag_bat)) display_print((char *)"OK ", 220, 120); else display_print((char *)"Failed", 220, 120); //Check FLIR Lepton if (checkDiagnostic(diag_lepton)) display_print((char *)"OK ", 220, 155); else display_print((char *)"Failed", 220, 155); //Check sd card if (checkDiagnostic(diag_sd)) display_print((char *)"OK ", 220, 190); else display_print((char *)"Failed", 220, 190); //Show hint display_print((char *)"Touch to continue", CENTER, 220); //Wait until touch while (!touch_touched()); showFullMessage((char*) "Trying to continue boot, may freeze.."); } /* 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); } ================================================ FILE: firmware/3.0/src/gui/livemode.cpp ================================================ /* * * 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; } ================================================ FILE: firmware/3.0/src/gui/loadmenu.cpp ================================================ /* * * 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(uint32_t 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 uint32_t videoCounter = 0; bool exists; char filename[] = "000000.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[6], ".BMP"); if (sd.exists(filename)) sd.remove(filename); //Remove Jpeg if there strcpy(&filename[6], ".JPG"); if (sd.exists(filename)) sd.remove(filename); //Reset ending strcpy(&filename[6], ".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); uint32_t frames = getVideoFrameNumber(); char filename[] = "000000.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, uint32_t 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 */ uint32_t getVideoFrameNumber() { uint32_t videoCounter = 0; bool exists; char filename[] = "000000.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(uint32_t imgCount) { char filename[] = "000000.DAT"; //Get the frame name frameFilename(filename, imgCount); //Load Raw data loadRawData(filename); //Display Raw Data displayRawData(); } /* Play a video from the internal storage */ void playVideo(char* dirname, uint32_t imgCount) { char buffer[14]; //Save the current frame number uint32_t frameNumber = 0; //Switch to video folder sd.chdir("/"); sd.chdir(dirname); //Get the total number of frames in the dir uint32_t numberOfFrames = getVideoFrameNumber(); //Jump here when pausing a video showFrame: //Display frame displayVideoFrame(frameNumber); //Create string sprintf(buffer, "%6lu / %-6lu", 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, "%6lu / %-6lu", 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; } } } } ================================================ FILE: firmware/3.0/src/gui/mainmenu.cpp ================================================ /* * * 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) { //Enter mass storage on USB connect checkMassStorage(); //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(); } ================================================ FILE: firmware/3.0/src/gui/settingsmenu.cpp ================================================ /* * * 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(); } ================================================ FILE: firmware/3.0/src/gui/videomenu.cpp ================================================ /* * * 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, uint32_t* framesCaptured, uint16_t* folderFrames, char* buffer, char* dirName) { //Measure time long measure = millis(); //If there is no more time or the first frame if ((*remainingTime <= 0) || (*folderFrames == 0)) { saveRawData(false, dirName, *folderFrames); *folderFrames = *folderFrames + 1; } //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(uint32_t* framesCaptured, uint16_t* folderFrames, char* buffer, char* dirName) { //Save video raw frame saveRawData(false, dirName, *folderFrames); *folderFrames = *folderFrames + 1; //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: %6lu", *framesCaptured); display_print(buffer, 70, 200); //Disable image buffer display_writeToImage = false; //Refresh capture displayBuffer(); } void videoCreateFolder(char *dirName) { createSDName(dirName, true); if(!sd.chdir("/")) { beginSD(); if(!sd.chdir("/")) { showFullMessage((char*) "Error creating folder!"); delay(1000); return; } } sd.mkdir(dirName); sd.chdir(dirName); } /* This screen is shown during the video capture */ void videoCapture() { //Help variables char dirName[20]; char buffer[30]; int16_t delayTime = videoInterval; uint32_t framesCaptured = 0; uint16_t folderFrames = 0; //Show message showFullMessage((char*)"Touch screen to turn it on/off"); display_print((char*) "CAPTURING FRAMES..", CENTER, 50); display_print((char*) "Press push button to stop", CENTER, 170); delay(1000); //Create folder videoCreateFolder(dirName); //Switch to recording mode videoSave = videoSave_recording; lepton_startFrame(); //Main loop while (videoSave == videoSave_recording) { //Do not store too many files in one folder, otherwise MTP will make issues if(folderFrames >= 1000) { videoCreateFolder(dirName); folderFrames = 0; } //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, &folderFrames, buffer, dirName); } //Interval capture else { videoCaptureInterval(&delayTime, &framesCaptured, &folderFrames, buffer, dirName); } lepton_startFrame(); } //Turn the display on if it was off before if (!checkScreenLight()) enableScreenLight(); //Post processing for interval videos if enabled if ((framesCaptured > 0) && convertEnabled) 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() { //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; } } } } ================================================ FILE: firmware/3.0/src/hardware/battery.cpp ================================================ /* * * 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); } } ================================================ FILE: firmware/3.0/src/hardware/connection.cpp ================================================ /* * * 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]); } /* 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(2); } /* Send the diagnostic information */ void sendDiagnostic() { //Send the diag byte Serial.write(diagnostic); } /* 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 calibration data case CMD_GET_CALIBDATA: sendCalibrationData(); break; //Send spot temp case CMD_GET_SPOTTEMP: sendSpotTemp(); break; //Send temperature points case CMD_GET_TEMPPOINTS: sendTempPoints(); 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; //Send battery status case CMD_GET_BATTERYSTATUS: sendBatteryStatus(); 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; //Get diagnostic information case CMD_GET_DIAGNOSTIC: sendDiagnostic(); 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; } 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(); } /* 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 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(); } ================================================ FILE: firmware/3.0/src/hardware/display/display.cpp ================================================ /* * * 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_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() { if(videoSave == videoSave_recording) { SPI.beginTransaction(SPISettings(60000000, MSBFIRST, SPI_MODE0)); } else { SPI.beginTransaction(SPISettings(25000000, 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 320x240 RGB565 data to the screen */ void display_writeScreen(unsigned short *pcolors) { disableSPIIRQ = true; display_begin_spi_transaction(); display_setAddr(0, 0, 319, 239); display_writecommand_cont(ILI9341_RAMWR); uint16_t *pfbtft_end = &pcolors[(ILI9341_TFTWIDTH * ILI9341_TFTHEIGHT) - 1]; uint16_t *pftbft = pcolors; while (pftbft < pfbtft_end) { display_writedata16_cont(*pftbft++); } display_writedata16_last(*pftbft); display_end_spi_transaction(); disableSPIIRQ = false; } ================================================ FILE: firmware/3.0/src/hardware/display/fonts.cpp ================================================ /* * * 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 // ~ }; ================================================ FILE: firmware/3.0/src/hardware/hardware.cpp ================================================ /* * * 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); } /* 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); } ================================================ FILE: firmware/3.0/src/hardware/lepton.cpp ================================================ /* * * 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 */ 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() { if (disableSPIIRQ) return; 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 long timer = millis(); while ((lepton_readReg(0x2) & 0x01) && ((millis() - timer) < 1000)) ; if ((millis() - timer) >= 1000) return 1; 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) 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 if (error != 0) 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.1R or Lepton3.5 Shuttered (Radiometric) else if ((strstr(leptonhw, "05-070170") != NULL) || (strstr(leptonhw, "05-070850") != 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 the Lepton version if (!lepton_version()) { setDiagnostic(diag_lepton); return; } //Enable VSync IRQ lepton_setGpioMode(true); //Do FFC lepton_ffc(); autoMode = true; limitsLocked = false; leptonBufferValid = false; } ================================================ FILE: firmware/3.0/src/hardware/massstorage.cpp ================================================ /* * * 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; } } ================================================ FILE: firmware/3.0/src/hardware/sdcard.cpp ================================================ /* * * 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; } ================================================ FILE: firmware/3.0/src/hardware/touchscreen/ft6206_touchscreen.cpp ================================================ /* * * 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(); } ================================================ FILE: firmware/3.0/src/hardware/touchscreen/touchscreen.cpp ================================================ /* * * 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; } ================================================ FILE: firmware/3.0/src/hardware/touchscreen/xpt2046_touchscreen.cpp ================================================ /* * * 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; } ================================================ FILE: firmware/3.0/src/main.cpp ================================================ /* * * 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() { } ================================================ FILE: firmware/3.0/src/thermal/create.cpp ================================================ /* * * 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); } ================================================ FILE: firmware/3.0/src/thermal/load.cpp ================================================ /* * * 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); } ================================================ FILE: firmware/3.0/src/thermal/save.cpp ================================================ /* * * 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); } } /* 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, uint32_t count) { filename[0] = '0' + count / 100000 % 10; filename[1] = '0' + count / 10000 % 10; filename[2] = '0' + count / 1000 % 10; filename[3] = '0' + count / 100 % 10; filename[4] = '0' + count / 10 % 10; filename[5] = '0' + count % 10; } /* Save video frame to image file */ void saveVideoFrame(char *filename) { // Open the file for writing if (!sdFile.open(filename, O_RDWR | O_CREAT | O_AT_END)) { beginSD(); if (!sdFile.open(filename, O_RDWR | O_CREAT | O_AT_END)) { showFullMessage((char *)"Error saving BMP!"); delay(1000); return; } } //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(uint32_t framesCaptured, char *dirname) { char buffer[30]; char filename[] = "000000.DAT"; uint32_t 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: %6lu / %6lu", 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[6], ".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[6], ".BMP"); saveVideoFrame(filename); //Font color display_setBackColor(200, 200, 200); display_setFont(smallFont); display_setColor(VGA_BLACK); //Update screen content sprintf(buffer, "Frames converted: %6lu / %6lu", 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, uint32_t framesCaptured) { uint16_t result; //Create filename for image if (isImage) { strcpy(&name[14], ".DAT"); if (!sdFile.open(name, O_RDWR | O_CREAT | O_AT_END)) { beginSD(); if (!sdFile.open(name, O_RDWR | O_CREAT | O_AT_END)) { showFullMessage((char *)"Error saving image!"); delay(1000); return; } } } //Create filename for video frame else { char fileName[] = "000000.DAT"; frameFilename(fileName, framesCaptured); if (!sdFile.open(fileName, O_RDWR | O_CREAT | O_AT_END)) { beginSD(); sd.chdir("/" + String(name)); if (!sdFile.open(fileName, O_RDWR | O_CREAT | O_AT_END)) { showFullMessage((char *)"Error saving frame!"); delay(1000); return; } } } //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 strcpy(&filename[14], ".BMP"); if (!sdFile.open(filename, O_RDWR | O_CREAT | O_AT_END)) { beginSD(); if (!sdFile.open(filename, O_RDWR | O_CREAT | O_AT_END)) { showFullMessage((char *)"Error saving BMP!"); delay(1000); return; } } //Write the BMP header sdFile.write((uint8_t *)bmp_header_large, 66); //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(); } ================================================ FILE: firmware/3.0/src/thermal/temperature.cpp ================================================ /* * * 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; } ================================================ FILE: firmware/3.0/src/thermal/thermal.cpp ================================================ /* * * 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()) ; } } /* 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; disableSPIIRQ = 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(); } } ================================================ FILE: firmware/README.MD ================================================ # DIY-Thermocam - Firmware ### THIS GUIDE IS ONLY REQUIRED, IF YOU WANT TO MAKE CHANGES TO THE FIRMWARE ON YOUR OWN In case you want to flash the newest version of the firmware to your device without modification, **go to the release section of this repo**. This guide should work on **all common operating systems** (**Windows, Linux & macOS**). You need to have [VS Code](https://code.visualstudio.com/) with the [PlatformIO extension](https://platformio.org/install/ide?install=vscode) installed and download the zip archive of this repository. Then **unpack the 3.0 folder inside the firmware subfolder**, **start VS Code** and open this folder 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**. If this is not the case, **set it up manually using the instructions provided [here](https://platformio.org/install/ide?install=vscode)**. **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. ================================================ FILE: pcb/3.0/pcb.brd ================================================ R LEPTON CHARGER BOOSTER G LIPO PROGRAM BATTERY MICROCONTROLLER + - DIY-THERMOCAM V3 www.diy-thermocam.net REV 3.0 <h3>SparkFun Electronics' preferred foot prints</h3> In this library you'll find connectors and sockets- basically anything that can be plugged into or onto.<br><br> We've spent an enormous amount of time creating and checking these footprints and parts, but it is the end user's responsibility to ensure correctness and suitablity for a given componet or application. If you enjoy using this library, please buy one of our products at www.sparkfun.com. <br><br> <b>Licensing:</b> Creative Commons ShareAlike 4.0 International - https://creativecommons.org/licenses/by-sa/4.0/ <br><br> You are welcome to use this library for commercial purposes. For attribution, we ask that when you begin to sell your device using our footprint, you email us with a link to the product being sold. We want bragging rights that we helped (in a very small part) to create your 8th world wonder. We would like the opportunity to feature your device on our homepage. >NAME >VALUE >NAME >VALUE >NAME >VALUE <h3>Plated Through Hole - 2x10 No Silk Outline</h3> <p>Specifications: <ul><li>Pin count:20</li> <li>Pin pitch:0.1"</li> </ul></p> <p>Example device(s): <ul><li>CONN_10x2</li> </ul></p> >NAME >VALUE <h3>SparkFun Electronics' preferred foot prints</h3> In this library you'll find resistors, capacitors, inductors, test points, jumper pads, etc.<br><br> We've spent an enormous amount of time creating and checking these footprints and parts, but it is the end user's responsibility to ensure correctness and suitablity for a given componet or application. If you enjoy using this library, please buy one of our products at www.sparkfun.com. <br><br> <b>Licensing:</b> Creative Commons ShareAlike 4.0 International - https://creativecommons.org/licenses/by-sa/4.0/ <br><br> You are welcome to use this library for commercial purposes. For attribution, we ask that when you begin to sell your device using our footprint, you email us with a link to the product being sold. We want bragging rights that we helped (in a very small part) to create your 8th world wonder. We would like the opportunity to feature your device on our homepage. 1/4W Resistor, 0.4" wide<p> Yageo CFR series <a href="http://www.yageo.com/pdf/yageo/Leaded-R_CFR_2008.pdf">http://www.yageo.com/pdf/yageo/Leaded-R_CFR_2008.pdf</a> >Name >Value <b>Pin Header Connectors</b><p> <author>Created by librarian@cadsoft.de</author> <b>PIN HEADER</b> >NAME >VALUE <h3>SparkFun Electronics' preferred foot prints</h3> In this library you'll find anything that moves- switches, relays, buttons, potentiometers. Also, anything that goes on a board but isn't electrical in nature- screws, standoffs, etc.<br><br> We've spent an enormous amount of time creating and checking these footprints and parts, but it is the end user's responsibility to ensure correctness and suitablity for a given componet or application. If you enjoy using this library, please buy one of our products at www.sparkfun.com. <br><br> <b>Licensing:</b> Creative Commons ShareAlike 4.0 International - https://creativecommons.org/licenses/by-sa/4.0/ <br><br> You are welcome to use this library for commercial purposes. For attribution, we ask that when you begin to sell your device using our footprint, you email us with a link to the product being sold. We want bragging rights that we helped (in a very small part) to create your 8th world wonder. We would like the opportunity to feature your device on our homepage. >NAME >VALUE <b>Jumpers</b><p> <author>Created by librarian@cadsoft.de</author> <b>Solder jumper</b> >NAME >VALUE <b>EAGLE Design Rules</b> <p> Die Standard-Design-Rules sind so gewählt, dass sie für die meisten Anwendungen passen. Sollte ihre Platine besondere Anforderungen haben, treffen Sie die erforderlichen Einstellungen hier und speichern die Design Rules unter einem neuen Namen ab. <b>EAGLE Design Rules</b> <p> The default Design Rules have been set to cover a wide range of applications. Your particular design may have different requirements, so please make the necessary adjustments and save your customized design rules under a new name. <b>NOA-Labs EAGLE Design Rules</b> ================================================ FILE: pcb/3.0/pcb.sch ================================================ <h3>SparkFun Electronics' preferred foot prints</h3> In this library you'll find non-functional items- supply symbols, logos, notations, frame blocks, etc.<br><br> We've spent an enormous amount of time creating and checking these footprints and parts, but it is the end user's responsibility to ensure correctness and suitablity for a given componet or application. If you enjoy using this library, please buy one of our products at www.sparkfun.com. <br><br> <b>Licensing:</b> Creative Commons ShareAlike 4.0 International - https://creativecommons.org/licenses/by-sa/4.0/ <br><br> You are welcome to use this library for commercial purposes. For attribution, we ask that when you begin to sell your device using our footprint, you email us with a link to the product being sold. We want bragging rights that we helped (in a very small part) to create your 8th world wonder. We would like the opportunity to feature your device on our homepage. >DRAWING_NAME >LAST_DATE_TIME >SHEET Sheet: >CNAME Rev: >DESIGNER >CREVISION <b>Schematic Frame-European Format</b> <br><br> Standard A4 size frame in Landscape <h3>SparkFun Electronics' preferred foot prints</h3> In this library you'll find connectors and sockets- basically anything that can be plugged into or onto.<br><br> We've spent an enormous amount of time creating and checking these footprints and parts, but it is the end user's responsibility to ensure correctness and suitablity for a given componet or application. If you enjoy using this library, please buy one of our products at www.sparkfun.com. <br><br> <b>Licensing:</b> Creative Commons ShareAlike 4.0 International - https://creativecommons.org/licenses/by-sa/4.0/ <br><br> You are welcome to use this library for commercial purposes. For attribution, we ask that when you begin to sell your device using our footprint, you email us with a link to the product being sold. We want bragging rights that we helped (in a very small part) to create your 8th world wonder. We would like the opportunity to feature your device on our homepage. >NAME >VALUE >NAME >VALUE >NAME >VALUE This footprint was designed to help hold the alignment of a through-hole component (i.e. 6-pin header) while soldering it into place. You may notice that each hole has been shifted either up or down by 0.005 of an inch from it's more standard position (which is a perfectly straight line). This slight alteration caused the pins (the squares in the middle) to touch the edges of the holes. Because they are alternating, it causes a "brace" to hold the component in place. 0.005 has proven to be the perfect amount of "off-center" position when using our standard breakaway headers. Although looks a little odd when you look at the bare footprint, once you have a header in there, the alteration is very hard to notice. Also, if you push a header all the way into place, it is covered up entirely on the bottom side. This idea of altering the position of holes to aid alignment will be further integrated into the Sparkfun Library for other footprints. It can help hold any component with 3 or more connection pins. >NAME >VALUE >NAME >VALUE >NAME >VALUE >NAME >VALUE >Name >Value + - S >NAME >VALUE >NAME >VALUE >Name >Value >NAME >VALUE >Value >Name <h3>SMD 3-Pin Male Right-Angle Header w/ Alignment posts</h3> Matches 4UCONN part # 11026<br> <a href="http://www.4uconnector.com/online/object/4udrawing/11026.pdf">http://www.4uconnector.com/online/object/4udrawing/11026.pdf</a> This 3-pin connector mates with the JST cable sold on SparkFun. >Name >Value + - S >NAME >VALUE >NAME >VALUE 2mm SMD side-entry connector. tDocu layer indicates the actual physical plastic housing. +/- indicate SparkFun standard batteries and wiring. >Name >Value + - >Name >Value >NAME >VALUE >NAME >VALUE This footprint was designed to help hold the alignment of a through-hole component (i.e. 6-pin header) while soldering it into place. You may notice that each hole has been shifted either up or down by 0.005 of an inch from it's more standard position (which is a perfectly straight line). This slight alteration caused the pins (the squares in the middle) to touch the edges of the holes. Because they are alternating, it causes a "brace" to hold the component in place. 0.005 has proven to be the perfect amount of "off-center" position when using our standard breakaway headers. Although looks a little odd when you look at the bare footprint, once you have a header in there, the alteration is very hard to notice. Also, if you push a header all the way into place, it is covered up entirely on the bottom side. This idea of altering the position of holes to aid alignment will be further integrated into the Sparkfun Library for other footprints. It can help hold any component with 3 or more connection pins. >NAME >VALUE >NAME >VALUE >NAME >VALUE >Name >Value + - >NAME >VALUE >Name >Value + - <H3>JST-2-PTH-KIT</h3> 2-Pin JST, through-hole connector<br> <br> <b>Warning:</b> This is the KIT version of this package. This package has a smaller diameter top stop mask, which doesn't cover the diameter of the pad. This means only the bottom side of the pads' copper will be exposed. You'll only be able to solder to the bottom side. >Name >Value + - >Name >Value + - >NAME >VALUE >NAME >VALUE >Value >Name >NAME >VALUE >NAME >VALUE >NAME >VALUE >NAME >VALUE <h3>Plated Through Hole - 2x10</h3> <p>Specifications: <ul><li>Pin count:20</li> <li>Pin pitch:0.1"</li> </ul></p> <p>Example device(s): <ul><li>CONN_10x2</li> </ul></p> >NAME >VALUE <h3>Plated Through Hole - 2x10 Locking Footprint</h3> Holes are offset 0.005", to hold pins in place while soldering. <p>Specifications: <ul><li>Pin count:20</li> <li>Pin pitch:0.1"</li> </ul></p> <p>Example device(s): <ul><li>CONN_10x2</li> </ul></p> >NAME >VALUE <h3>Plated Through Hole - 2x10 Locking Footprint Special</h3> Holes are offset 0.005", to hold pins in place while soldering. Pin rows are offset 5mm as well <p>Specifications: <ul><li>Pin count:20</li> <li>Pin pitch:0.1"</li> </ul></p> <p>Example device(s): <ul><li>CONN_10x2</li> </ul></p> >NAME >VALUE <h3>Plated Through Hole - 2x10 No Silk Outline</h3> <p>Specifications: <ul><li>Pin count:20</li> <li>Pin pitch:0.1"</li> </ul></p> <p>Example device(s): <ul><li>CONN_10x2</li> </ul></p> >NAME >VALUE >VALUE >NAME >VALUE >NAME >VALUE >NAME <h3> 20 Pin Connection</h3> 10x2 pin layout >VALUE >NAME <b>Header 3</b> Standard 3-pin 0.1" header. Use with straight break away headers (SKU : PRT-00116), right angle break away headers (PRT-00553), swiss pins (PRT-00743), machine pins (PRT-00117), and female headers (PRT-00115). Molex polarized connector foot print use with SKU : PRT-08232 with associated crimp pins and housings. Standard 2-pin 0.1" header. Use with <br> - straight break away headers ( PRT-00116)<br> - right angle break away headers (PRT-00553)<br> - swiss pins (PRT-00743)<br> - machine pins (PRT-00117)<br> - female headers (PRT-00115)<br><br> Molex polarized connector foot print use with: PRT-08233 with associated crimp pins and housings.<br><br> 2.54_SCREWTERM for use with PRT-10571.<br><br> 3.5mm Screw Terminal footprints for PRT-08084<br><br> 5mm Screw Terminal footprints for use with PRT-08432 <b>Header 1</b> Standard 1-pin 0.1" header. Use with straight break away headers (SKU : PRT-00116), right angle break away headers (PRT-00553), swiss pins (PRT-00743), machine pins (PRT-00117), and female headers (PRT-00115). <h3>Multi connection point. Often used as Generic Header-pin footprint for 0.1 inch spaced/style header connections</h3> <p></p> <b>On any of the 0.1 inch spaced packages, you can populate with these:</b> <ul> <li><a href="https://www.sparkfun.com/products/116"> Break Away Headers - Straight</a> (PRT-00116)</li> <li><a href="https://www.sparkfun.com/products/553"> Break Away Male Headers - Right Angle</a> (PRT-00553)</li> <li><a href="https://www.sparkfun.com/products/115"> Female Headers</a> (PRT-00115)</li> <li><a href="https://www.sparkfun.com/products/117"> Break Away Headers - Machine Pin</a> (PRT-00117)</li> <li><a href="https://www.sparkfun.com/products/743"> Break Away Female Headers - Swiss Machine Pin</a> (PRT-00743)</li> </ul> <p></p> <b>Special note about the "SPECIAL" package:</b> <p></p> This was SPECIALLY designed to be used with our Graphic LCD Backpack. Be sure you want to use this! It is not only staggered on each line of header holes, but IT IS ALSO offset of the center point of the top and bottom lines by 5 mil. This causes the headers to lock into place on the "standard" footprint on the LCD screen. The extra squares on the tdocu layer are there simply to reference other pins (if you were to actually populate a longer header than ten long - this is what we do with the backpacks). <h3>SparkFun Electronics' preferred foot prints</h3> In this library you'll find resistors, capacitors, inductors, test points, jumper pads, etc.<br><br> We've spent an enormous amount of time creating and checking these footprints and parts, but it is the end user's responsibility to ensure correctness and suitablity for a given componet or application. If you enjoy using this library, please buy one of our products at www.sparkfun.com. <br><br> <b>Licensing:</b> Creative Commons ShareAlike 4.0 International - https://creativecommons.org/licenses/by-sa/4.0/ <br><br> You are welcome to use this library for commercial purposes. For attribution, we ask that when you begin to sell your device using our footprint, you email us with a link to the product being sold. We want bragging rights that we helped (in a very small part) to create your 8th world wonder. We would like the opportunity to feature your device on our homepage. >NAME >VALUE >Name >Value <b>RESISTOR</b><p> chip >NAME >VALUE >NAME >VALUE >NAME >VALUE 1/6W Thru-hole Resistor - *UNPROVEN* >NAME >VALUE >NAME >VALUE 1/4W Resistor, 0.4" wide<p> Yageo CFR series <a href="http://www.yageo.com/pdf/yageo/Leaded-R_CFR_2008.pdf">http://www.yageo.com/pdf/yageo/Leaded-R_CFR_2008.pdf</a> >Name >Value 1/2W Resistor, 0.5" wide<p> Yageo CFR series <a href="http://www.yageo.com/pdf/yageo/Leaded-R_CFR_2008.pdf">http://www.yageo.com/pdf/yageo/Leaded-R_CFR_2008.pdf</a> >Name >Value 1W Resistor, 0.6" wide<p> Yageo CFR series <a href="http://www.yageo.com/pdf/yageo/Leaded-R_CFR_2008.pdf">http://www.yageo.com/pdf/yageo/Leaded-R_CFR_2008.pdf</a> >Name >Value 2W Resistor, 0.8" wide<p> Yageo CFR series <a href="http://www.yageo.com/pdf/yageo/Leaded-R_CFR_2008.pdf">http://www.yageo.com/pdf/yageo/Leaded-R_CFR_2008.pdf</a> >Name >Value <h3>AXIAL-0.3-KIT</h3> Commonly used for 1/4W through-hole resistors. 0.3" pitch between holes.<br> <br> <b>Warning:</b> This is the KIT version of the AXIAL-0.3 package. This package has a smaller diameter top stop mask, which doesn't cover the diameter of the pad. This means only the bottom side of the pads' copper will be exposed. You'll only be able to solder to the bottom side. >Name >Value This is the "EZ" version of the standard .3" spaced resistor package.<br> It has a reduced top mask to make it harder to install upside-down. >Name >Value >Name >Value >Name >Value >NAME >VALUE <b>Resistor</b> Basic schematic elements and footprints for 0603, 1206, and PTH resistors. <b>Pin Header Connectors</b><p> <author>Created by librarian@cadsoft.de</author> <b>PIN HEADER</b> >NAME >VALUE <b>PIN HEADER</b> >NAME >VALUE >NAME >VALUE <b>PIN HEADER</b> <h3>SparkFun Electronics' preferred foot prints</h3> In this library you'll find anything that moves- switches, relays, buttons, potentiometers. Also, anything that goes on a board but isn't electrical in nature- screws, standoffs, etc.<br><br> We've spent an enormous amount of time creating and checking these footprints and parts, but it is the end user's responsibility to ensure correctness and suitablity for a given componet or application. If you enjoy using this library, please buy one of our products at www.sparkfun.com. <br><br> <b>Licensing:</b> Creative Commons ShareAlike 4.0 International - https://creativecommons.org/licenses/by-sa/4.0/ <br><br> You are welcome to use this library for commercial purposes. For attribution, we ask that when you begin to sell your device using our footprint, you email us with a link to the product being sold. We want bragging rights that we helped (in a very small part) to create your 8th world wonder. We would like the opportunity to feature your device on our homepage. + - <B>BATTERY</B><p> 22 mm >NAME >VALUE + - >NAME >VALUE >NAME >VALUE >NAME >VALUE >NAME >VALUE + - >NAME >VALUE >NAME >VALUE (layout parts on top layer) >NAME >VALUE >NAME >VALUE <h3>BATTERY-AA-KIT</h3> <b>Warning:</b> This is the KIT version of this package. This package has a smaller diameter top stop mask, which doesn't cover the diameter of the pad. This means only the bottom side of the pads' copper will be exposed. You'll only be able to solder to the bottom side. >Name >Value >NAME >VALUE Works with 18650 clips PRT-13113. + - >Name >Value >NAME >VALUE <b>Battery Holders</b><br> Various common sizes : AA, AAA, 18650 (PRT-12895), 20mm coin cell and 12mm coin cell.<br> 20MM_4LEGS, BATT-10373 <b>Jumpers</b><p> <author>Created by librarian@cadsoft.de</author> <b>Solder jumper</b> >NAME >VALUE <b>Solder jumper</b> >NAME >VALUE >NAME >VALUE SMD solder <b>JUMPER</b> TEENSY 4.1 CHARGER DISPLAY TEENSY 4.1 LEPTON BUTTON RTC-BAT SWITCH LIPO-MEASURE BOOSTER GND 5V BAT GND I2C PULLUPS ================================================ FILE: pcb/README.MD ================================================ # DIY-Thermocam - PCB ## General The printed circuit board has been created with [Autosoft Eagle](http://www.autodesk.com/education/free-software/eagle). Use this software to open the [schematic](3.0/pcb.sch) or [board](3.0/pcb.brd) file. You can also use the [Gerber.zip](3.0/gerber.zip) and upload it to any PCB service of your choice to manufacture the board, for example [Smart Prototyping](http://www.smart-prototyping.com/PCB-Prototyping.html). The PCB dimensions are 89.4mm (w) x 68.4mm (h), 1.6mm thickness and 2 layers. ## Front ![Front](3.0/front.png) ## Back ![FRONT](3.0/back.png) ================================================ FILE: software/README.MD ================================================ # DIY-Thermocam - Software This folder includes all software currently available for the project: - **Thermal Analysis Software**: Advanced desktop PC software with many analysis tools available, only runs under Windows - **Thermal Data Viewer**: Simple tool to open and edit the raw data files - **Thermal Live Viewer**: Basic python implementation for live streaming of thermal and visual images over the USB connection - **Video Converter**: Can convert a series of raw images to movies Apart from that, haralds has provided a **set of tools** for post processing raw images **[here](https://github.com/haraldg/thermocam-tools)**. ================================================ FILE: software/thermal_analysis_software/README.MD ================================================ # DIY-Thermocam - Thermal Analysis Software The thermal analysis software called ThermoVision is created by Joe-C. The latest stable version that works well with the DIY-Thermocam is 1.7.0.0. Use the Sample.DAT file in this folder to try out the functionalities of the software. Just drag it into the Main IR window to load it. For more information and the changelog, check out the [authors website](https://translate.google.com/translate?sl=de&tl=en&js=y&prev=_t&hl=de&ie=UTF-8&u=http%3A%2F%2Fjoe-c.de%2Fpages%2Fprojekte%2Fthermovision.php&edit-text=). ================================================ FILE: software/thermal_data_viewer/README.MD ================================================ # DIY-Thermocam - Thermal Data Viewer The thermal data viewer is created by Laszlo Lovass: leslovass@gmail.com. ================================================ FILE: software/thermal_live_viewer/3.0/.gitignore ================================================ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging dist/ build/ *.egg-info/ # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # IDEs / Editors .idea/ .vscode/ *.sublime-project *.sublime-workspace # Database *.sqlite3 *.db # OS generated files .DS_Store Thumbs.db # Logs and temp files *.log *.swp *.tmp thermal videos ================================================ FILE: software/thermal_live_viewer/3.0/main.py ================================================ from typing import Optional import pygame from src.liveviewer import LiveViewer import serial.tools.list_ports as ports def create_port_menu() -> Optional[str]: """ Create a Pygame-based menu for serial port selection. Returns: Optional[str]: Selected port device path or None if selection was cancelled """ pygame.init() # Calculate required window size based on content com_ports = list(ports.comports()) max_port_length = max(len(str(port.device)) for port in com_ports) if com_ports else 20 window_width = max(600, max_port_length * 15) # Ensure minimum width of 600px window_height = 100 + len(com_ports) * 40 # Height based on number of ports screen = pygame.display.set_mode((window_width, window_height)) pygame.display.set_caption("Serial Port Selection") # Setup fonts and colors font = pygame.font.Font(None, 36) text_color = (220, 220, 220) highlight_color = (100, 150, 255) bg_color = (30, 30, 30) selected_bg_color = (50, 50, 50) if not com_ports: pygame.quit() raise RuntimeError("No serial ports found!") selected_index = 0 clock = pygame.time.Clock() while True: screen.fill(bg_color) # Draw title with more standard arrow notation title = font.render("Select Serial Port (Use Up/Down, Enter to select)", True, text_color) screen.blit(title, (20, 20)) # Draw port options for idx, port in enumerate(com_ports): # Draw selection background if idx == selected_index: pygame.draw.rect(screen, selected_bg_color, (15, 65 + idx * 40, window_width - 30, 35)) # Draw port information port_text = f"{port.device} - {port.description}" color = highlight_color if idx == selected_index else text_color text = font.render(port_text, True, color) screen.blit(text, (20, 70 + idx * 40)) pygame.display.flip() clock.tick(60) # Limit to 60 FPS for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.display.quit() return None if event.type == pygame.KEYDOWN: if event.key == pygame.K_UP: selected_index = (selected_index - 1) % len(com_ports) elif event.key == pygame.K_DOWN: selected_index = (selected_index + 1) % len(com_ports) elif event.key == pygame.K_RETURN: selected_port = com_ports[selected_index].device pygame.display.quit() return selected_port if __name__ == "__main__": selected_port = create_port_menu() if selected_port: LiveViewer(selected_port).run() ================================================ FILE: software/thermal_live_viewer/3.0/requirements.txt ================================================ pyserial numpy pygame pygbutton scipy scikit-image opencv-python ================================================ FILE: software/thermal_live_viewer/3.0/setup.py ================================================ from setuptools import setup, find_packages with open("requirements.txt") as f: requirements = f.read().splitlines() setup( name="Thermal Live Viewer", version="3.0", packages=find_packages(), install_requires=requirements, ) ================================================ FILE: software/thermal_live_viewer/3.0/src/__init__.py ================================================ ================================================ FILE: software/thermal_live_viewer/3.0/src/colorschemes.py ================================================ class ColorSchemes: def __init__(self): # Arctic - 240 elements self.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] # Black-Hot - 224 elements self.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] # Blue-Red - 192 elements self.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] # Coldest - 224 elements self.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] # Contrast - 224 elements self.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] # Double-Rainbow - 256 elements self.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] # Gray-Red - 224 elements self.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] # Glowbow - 224 elements self.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] # Grayscale - 256 elements self.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] # Hottest - 224 elements self.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] # Ironblack - 256 elements self.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] # Lava - 240 elements self.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] # Medical - 224 elements self.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] # Rainbow - 256 elements self.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] # Wheel 1 - 256 elements self.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] # Wheel 2 - 256 elements self.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] # Wheel 3 - 256 elements self.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] # White-Hot - 224 elements self.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] # Yellow - 224 elements self.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] ================================================ FILE: software/thermal_live_viewer/3.0/src/liveviewer.py ================================================ import serial import numpy import pygame import pygbutton import os import scipy import time import scipy.ndimage import sys import shutil import struct import platform import io from skimage.transform import resize from src.colorschemes import ColorSchemes import cv2 class LiveViewer: def __init__(self, serial_port): self.colorschemes = ColorSchemes() self.serial_port = serial_port # Start & Stop command self.cmd_start = 100 self.cmd_end = 200 # Serial terminal commands self.cmd_get_rawlimits = 110 self.cmd_get_rawdata = 111 self.cmd_get_configdata = 112 self.cmd_get_calstatus = 113 self.cmd_get_calibdata = 114 self.cmd_get_spottemp = 115 self.cmd_set_time = 116 self.cmd_get_temppoints = 117 self.cmd_set_laser = 118 self.cmd_get_laser = 119 self.cmd_set_shutterrun = 120 self.cmd_set_shuttermode = 121 self.cmd_set_filtertype = 122 self.cmd_get_shuttermode = 123 self.cmd_get_batterystatus = 124 self.cmd_set_calslope = 125 self.cmd_set_caloffset = 126 self.cmd_get_diagnostic = 127 self.cmd_get_visualimg = 128 self.cmd_get_fwversion = 129 self.cmd_set_limits = 130 self.cmd_set_textcolor = 131 self.cmd_set_colorscheme = 132 self.cmd_set_tempformat = 133 self.cmd_set_showspot = 134 self.cmd_set_showcolorbar = 135 self.cmd_set_showminmax = 136 self.cmd_set_temppoints = 137 self.cmd_get_hwversion = 138 self.cmd_set_rotation = 139 self.cmd_set_calibration = 140 self.cmd_get_hqresolution = 141 # Serial frame commands self.cmd_rawframe = 150 self.cmd_colorframe = 151 self.cmd_displayframe = 152 # Types of frame responses self.frame_capture_thermal = 180 self.frame_capture_visual = 181 self.frame_capture_video = 182 self.frame_normal = 183 # Config variables self.leptonVersion = int self.hardwareVersion = int self.fwVersion = int self.calStatus = 1 self.minData = int self.maxData = int self.spotTemp = float self.colorScheme = int self.tempFormat = bool self.spotEnabled = bool self.barEnabled = bool self.minMaxEnabled = int self.rotateScreen = False self.textColor = int self.filterType = int self.calOffset = float self.calSlope = float self.adjustLimits = bool self.hqRes = int # Data storage self.tempPoints = numpy.zeros((96, 2), dtype=int) self.leptonData = None self.leptonValues = None self.imageRaw = numpy.zeros((320, 240), dtype=int) self.image = numpy.zeros((320, 240, 3), dtype=int) self.thermalImg = None # Color Scheme self.colorMap = self.colorschemes.colorMap_rainbow self.colorElements = int self.colorSchemeTotal = 19 # Text color self.colorWhite = (255, 255, 255) self.colorBlack = (0, 0, 0) self.colorRed = (255, 0, 0) self.colorGreen = (0, 255, 0) self.colorBlue = (0, 0, 255) # Buttons self.buttonSaveThermal = None self.buttonSaveVideo = None self.buttonRenderMode = None self.buttonSpot = None self.buttonBar = None self.buttonHotCold = None self.buttonTextColor = None self.buttonFilter = None self.buttonColor = None self.buttonLimits = None self.buttonFormat = None self.buttonShutter = None self.allButtons = None # Global Variables self.screen = None self.ser = None self.calTimer = None self.calTimeLeft = None self.renderMode = True self.recordVideo = False self.videoFolder = None self.videoCounter = 0 self.videoStart = None def resource_path(self, relative_path): """Get absolute path to resource, works for dev and for PyInstaller""" base_path = os.path.abspath("./res") return os.path.join(base_path, relative_path) # Set the color scheme def setColorScheme(self, updateValue): # Update value if updateValue: # Pick new color scheme if self.colorScheme < (self.colorSchemeTotal - 1): self.colorScheme += 1 else: self.colorScheme = 0 # Send new value to the device try: # Send command and new value sendArray = (chr(self.cmd_set_colorscheme), chr(self.colorScheme)) self.ser.write(b"".join([x.encode() for x in sendArray])) # Get ACK inData = list(map(lambda x: ord(chr(x)), self.ser.read(2))) # Error except serial.serialutil.SerialException: return False # Timeout if not inData[1]: return False # Check for ACK if inData[1] != self.cmd_set_colorscheme: return False # Arctic if self.colorScheme == 0: self.colorMap = self.colorschemes.colorMap_arctic self.colorElements = 240 self.buttonColor.caption = "Color: Arctic" # Black-Hot elif self.colorScheme == 1: self.colorMap = self.colorschemes.colorMap_blackHot self.colorElements = 224 self.buttonColor.caption = "Color: Back-Hot" # Blue-Red elif self.colorScheme == 2: self.colorMap = self.colorschemes.colorMap_blueRed self.colorElements = 192 self.buttonColor.caption = "Color: Blue-Red" # Coldest elif self.colorScheme == 3: self.colorMap = self.colorschemes.colorMap_coldest self.colorElements = 224 self.buttonColor.caption = "Color: Coldest" # Contrast elif self.colorScheme == 4: self.colorMap = self.colorschemes.colorMap_contrast self.colorElements = 224 self.buttonColor.caption = "Color: Contrast" # Double-Rainbow elif self.colorScheme == 5: self.colorMap = self.colorschemes.colorMap_doubleRainbow self.colorElements = 256 self.buttonColor.caption = "Color: DoubleRain" # Gray-Red elif self.colorScheme == 6: self.colorMap = self.colorschemes.colorMap_grayRed self.colorElements = 224 self.buttonColor.caption = "Color: Gray-Red" # Glowbow elif self.colorScheme == 7: self.colorMap = self.colorschemes.colorMap_glowBow self.colorElements = 224 self.buttonColor.caption = "Color: Glowbow" # Grayscale elif self.colorScheme == 8: self.colorMap = self.colorschemes.colorMap_grayscale self.colorElements = 256 self.buttonColor.caption = "Color: Grayscale" # Hottest elif self.colorScheme == 9: self.colorMap = self.colorschemes.colorMap_hottest self.colorElements = 224 self.buttonColor.caption = "Color: Hottest" # Ironblack elif self.colorScheme == 10: self.colorMap = self.colorschemes.colorMap_ironblack self.colorElements = 256 self.buttonColor.caption = "Color: Ironblack" # Lava elif self.colorScheme == 11: self.colorMap = self.colorschemes.colorMap_lava self.colorElements = 240 self.buttonColor.caption = "Color: Lava" # Medical elif self.colorScheme == 12: self.colorMap = self.colorschemes.colorMap_medical self.colorElements = 224 self.buttonColor.caption = "Color: Medical" # Rainbow elif self.colorScheme == 13: self.colorMap = self.colorschemes.colorMap_rainbow self.colorElements = 256 self.buttonColor.caption = "Color: Rainbow" # Wheel 1 elif self.colorScheme == 14: self.colorMap = self.colorschemes.colorMap_wheel1 self.colorElements = 256 self.buttonColor.caption = "Color: Wheel 1" # Wheel 2 elif self.colorScheme == 15: self.colorMap = self.colorschemes.colorMap_wheel2 self.colorElements = 256 self.buttonColor.caption = "Color: Wheel 2" # Wheel 3 elif self.colorScheme == 16: self.colorMap = self.colorschemes.colorMap_wheel3 self.colorElements = 256 self.buttonColor.caption = "Color: Wheel 3" # White-Hot elif self.colorScheme == 17: self.colorMap = self.colorschemes.colorMap_whiteHot self.colorElements = 224 self.buttonColor.caption = "Color: White-Hot" # Yellow elif self.colorScheme == 18: self.colorMap = self.colorschemes.colorMap_yellow self.colorElements = 224 self.buttonColor.caption = "Color: Yellow" # Update UI self.buttonColor.draw(self.screen) # Everything worked return True # Set spot info def setSpot(self, updateValue): # Update value if updateValue: # Toggle value self.spotEnabled = not self.spotEnabled # Send new value to the device try: # Send command and new value sendArray = (chr(self.cmd_set_showspot), chr(self.spotEnabled)) self.ser.write(b"".join([x.encode() for x in sendArray])) # Get ACK inData = list(map(lambda x: ord(chr(x)), self.ser.read(2))) # Error except serial.serialutil.SerialException: return False # Timeout if not inData[1]: return False # Check for ACK if inData[1] != self.cmd_set_showspot: return False # Update UI if self.spotEnabled: self.buttonSpot.caption = "Spot: On" else: self.buttonSpot.caption = "Spot: Off" self.buttonSpot.draw(self.screen) # Everything worked return True # Set bar info def setBar(self, updateValue): # Update value if updateValue: # Toggle value self.barEnabled = not self.barEnabled # Send new value to the device try: # Send command and new value sendArray = (chr(self.cmd_set_showcolorbar), chr(self.barEnabled)) self.ser.write(b"".join([x.encode() for x in sendArray])) # Get ACK inData = list(map(lambda x: ord(chr(x)), self.ser.read(2))) # Error except serial.serialutil.SerialException: return False # Timeout if not inData[1]: return False # Check for ACK if inData[1] != self.cmd_set_showcolorbar: return False # Update UI if self.barEnabled: self.buttonBar.caption = "Bar: On" else: self.buttonBar.caption = "Bar: Off" self.buttonBar.draw(self.screen) # Everything worked return True # Set min / max def setMinMax(self, updateValue): # Update value if updateValue: # Pick new calue if self.minMaxEnabled < 3: self.minMaxEnabled += 1 else: self.minMaxEnabled = 0 # Send new value to the device try: # Send command and new value sendArray = (chr(self.cmd_set_showminmax), chr(self.minMaxEnabled)) self.ser.write(b"".join([x.encode() for x in sendArray])) # Get ACK inData = list(map(lambda x: ord(chr(x)), self.ser.read(2))) # Error except serial.serialutil.SerialException: return False # Timeout if not inData[1]: return False # Check for ACK if inData[1] != self.cmd_set_showminmax: return False # Update UI if self.minMaxEnabled == 0: self.buttonHotCold.caption = "MinMax: Off" elif self.minMaxEnabled == 1: self.buttonHotCold.caption = "MinMax: Cold" elif self.minMaxEnabled == 2: self.buttonHotCold.caption = "MinMax: Hot" else: self.buttonHotCold.caption = "MinMax: Both" self.buttonHotCold.draw(self.screen) # Everything worked return True # Set text color def setTextColor(self, updateValue): # Update value if updateValue: # Pick new calue if self.textColor < 4: self.textColor += 1 else: self.textColor = 0 # Send new value to the device try: # Send command and new value sendArray = (chr(self.cmd_set_textcolor), chr(self.textColor)) self.ser.write(b"".join([x.encode() for x in sendArray])) # Get ACK inData = list(map(lambda x: ord(chr(x)), self.ser.read(2))) # Error except serial.serialutil.SerialException: return False # Timeout if not inData[1]: return False # Check for ACK if inData[1] != self.cmd_set_textcolor: return False # Update UI if self.textColor == 0: self.buttonTextColor.caption = "Text: White" elif self.textColor == 1: self.buttonTextColor.caption = "Text: Black" elif self.textColor == 2: self.buttonTextColor.caption = "Text: Red" elif self.textColor == 3: self.buttonTextColor.caption = "Text: Green" else: self.buttonTextColor.caption = "Text: Blue" self.buttonTextColor.draw(self.screen) # Everything worked return True # Set filter type def setFilterType(self, updateValue): # Update value if updateValue: # Pick new calue if self.filterType < 2: self.filterType += 1 else: self.filterType = 0 # Send new value to the device try: # Send command and new value sendArray = (chr(self.cmd_set_filtertype), chr(self.filterType)) self.ser.write(b"".join([x.encode() for x in sendArray])) # Get ACK inData = list(map(lambda x: ord(chr(x)), self.ser.read(2))) # Error except serial.serialutil.SerialException: return False # Timeout if not inData[0]: return False # Check for ACK if inData[0] != self.cmd_set_filtertype: return False # Update UI if self.filterType == 0: self.buttonFilter.caption = "Filter: None" elif self.filterType == 1: self.buttonFilter.caption = "Filter: Gaussian" else: self.buttonFilter.caption = "Filter: Box" self.buttonFilter.draw(self.screen) # Everything worked return True # Set limits def setLimits(self, updateValue): # Update value if updateValue: # Toggle value self.adjustLimits = not self.adjustLimits # Send new value to the device try: # Send command and new value sendArray = (chr(self.cmd_set_limits), chr(self.adjustLimits)) self.ser.write(b"".join([x.encode() for x in sendArray])) # Get ACK inData = list(map(lambda x: ord(chr(x)), self.ser.read(2))) # Error except serial.serialutil.SerialException: return False # Timeout if not inData[1]: return False # Check for ACK if inData[1] != self.cmd_set_limits: return False # Update UI if self.adjustLimits: self.buttonLimits.caption = "Limits: Auto" else: self.buttonLimits.caption = "Limits: Locked" self.buttonLimits.draw(self.screen) # Everything worked return True # Set format def setFormat(self, updateValue): # Update value if updateValue: # Toggle value self.tempFormat = not self.tempFormat # Send new value to the device try: # Send command and new value sendArray = (chr(self.cmd_set_tempformat), chr(self.tempFormat)) self.ser.write(b"".join([x.encode() for x in sendArray])) # Get ACK inData = list(map(lambda x: ord(chr(x)), self.ser.read(2))) # Error except serial.serialutil.SerialException: return False # Timeout if not inData[1]: return False # Check for ACK if inData[1] != self.cmd_set_tempformat: return False # Update UI if self.tempFormat: self.buttonFormat.caption = "Format: " "\N{DEGREE SIGN}" + "F" else: self.buttonFormat.caption = "Format: " "\N{DEGREE SIGN}" + "C" self.buttonFormat.draw(self.screen) # Everything worked return True # Set rotation def setRotation(self, updateValue): # Update value if updateValue: # Toggle value self.rotateScreen = not self.rotateScreen # Send new value to the device try: # Send command and new value sendArray = (chr(self.cmd_set_rotation), chr(self.rotateScreen)) self.ser.write(b"".join([x.encode() for x in sendArray])) # Get ACK inData = list(map(lambda x: ord(chr(x)), self.ser.read(2))) # Error except serial.serialutil.SerialException: return False # Timeout if not inData[1]: return False # Check for ACK if inData[1] != self.cmd_set_rotation: return False # Everything worked return True # Run the shutter def runShutter(self): # Show message self.displayText("Trigger Shutter..", False) # Send new value to the device try: # Send command self.ser.write(chr(self.cmd_set_shutterrun).encode()) # Get ACK inData = list(map(lambda x: ord(chr(x)), self.ser.read(2))) # Error except serial.serialutil.SerialException: return False # Timeout if not inData[0]: return False # Check for ACK if inData[0] != self.cmd_set_shutterrun: return False # Wait two second time.sleep(2) # Everything worked return True # Run the calibration def runCalibration(self): # Show message self.displayText("Start Calibration..", False) # Send new value to the device try: # Send command self.ser.write(chr(self.cmd_set_calibration).encode()) # Get ACK inData = list(map(lambda x: ord(chr(x)), self.ser.read(2))) # Error except serial.serialutil.SerialException: return False # Timeout if not inData[1]: return False # Wait until ACK received while inData[1] != self.cmd_set_calibration: # If not ACK, check if status valid if inData[1] > 100: return False # Display current status message = "Calibration Status: " + str(inData) + "%" self.displayText(message, False) # Receive the next status or ACK try: # Get data inData = list(map(lambda x: ord(chr(x)), self.ser.read(2))) # Error except serial.serialutil.SerialException: return False # Timeout if not inData[1]: return False # Show finish message self.displayText("Calibration completed!", True) # Get the new offset and slope try: # Send command self.ser.write(chr(self.cmd_get_calibdata).encode()) # Get data inData = list(map(lambda x: ord(chr(x)), self.ser.read(8))) # Error except serial.serialutil.SerialException: return False # Timeout if not inData: return False # Return if size does not match if len(inData) != 8: return False # Read calibration offset self.calOffset = [inData, inData[1], inData[2], inData[3]] self.calOffset = "".join(chr(i) for i in self.calOffset) self.calOffset = float(struct.unpack("f", self.calOffset)[0]) # Read calibration slope self.calSlope = [inData[4], inData[5], inData[6], inData[7]] self.calSlope = "".join(chr(i) for i in self.calSlope) self.calSlope = float(struct.unpack("f", self.calSlope)[0]) # Show new slope and offset message = ( "Slope: " + str(round(self.calSlope, 4)) + ", Offset: " + str(round(self.calOffset, 4)) ) self.displayText(message, True) time.sleep(2) # Everything worked return True # Draw the buttons on the screen def drawButtons(self): # First row self.buttonRenderMode = pygbutton.PygButton( (11, 491, 147, 27), "Mode: Rendering" ) self.buttonSaveThermal = pygbutton.PygButton((168, 491, 147, 27), "Save Image") self.buttonSaveVideo = pygbutton.PygButton((325, 491, 147, 27), "Record Video") self.buttonShutter = pygbutton.PygButton((482, 491, 147, 27), "Trigger Shutter") # Second row self.buttonSpot = pygbutton.PygButton((11, 528, 147, 27), "Spot: On") self.buttonBar = pygbutton.PygButton((168, 528, 147, 27), "Bar: On") self.buttonHotCold = pygbutton.PygButton((325, 528, 147, 27), "MinMax: Hot") self.buttonTextColor = pygbutton.PygButton((482, 528, 147, 27), "Text: Black") # Third row self.buttonFilter = pygbutton.PygButton((11, 565, 147, 27), "Filter: Gaussian") self.buttonColor = pygbutton.PygButton((168, 565, 147, 27), "Color: Rainbow") self.buttonLimits = pygbutton.PygButton((325, 565, 147, 27), "Limits: Auto") self.buttonFormat = pygbutton.PygButton( (482, 565, 147, 27), "Format: " "\N{DEGREE SIGN}" + "F" ) # Group them all together for refresh self.allButtons = ( self.buttonSaveThermal, self.buttonSaveVideo, self.buttonRenderMode, self.buttonSpot, self.buttonBar, self.buttonHotCold, self.buttonTextColor, self.buttonFilter, self.buttonColor, self.buttonLimits, self.buttonFormat, self.buttonShutter, ) # Draw the buttons for b in self.allButtons: b.draw(self.screen) # Update screen pygame.display.flip() # Apply filter to the raw data def applyFilter(self): # Repeat array four times for Lepton2 if self.leptonVersion == 0: array2d = self.leptonValues.reshape(60, 80) array2dBig = array2d.repeat(4, axis=0).repeat(4, axis=1) self.imageRaw = numpy.transpose(array2dBig) # Repeat array two times for Lepton3 else: array2d = self.leptonValues.reshape(120, 160) array2dBig = array2d.repeat(2, axis=0).repeat(2, axis=1) self.imageRaw = numpy.transpose(array2dBig) # Apply the gaussian blur filter if self.filterType == 1: self.imageRaw = scipy.ndimage.filters.gaussian_filter( self.imageRaw, 1.33, mode="nearest" ) # Apply box blur filter if self.filterType == 2: self.imageRaw = scipy.ndimage.filters.uniform_filter(self.imageRaw, 3) # Converts the Lepton raw values to RGB colors def convertColors(self): # Calculate the scale scale = (self.colorElements - 1.0) / (self.maxData - self.minData) # Check if values are out of color scheme bounds array_np = numpy.asarray(self.imageRaw) low_values_indices = array_np < self.minData array_np[low_values_indices] = self.minData high_values_indices = array_np > self.maxData array_np[high_values_indices] = self.maxData self.imageRaw = array_np # Calculate the lookup vale (0-255) self.imageRaw[0:, 0:] = (self.imageRaw[0:, 0:] - self.minData) * scale self.imageRaw[0:, 0:] = 3 * self.imageRaw[0:, 0:] self.image[0:, 0:, 0] = self.imageRaw[0:, 0:] self.image[0:, 0:, 1] = self.imageRaw[0:, 0:] + 1 self.image[0:, 0:, 2] = self.imageRaw[0:, 0:] + 2 self.image = numpy.take(self.colorMap, self.image) # Convert the Lepton 8 bit data to 16 bit data def convertLeptonData(self): # For Lepton2, convert 4800 values if self.leptonVersion == 0: leptonDataReshaped = numpy.array(self.leptonData).reshape(4800, 2) # For Lepton3, convert 19200 values else: leptonDataReshaped = numpy.array(self.leptonData).reshape(19200, 2) # Assign them to the lepton values array self.leptonValues[0:] = (leptonDataReshaped[0:, 0] * 256) + leptonDataReshaped[ 0:, 1 ] # Display some text on the screen def displayText(self, msg, wait): # Fill background background = pygame.Surface((640, 480)) background.fill((250, 250, 250)) # Display some text font = pygame.font.Font(self.resource_path("freesansbold.ttf"), 36) text = font.render(msg, 1, (10, 10, 10)) textpos = text.get_rect() textpos.centerx = background.get_rect().centerx textpos.centery = background.get_rect().centery background.blit(text, textpos) # Blit everything to the screen self.screen.blit(background, (0, 0)) pygame.display.flip() # Opt: Wait a second, so the user can read the text if wait: time.sleep(1) def SaveVideoFile(self,image_folder,output_video): # Get all image files in the folder (assuming they're named sequentially) images = [img for img in os.listdir(image_folder) if img.endswith(".jpg")] images.sort() # Ensure images are in the correct order (e.g., image001, image002, etc.) # Read the first image to get the size (all images should be the same size) first_image = cv2.imread(os.path.join(image_folder, images[0])) height, width, _ = first_image.shape # Define the codec and create VideoWriter object fourcc = cv2.VideoWriter_fourcc(*'mp4v') # for .mp4 format fps = 4 # Frames per second in the video # Create VideoWriter object to save the video video_writer = cv2.VideoWriter(output_video, fourcc, fps, (width, height)) # Loop through images and add them to the video for image in images: img_path = os.path.join(image_folder, image) img = cv2.imread(img_path) video_writer.write(img) # Release the video writer object video_writer.release() print(f"Video saved as {output_video}") # Starts or stops video recording def videoRecord(self): # Start recording a video if not self.recordVideo: # Show message self.displayText("Recording video..", True) # Update button self.buttonSaveVideo.caption = "Stop Recording" self.buttonSaveVideo.draw(self.screen) # Create dir if required if not os.path.exists("videos"): os.makedirs("videos") owd = os.getcwd() os.chdir("videos") self.videoFolder = time.strftime("%Y%m%d%H%M%S") os.mkdir(self.videoFolder) self.videoFolder = os.path.join("videos", self.videoFolder) os.chdir(owd) # Set record video to true and save time self.recordVideo = True self.videoStart = time.time() # Stop recording a video else: # Calculate the number of frames frames = (self.videoCounter * 1.0) / (time.time() - self.videoStart) # Show stop message self.displayText("Stop recording..", True) # Update button self.buttonSaveVideo.caption = "Record Video" self.buttonSaveVideo.draw(self.screen) # Show converting message self.displayText("Converting..", False) # Use ffmpeg to convert the frames to avi ffmpegCmd = self.resource_path( "ffmpeg" ) + " -framerate %d -i %s/%%06d.jpg -codec copy %s" % ( round(frames), self.videoFolder, self.videoFolder + ".avi", ) split_string = ffmpegCmd.split(' ') # Get the path for the images and the video directory video_save_folder = split_string[-1] image_folder_for_video = video_save_folder[:-4] self.SaveVideoFile(image_folder_for_video,video_save_folder) # Remove the folder with the single frames shutil.rmtree(self.videoFolder, True) # Clear marker self.videoFolder = None self.recordVideo = False self.videoCounter = 0 # Saves a thermal image as JPEG def saveThermal(self): # Check if thermal image exists and not recording video if (self.thermalImg is not None) and (self.recordVideo is False): # Create dir if required if not os.path.exists("thermal"): os.makedirs("thermal") # Save image to dir pygame.image.save( self.thermalImg, os.path.join( "thermal", time.strftime("%Y%m%d%H%M%S.jpg", time.gmtime()) ), ) # Show message self.displayText("Thermal image saved!", True) # Receive and save the visual image as JPEG def saveVisual(self): # Show message self.displayText("Transfer visual image..", False) # Set the timeout higher if self.hardwareVersion == 0: self.ser.timeout = 10.0 else: self.ser.timeout = 5.0 # Receive the visual image data length try: # Send visual img and high res command sendArray = (chr(self.cmd_get_visualimg), chr(1)) self.ser.write(b"".join([x.encode() for x in sendArray])) # Get visual image length inData = list(map(lambda x: ord(chr(x)), self.ser.read(2))) # Error except serial.serialutil.SerialException: return False # Timeout if not inData: return False # Return if size does not match if len(inData) != 2: return False # Calculate data length dataLen = inData * 256 + inData[1] # Try to read JPEG bytestream try: # Get data by length inData = list(map(lambda x: ord(chr(x)), self.ser.read(dataLen))) # Error except serial.serialutil.SerialException: return False # Set timeout back self.ser.timeout = 3.0 # Timeout if not inData: return False # Return if size does not match if len(inData) != dataLen: return False # Create dir if required if not os.path.exists("visual"): os.makedirs("visual") # Save the data to file byteArray = bytearray(inData) f = io.open( os.path.join("visual", time.strftime("%Y%m%d%H%M%S.jpg", time.gmtime())), "wb", ) f.write(byteArray) f.close() # Show message time.sleep(0.1) self.displayText("Visual image saved!", True) # Everything worked return True # Close the application and end connection def endConnection(self): # Show message self.displayText("Exiting..", True) # Send end command over serial port try: if self.ser: if self.ser.isOpen(): self.ser.write(chr(self.cmd_end).encode()) self.ser.close() # Discard error except serial.serialutil.SerialException: pass # Exit application pygame.quit() sys.exit() # Check if the user wants to exit def checkExit(self): # User wants to exit for event in pygame.event.get(): if event.type == pygame.locals.QUIT: self.endConnection() # Check if the warmup has been completed def checkWarmup(self): # If calibration status is warmup and longer than remaining time, switch to done if self.calStatus == 0 and (time.time() - self.calTimer) >= self.calTimeLeft: self.calStatus = 1 return True # Still in warmup elif self.calStatus == 0: return False # Calibration done return True # Add or remove a temperature point def tempFunc(self, mousePos, add): # If warmup is not completed, return if not self.checkWarmup(): return True # Check if we have some space left when adding a point pos = int if add: # Go through the array and find the next free pos for i in range(0, 96): if self.tempPoints[i][0] == 0: pos = i break # Maximum number reached, exit if pos == 96: return True # Get x and y position from mouse click xpos = mousePos[0] ypos = mousePos[1] # Check if inside the frame if xpos < 0 or xpos > 639 or ypos < 0 or ypos > 479: return True # Divide to match array size xpos /= 4 ypos /= 4 # Add point if add: # Index is between 1 and max self.tempPoints[pos][0] = xpos + (ypos * 160) + 1 # Temperature value will be refreshed in the next frame self.tempPoints[pos][1] = 0 # Remove point else: for x in range(xpos - 10, xpos + 10): for y in range(ypos - 10, ypos + 10): # Calculate index index = x + (y * 160) + 1 # If index is valid if 1 <= index <= 19200: # Go through the tempPoints for i in range(0, 96): # We found the right one if self.tempPoints[i][0] == index: # Set to invalid self.tempPoints[i][0] = 0 # Reset value self.tempPoints[i][1] = 0 # Update tempPoints on device try: # Build send array sendArray = (chr(self.cmd_set_temppoints),) for i in range(0, 96): # Index sendArray += (chr((self.tempPoints[i][0] & 0xFF00) >> 8),) sendArray += (chr(self.tempPoints[i][0] & 0x00FF),) # Value sendArray += (chr((self.tempPoints[i][1] & 0xFF00) >> 8),) sendArray += (chr(self.tempPoints[i][1] & 0x00FF),) # Send byte array self.ser.write(b"".join([x.encode() for x in sendArray])) # Wait for ACK inData = list(map(lambda x: ord(chr(x)), self.ser.read(2))) # Error except serial.serialutil.SerialException: return False # Timeout if not inData[1]: return False # Check if ACK matches if inData[1] != self.cmd_set_temppoints: return False # Everything OK return True # Function to calculate temperature out of Lepton raw value def calFunction(self, rawValue): # Calculate the temperature in Celcius out of the coefficients temp = (self.calSlope * rawValue) + self.calOffset # Convert to Fahrenheit if needed if self.tempFormat: temp = (9.0 / 5.0) * temp + 32.0 # Return absolute temperature return temp # Get the current text color def getTextColor(self): color = None if self.textColor == 0: color = self.colorWhite if self.textColor == 1: color = self.colorBlack if self.textColor == 2: color = self.colorRed if self.textColor == 3: color = self.colorGreen if self.textColor == 4: color = self.colorBlue return color # Show the spot temperature def showSpot(self): # Choose font font = pygame.font.Font(self.resource_path("freesansbold.ttf"), 26) # Get the color color = self.getTextColor() # Add circle pygame.draw.circle(self.thermalImg, color, (320, 240), 24, 2) # Add lines pygame.draw.line(self.thermalImg, color, (272, 240), (296, 240), 2) pygame.draw.line(self.thermalImg, color, (344, 240), (368, 240), 2) pygame.draw.line(self.thermalImg, color, (320, 192), (320, 216), 2) pygame.draw.line(self.thermalImg, color, (320, 264), (320, 288), 2) # Create the string tmpstr = "{:.2f}".format(self.spotTemp) text = font.render(tmpstr, 1, color) # Show it on the screen textpos = text.get_rect() textpos.centerx = self.thermalImg.get_rect().centerx textpos.centery = self.thermalImg.get_rect().centery + 65 self.thermalImg.blit(text, textpos) # Draw a temperature measurement on the screen def drawTemperature(self, xpos, ypos, rawValue, minMax): # Choose font font = pygame.font.Font(self.resource_path("freesansbold.ttf"), 26) # Get the color color = self.getTextColor() # Text text = None # When warmup is complete if self.checkWarmup(): # Calculate absolute temperature temp = self.calFunction(rawValue) # Create a string out of the temp tempstr = "{:.2f}".format(temp) text = font.render(tempstr, 1, color) # Show min and max as char or skip else: if minMax == 0: return if minMax == 1: text = font.render("C", 1, color) if minMax == 2: text = font.render("H", 1, color) # Draw marker pygame.draw.circle(self.thermalImg, color, (xpos, ypos), 2) # Calc x position for the temperature if self.checkWarmup(): xpos -= 35 if xpos > 569: xpos = 569 else: xpos -= 10 if xpos > 599: xpos = 599 if xpos < 0: xpos = 0 # Calc y position for the temperature ypos += 12 if ypos > 449: ypos = 449 # Put it into the image self.thermalImg.blit(text, (xpos, ypos)) # Show the min / max position def showMinMax(self): minValue = 65535 minTempPos = int maxValue = 0 maxTempPos = int # For Lepton2 if self.leptonVersion == 0: elements = 4800 # For Lepton3 else: elements = 19200 # Go through the lepton values for i in range(0, elements): # We found minTemp if self.leptonValues[i] < minValue: minValue = self.leptonValues[i] minTempPos = i # We found maxTemp if self.leptonValues[i] > maxValue: maxValue = self.leptonValues[i] maxTempPos = i # Show minimum position if self.minMaxEnabled & 1: # Get position if self.leptonVersion == 0: xpos = (minTempPos % 80) * 8 ypos = (minTempPos / 80) * 8 else: xpos = (minTempPos % 160) * 4 ypos = (minTempPos / 160) * 4 # Draw it on the screen self.drawTemperature(xpos, ypos, minValue, 1) # Show maximum position if self.minMaxEnabled & 2: # Get position if self.leptonVersion == 0: xpos = (maxTempPos % 80) * 8 ypos = (maxTempPos / 80) * 8 else: xpos = (maxTempPos % 160) * 4 ypos = (maxTempPos / 160) * 4 # Draw it on the screen self.drawTemperature(xpos, ypos, maxValue, 2) # Show the remaining warmup time def showWarmupTime(self): # Choose font font = pygame.font.Font(self.resource_path("freesansbold.ttf"), 26) # Get the color color = self.getTextColor() # Create text warmupLeft = self.calTimeLeft - (time.time() - self.calTimer) warmupStr = "{:.0f}".format(warmupLeft) text = font.render("Sensor warmup, " + warmupStr + "s left", 1, color) # Calculate position textpos = text.get_rect() textpos.centerx = self.thermalImg.get_rect().centerx textpos.centery = 440 # Insert into thermal image self.screen.blit(text, textpos) # Show the color bar def showColorbar(self): # Calculate height height = 480 - ((480 - self.colorElements) / 2) # Choose font font = pygame.font.Font(self.resource_path("freesansbold.ttf"), 26) # Display color bar for i in range(0, self.colorElements - 1): red = self.colorMap[i * 3] green = self.colorMap[(i * 3) + 1] blue = self.colorMap[(i * 3) + 2] pygame.draw.line( self.thermalImg, (red, green, blue), (590, height - i), (630, height - i), ) # Calculate min and max minTemp = self.calFunction(self.minData) maxTemp = self.calFunction(self.maxData) # Calculate step step = (maxTemp - minTemp) / 3.0 # Get the color color = self.getTextColor() # Draw min temp tempstr = "{:.0f}".format(minTemp) text = font.render(tempstr, 1, color) self.thermalImg.blit(text, (540, height - 5)) # Draw temperatures after min before max tempstr = "{:.0f}".format(minTemp + step) text = font.render(tempstr, 1, color) self.thermalImg.blit(text, (540, height - 5 - (self.colorElements / 3))) tempstr = "{:.0f}".format(minTemp + (2 * step)) text = font.render(tempstr, 1, color) self.thermalImg.blit(text, (540, height - 5 - (2 * (self.colorElements / 3)))) # Draw max temp tempstr = "{:.0f}".format(maxTemp) text = font.render(tempstr, 1, color) self.thermalImg.blit(text, (540, height - 5 - (3 * (self.colorElements / 3)))) # Show the selected temperature points on the screen def showTemperatures(self): # Go through the tempPoints array for i in range(0, 96): # Get index index = self.tempPoints[i][0] # Check if the tempPoint is active if index != 0: # Index value is between 1 and max index -= 1 # Calculate x and y position xpos = index % 160 ypos = index / 160 # Update raw value if self.leptonVersion == 0: self.tempPoints[i][1] = self.leptonValues[ (xpos / 2) + (ypos / 2) * 80 ] else: self.tempPoints[i][1] = self.leptonValues[xpos + (ypos * 160)] # Calculate screen position xpos *= 4 ypos *= 4 # Check if too close to border if ypos <= 20: ypos = 21 # Draw it on the screen self.drawTemperature(xpos, ypos, self.tempPoints[i][1], 0) # Display the thermal image on the screen def displayThermalImage(self): # Check if thermal image has been created if self.thermalImg is None: return # Show the spot temperature if enabled if self.spotEnabled: self.showSpot() # Show the colorbar if enabled and warmup completed if self.barEnabled and self.checkWarmup(): self.showColorbar() # Show the min / max pos if enabled if self.minMaxEnabled: self.showMinMax() # Show temperatures if warmup completed if self.checkWarmup(): self.showTemperatures() # Refresh screen self.screen.blit(self.thermalImg, (0, 0)) # Capture to file if video enabled if self.recordVideo: pygame.image.save( self.thermalImg, os.path.join( self.videoFolder, "{0:06d}".format(self.videoCounter) + ".jpg" ), ) self.videoCounter += 1 # Main method to create the thermal image def createThermalImage(self): # Convert 8 bit to 16 bit data self.convertLeptonData() # Apply gaussian blur self.applyFilter() # Convert to colors self.convertColors() # Resize image to 640x480 with nearest neighbour imageBig = resize( self.image, (640, 480), order=0, mode="constant", anti_aliasing=False ) # Convert to pygame surface and save as thermal image self.thermalImg = pygame.surfarray.make_surface(imageBig) # Extracts RGB values from the lepton raw data def extractRGB(self, buff): # Updated to use the new numpy 2.0 syntax for byte order conversion arr = numpy.frombuffer(buff, dtype=numpy.uint16) arr = arr.view(arr.dtype.newbyteorder('S')) r = (((arr & 0xF800) >> 11) * 255.0 / 31.0).astype(numpy.uint8) g = (((arr & 0x07E0) >> 5) * 255.0 / 63.0).astype(numpy.uint8) b = (((arr & 0x001F) >> 0) * 255.0 / 31.0).astype(numpy.uint8) arr = numpy.column_stack((r, g, b)) return arr # Get a stream frame from the display def getStream(self): # Calc length based on HW and HQRes if self.hardwareVersion == 0 or self.hqRes == 0: length = 38400 else: length = 153600 # Try to receive a frame try: frameBuffer = self.ser.read(length) # Error except serial.serialutil.SerialException: return False # Timeout if not frameBuffer: return False # Not enough data received if len(frameBuffer) != length: return False # Extract RGB values from the frame buffer rgbValues = self.extractRGB(frameBuffer) # Convert RGB values to image if self.hardwareVersion == 0 or self.hqRes == 0: img = pygame.image.frombuffer(rgbValues, (160, 120), "RGB") else: img = pygame.image.frombuffer(rgbValues, (320, 240), "RGB") # Scale image smooth self.thermalImg = pygame.transform.smoothscale(img, (640, 480)) # Refresh screen self.screen.blit(self.thermalImg, (0, 0)) # Everything OK return True # Get a whole set of raw data from the Lepton def getFrameData(self): # Receive 9600 byte for Lepton2 if self.leptonVersion == 0: length = 9600 # Receive 38400 byte for Lepton3 else: length = 38400 # Try to receive the data try: self.leptonData = list(map(lambda x: ord(chr(x)), self.ser.read(length))) # Error except serial.serialutil.SerialException: return False # Length does not match if len(self.leptonData) != length: return False # Timeout if not self.leptonData: return False # Try to receive the additional data try: additionalData = list(map(lambda x: ord(chr(x)), self.ser.read(16))) # Error except serial.serialutil.SerialException: return False # Timeout if not additionalData: return False # Return if size does not match if len(additionalData) != 16: return False # Read min & max self.minData = additionalData[0] * 256 + additionalData[1] self.maxData = additionalData[2] * 256 + additionalData[3] # Read spot temp self.spotTemp = [ additionalData[4], additionalData[5], additionalData[6], additionalData[7], ] self.spotTemp = b"".join(bytes([i]) for i in self.spotTemp) self.spotTemp = struct.unpack("> 6) & 1: self.displayText("Lepton I2C error", True) return False # Check Lepton SPI if not (cmd >> 7) & 1: self.displayText("Lepton SPI error", True) return False # Check Spot Sensor if not (cmd >> 0) & 1: self.displayText("Spot Sensor error", True) return False # Everything worked return True # Send start command def sendStart(self): # Send start and wait for ACK try: # Send start command self.ser.write(chr(self.cmd_start).encode()) # Read answer inData = self.ser.read(1) if inData: inData = ord(inData) else: return False # Error except serial.serialutil.SerialException: return False # Timeout if not inData: return False # ACK is not the expected value if inData != self.cmd_start: return False # Everything worked return True # Connect to the device def connect(self): # Display waiting message self.displayText(f"Waiting for device on {self.serial_port}..", False) # Check if device is connected to serial port while True: # Check if the user wants to exit self.checkExit() # Try to open the ports try: self.ser = serial.Serial(self.serial_port, 115200) # Did not work, try again in 1s except serial.serialutil.SerialException: time.sleep(1) continue # If connected, leave loop if self.ser: break # Show detected message self.displayText("Detected device!", False) # Give the device 5 seconds to boot self.ser.timeout = 5.0 # Clear buffer self.ser.flushInput() self.ser.flushOutput() # Send start command while not self.sendStart(): # Check if the user wants to exit self.checkExit() # Display message self.displayText("Put the device in live mode!", True) # Set the timeout to 3 seconds self.ser.timeout = 3.0 # Get diagnostic infos if not self.getDiagnostic(): self.displayText("There were hardware errors, exit..", True) self.endConnection() # Get configuration data if not self.getConfigData(): self.ser.close() return False # Get temperature points if not self.getTempPoints(): self.ser.close() return False # Everything worked return True # Check if the mouse has been pressed or a serial command is received def eventHandler(self): # Check for button clicks for event in pygame.event.get(): # Exit if event.type == pygame.QUIT: self.endConnection() # Click on image if event.type == pygame.MOUSEBUTTONUP: # Get mouse position mousePos = pygame.mouse.get_pos() # Left mouse - add point if event.button == 1: if not self.tempFunc(mousePos, True): self.displayText("Unable to add point on device!", True) # Right mouse - remove point if event.button == 3: if not self.tempFunc(mousePos, False): self.displayText("Unable to remove point on device!", True) # Save Thermal if "click" in self.buttonSaveThermal.handleEvent(event): self.saveThermal() # Record video if "click" in self.buttonSaveVideo.handleEvent(event): self.videoRecord() # Render mode if "click" in self.buttonRenderMode.handleEvent(event): self.setRenderMode() # Show spot if "click" in self.buttonSpot.handleEvent(event): if not self.setSpot(True): self.displayText("Error setting showSpot on device!", True) # Show bar if "click" in self.buttonBar.handleEvent(event): if not self.setBar(True): self.displayText("Error setting showBar on device!", True) # Show min / max if "click" in self.buttonHotCold.handleEvent(event): if not self.setMinMax(True): self.displayText("Error setting min/max device!", True) # Text color if "click" in self.buttonTextColor.handleEvent(event): if not self.setTextColor(True): self.displayText("Error setting textColor on device!", True) # Filter strength if "click" in self.buttonFilter.handleEvent(event): if not self.setFilterType(True): self.displayText("Error setting filterType on device!", True) # Color scheme if "click" in self.buttonColor.handleEvent(event): if not self.setColorScheme(True): self.displayText("Error setting colorScheme on device!", True) # Temperature limits if "click" in self.buttonLimits.handleEvent(event): if not self.setLimits(True): self.displayText("Error setting adjustLimits on device!", True) # Temperature format if "click" in self.buttonFormat.handleEvent(event): if not self.setFormat(True): self.displayText("Error setting tempFormat on device!", True) # Shutter if "click" in self.buttonShutter.handleEvent(event): if not self.runShutter(): self.displayText("Error triggering the shutter!", True) # Init procedures def setup(self): # Add environment variable for Windows XP if platform.release() == 6: os.environ["SDL_VIDEODRIVER"] = "windlib" # Init pygame pygame.init() # Set icon programIcon = pygame.image.load(self.resource_path("icon.jpg")) pygame.display.set_icon(programIcon) # Set window resolution self.screen = pygame.display.set_mode((640, 600)) self.screen.fill((230, 230, 230)) # Set windows title pygame.display.set_caption("DIY-Thermocam - Thermal Live Viewer") # Add the buttons self.drawButtons() # Welcome message self.displayText("Thermal Live Viewer", True) def loop(self): # Try to establish a connection while not self.connect(): self.displayText("Error connecting to device..", True) self.displayText("Connected to DIY-Thermocam V3!", True) while True: # Check for serial events if not self.serialHandler(): self.ser.close() self.displayText("Connection lost, reconnect..", True) break # Check for mouse press self.eventHandler() # Update screen pygame.display.flip() def run(self): self.setup() while True: self.loop() ================================================ FILE: software/thermal_live_viewer/README.MD ================================================ # DIY-Thermocam - Thermal Live Viewer This Python application allows you to connect to your DIY-Thermocam from a PC over the USB serial protocol and stream live thermal images. Run the main.py and change the comport for the device to work. The Video Recording relies on FFMPEG and does currently only work under Windows. ================================================ FILE: software/video_converter/README.MD ================================================ # DIY-Thermocam - Video Converter This video converter allows you to convert a whole folder containing bitmap frames from a video or timelapse capture into a movie file. You can either convert the raw data files to bitmaps on the device, or use the thermal data viewer together with the batch export function to do so (faster).