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