kopia lustrzana https://github.com/dl9rdz/rdz_ttgo_sonde
TTGO Sonde Playground. Initial version
rodzic
74e730328e
commit
c4f3e8d63b
|
@ -0,0 +1,6 @@
|
|||
GPL License Exceptions:
|
||||
|
||||
- The SX1278FSK library is based on
|
||||
https://github.com/pdelmo/lora_shield_arduino.git
|
||||
and licensed under
|
||||
GNU Lesser General Public License, version 2.1 (SPDX short identifier: LGPL-2.1)
|
|
@ -0,0 +1,30 @@
|
|||
TTGOSonde is a Free Software code collection for decoding radiosondes with
|
||||
the SX1278/ESP32-based TTGO board. The code is copyrighted by Hansi Reiser
|
||||
(dl9rdz) and several other authors who have provided a valuable base and
|
||||
additonal contributions.
|
||||
|
||||
You can redistribute TTGOSonde and/or modify it under the terms of version 2
|
||||
of the GNU General Public License as published by the Free Software Foundation,
|
||||
or, at your option, under any later version of the GNU General Public License.
|
||||
See individual files and the file 'Licenses/Exceptions' for exceptions.
|
||||
|
||||
Also note that the GPL and the other licenses are copyrighted by the Free
|
||||
Software Foundation and other organizations.
|
||||
|
||||
To avoid bloating individual code files with large license headers, the license
|
||||
headers in the source files have been replaced with a single line reference to
|
||||
a Unique License Identifier as defined by the Linux Foundation's SPDX project.
|
||||
For example, in a source file the full "GPL v2.0 or later" header text will be
|
||||
replaced by a single line:
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
The SPDX Unique License Identifiers are available at http://spdx.org/licenses/
|
||||
|
||||
We use the following License Identifiers in this project:
|
||||
|
||||
Full name SPDX Identifier OSI Approved File name URI
|
||||
=======================================================================================================================================
|
||||
GNU General Public License v2.0 only GPL-2.0 Y gpl-2.0.txt https://www.gnu.org/licenses/gpl-2.0.txt
|
||||
GNU General Public License v2.0 or later GPL-2.0+ Y gpl-2.0.txt https://www.gnu.org/licenses/gpl-2.0.txt
|
||||
GNU Lesser General Public License v2.1 LGPL-2.1+ Y lgpl-2.txt https://www.gnu.org/licenses/lgpl-2.1.txt
|
|
@ -0,0 +1,502 @@
|
|||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
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 this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
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
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser 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 Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "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
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY 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
|
||||
LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey 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 library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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 Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
|
@ -0,0 +1,184 @@
|
|||
#include <U8x8lib.h>
|
||||
#include <Sonde.h>
|
||||
|
||||
#include <WiFi.h>
|
||||
|
||||
#include <RS41.h>
|
||||
#include <SX1278FSK.h>
|
||||
#include <rsc.h>
|
||||
|
||||
#include <SPI.h>
|
||||
|
||||
#define LORA_LED 9
|
||||
|
||||
// I2C OLED Display works with SSD1306 driver
|
||||
#define OLED_SDA 4
|
||||
#define OLED_SCL 15
|
||||
#define OLED_RST 16
|
||||
|
||||
// UNCOMMENT one of the constructor lines below
|
||||
U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8(/* clock=*/ OLED_SCL, /* data=*/ OLED_SDA, /* reset=*/ OLED_RST); // Unbuffered, basic graphics, software I2C
|
||||
//U8G2_SSD1306_128X64_NONAME_1_SW_I2C Display(U8G2_R0, /* clock=*/ OLED_SCL, /* data=*/ OLED_SDA, /* reset=*/ OLED_RST); // Page buffer, SW I2C
|
||||
//U8G2_SSD1306_128X64_NONAME_F_SW_I2C Display(U8G2_R0, /* clock=*/ OLED_SCL, /* data=*/ OLED_SDA, /* reset=*/ OLED_RST); // Full framebuffer, SW I2C
|
||||
|
||||
int e;
|
||||
char my_packet[100];
|
||||
|
||||
const char* ssid = "DinoGast";
|
||||
const char* password = "Schokolade";
|
||||
|
||||
WiFiServer server(80);
|
||||
|
||||
pthread_t wifithread;
|
||||
|
||||
|
||||
int conn = 0;
|
||||
String currentLine;
|
||||
WiFiClient client;
|
||||
unsigned long lastdu;
|
||||
|
||||
void wifiloop(void *arg){
|
||||
lastdu=millis();
|
||||
while(true) {
|
||||
if(millis()-lastdu>500) {
|
||||
// This is too slow to do in main loop
|
||||
//u8x8.setFont(u8x8_font_chroma48medium8_r);
|
||||
//u8x8.clearDisplay();
|
||||
sonde.updateDisplay();
|
||||
lastdu=millis();
|
||||
}
|
||||
|
||||
|
||||
delay(1);
|
||||
if(!conn) {
|
||||
client = server.available(); // listen for incoming clients
|
||||
if (client) { // if you get a client,
|
||||
Serial.println("New Client."); // print a message out the serial port
|
||||
currentLine = ""; // make a String to hold incoming data from the client
|
||||
conn = 1;
|
||||
}
|
||||
} else {
|
||||
if(!client.connected()) { // loop while the client's connected
|
||||
conn = 0;
|
||||
Serial.println("Client no longer connected");
|
||||
continue;
|
||||
}
|
||||
while (client.available()) { // if there's bytes to read from the client,
|
||||
char c = client.read(); // read a byte, then
|
||||
Serial.write(c); // print it out the serial monitor
|
||||
if (c == '\n') { // if the byte is a newline character
|
||||
|
||||
// if the current line is blank, you got two newline characters in a row.
|
||||
// that's the end of the client HTTP request, so send a response:
|
||||
if (currentLine.length() == 0) {
|
||||
// HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
|
||||
// and a content-type so the client knows what's coming, then a blank line:
|
||||
client.println("HTTP/1.1 200 OK");
|
||||
client.println("Content-type:text/html");
|
||||
client.println();
|
||||
|
||||
// the content of the HTTP response follows the header:
|
||||
client.print("Click <a href=\"/H\">here</a> to turn the LED on pin 5 on.<br>");
|
||||
client.print("Click <a href=\"/L\">here</a> to turn the LED on pin 5 off.<br>");
|
||||
|
||||
// The HTTP response ends with another blank line:
|
||||
client.println();
|
||||
// break out of the while loop:
|
||||
// close the connection:
|
||||
client.stop();
|
||||
Serial.println("Client Disconnected.");
|
||||
continue;
|
||||
} else { // if you got a newline, then clear currentLine:
|
||||
currentLine = "";
|
||||
}
|
||||
} else if (c != '\r') { // if you got anything else but a carriage return character,
|
||||
currentLine += c; // add it to the end of the currentLine
|
||||
}
|
||||
|
||||
// Check to see if the client request was "GET /H" or "GET /L":
|
||||
if (currentLine.endsWith("GET /H")) {
|
||||
digitalWrite(5, HIGH); // GET /H turns the LED on
|
||||
}
|
||||
if (currentLine.endsWith("GET /L")) {
|
||||
digitalWrite(5, LOW); // GET /L turns the LED off
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
// Open serial communications and wait for port to open:
|
||||
Serial.begin(115200);
|
||||
|
||||
u8x8.begin();
|
||||
|
||||
|
||||
pinMode(LORA_LED, OUTPUT);
|
||||
WiFi.begin(ssid, password);
|
||||
while(WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
|
||||
Serial.println("");
|
||||
Serial.println("WiFi connected");
|
||||
Serial.println("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
server.begin();
|
||||
|
||||
xTaskCreatePinnedToCore(wifiloop, "WifiServer", 10240, NULL, 10, NULL, 0);
|
||||
|
||||
|
||||
rs41.setup();
|
||||
|
||||
if(rs41.setFrequency(402700000)==0) {
|
||||
Serial.println(F("Setting freq: SUCCESS "));
|
||||
} else {
|
||||
Serial.println(F("Setting freq: ERROR "));
|
||||
}
|
||||
float f = sx1278.getFrequency();
|
||||
Serial.print("Frequency set to ");
|
||||
Serial.println(f);
|
||||
|
||||
sx1278.setLNAGain(-48);
|
||||
int gain = sx1278.getLNAGain();
|
||||
Serial.print("RX LNA Gain is ");
|
||||
Serial.println(gain);
|
||||
|
||||
// Print a success message
|
||||
Serial.println(F("sx1278 configured finished"));
|
||||
Serial.println();
|
||||
|
||||
|
||||
Serial.println("Setup finished");
|
||||
// int returnValue = pthread_create(&wifithread, NULL, wifiloop, (void *)0);
|
||||
|
||||
// if (returnValue) {
|
||||
// Serial.println("An error has occurred");
|
||||
// }
|
||||
// xTaskCreate(mainloop, "MainServer", 10240, NULL, 10, NULL);
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
Serial.println("Running main loop");
|
||||
|
||||
|
||||
//wifiloop(NULL);
|
||||
//e = dfm.receiveFrame();
|
||||
e = rs41.receiveFrame();
|
||||
#if 0
|
||||
int rssi = sx1278.getRSSI();
|
||||
Serial.print(" RSSI: ");
|
||||
Serial.print(rssi);
|
||||
|
||||
int gain = sx1278.getLNAGain();
|
||||
Serial.print(" LNA Gain: "),
|
||||
Serial.println(gain);
|
||||
#endif
|
||||
|
||||
}
|
|
@ -0,0 +1,815 @@
|
|||
/*
|
||||
* Functions for using SX127x in FSK mode (mainly receive)
|
||||
* Copyright (C) 2019 Hansi Reiser, dl9rdz
|
||||
*
|
||||
* Partially based on the SX1278 libraray for managing Semtech modules
|
||||
* Copyright (C) 2015 Wireless Open Source
|
||||
* http://wirelessopensource.com
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#include "SX1278FSK.h"
|
||||
#include "SPI.h"
|
||||
|
||||
SX1278FSK::SX1278FSK()
|
||||
{
|
||||
// Initialize class variables
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Function: Turns the module ON.
|
||||
Returns: 0 on success, 1 otherwise
|
||||
*/
|
||||
uint8_t SX1278FSK::ON()
|
||||
{
|
||||
uint8_t state = 2;
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.println();
|
||||
Serial.println(F("Starting 'ON'"));
|
||||
#endif
|
||||
|
||||
// Powering the module
|
||||
pinMode(SX1278_SS, OUTPUT);
|
||||
digitalWrite(SX1278_SS, HIGH);
|
||||
|
||||
//Configure the MISO, MOSI, CS, SPCR.
|
||||
SPI.begin();
|
||||
//Set most significant bit first
|
||||
SPI.setBitOrder(MSBFIRST);
|
||||
//Divide the clock frequency
|
||||
SPI.setClockDivider(SPI_CLOCK_DIV2);
|
||||
//Set data mode
|
||||
SPI.setDataMode(SPI_MODE0);
|
||||
|
||||
// Set Maximum Over Current Protection
|
||||
state = setMaxCurrent(0x1B);
|
||||
if( state == 0 )
|
||||
{
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.println(F("## Setting ON with maximum current supply ##"));
|
||||
Serial.println();
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
// set FSK mode
|
||||
state = setFSK();
|
||||
return state;
|
||||
}
|
||||
|
||||
/*
|
||||
Function: Turn the module OFF.
|
||||
Returns: Nothing
|
||||
*/
|
||||
void SX1278FSK::OFF()
|
||||
{
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.println();
|
||||
Serial.println(F("Starting 'OFF'"));
|
||||
#endif
|
||||
|
||||
SPI.end();
|
||||
// Powering the module
|
||||
pinMode(SX1278_SS,OUTPUT);
|
||||
digitalWrite(SX1278_SS,LOW);
|
||||
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.println(F("## Setting OFF ##"));
|
||||
Serial.println();
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
Function: Reads the indicated register.
|
||||
Returns: The content of the register
|
||||
Parameters:
|
||||
address: address register to read from
|
||||
*/
|
||||
byte SX1278FSK::readRegister(byte address)
|
||||
{
|
||||
byte value = 0x00;
|
||||
|
||||
digitalWrite(SX1278_SS,LOW);
|
||||
|
||||
delay(1);
|
||||
bitClear(address, 7); // Bit 7 cleared to write in registers
|
||||
SPI.transfer(address);
|
||||
value = SPI.transfer(0x00);
|
||||
digitalWrite(SX1278_SS,HIGH);
|
||||
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
if(address!=0x3F) {
|
||||
Serial.print(F("## Reading: ##\t"));
|
||||
Serial.print(F("Register "));
|
||||
Serial.print(address, HEX);
|
||||
Serial.print(F(": "));
|
||||
Serial.print(value, HEX);
|
||||
Serial.println();
|
||||
}
|
||||
#endif
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
Function: Writes on the indicated register.
|
||||
Returns: Nothing
|
||||
Parameters:
|
||||
address: address register to write in
|
||||
data: value to write in the register
|
||||
*/
|
||||
void SX1278FSK::writeRegister(byte address, byte data)
|
||||
{
|
||||
digitalWrite(SX1278_SS,LOW);
|
||||
|
||||
delay(1);
|
||||
bitSet(address, 7); // Bit 7 set to read from registers
|
||||
SPI.transfer(address);
|
||||
SPI.transfer(data);
|
||||
digitalWrite(SX1278_SS,HIGH);
|
||||
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.print(F("## Writing: ##\t"));
|
||||
Serial.print(F("Register "));
|
||||
bitClear(address, 7);
|
||||
Serial.print(address, HEX);
|
||||
Serial.print(F(": "));
|
||||
Serial.print(data, HEX);
|
||||
Serial.println();
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Function: Clears the IRQ flags
|
||||
*
|
||||
* Configuration registers are accessed through the SPI interface.
|
||||
* Registers are readable in all device mode including Sleep. However, they
|
||||
* should be written only in Sleep and Stand-by modes.
|
||||
*
|
||||
* Returns: Nothing
|
||||
*/
|
||||
void SX1278FSK::clearIRQFlags()
|
||||
{
|
||||
byte st0;
|
||||
|
||||
// Save the previous status
|
||||
st0 = readRegister(REG_OP_MODE);
|
||||
// Stdby mode to write in registers
|
||||
writeRegister(REG_OP_MODE, FSK_STANDBY_MODE);
|
||||
// FSK mode flags1 register
|
||||
writeRegister(REG_IRQ_FLAGS1, 0xFF);
|
||||
// FSK mode flags2 register
|
||||
writeRegister(REG_IRQ_FLAGS2, 0xFF);
|
||||
// Getting back to previous status
|
||||
if(st0 != FSK_STANDBY_MODE) {
|
||||
writeRegister(REG_OP_MODE, st0);
|
||||
}
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.println(F("## FSK flags cleared ##"));
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
Function: Sets the module in FSK mode.
|
||||
Returns: Integer that determines if there has been any error
|
||||
state = 2 --> The command has not been executed
|
||||
state = 1 --> There has been an error while executing the command
|
||||
state = 0 --> The command has been executed with no errors
|
||||
*/
|
||||
uint8_t SX1278FSK::setFSK()
|
||||
{
|
||||
uint8_t state = 2;
|
||||
byte st0;
|
||||
byte config1;
|
||||
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.println();
|
||||
Serial.println(F("Starting 'setFSK'"));
|
||||
#endif
|
||||
|
||||
writeRegister(REG_OP_MODE, FSK_SLEEP_MODE); // Sleep mode (mandatory to change mode)
|
||||
// If we are in LORA mode, above line activate Sleep mode, but does not change mode to FSK
|
||||
// as mode change is only allowed in sleep mode. Next line changes to FSK
|
||||
writeRegister(REG_OP_MODE, FSK_SLEEP_MODE);
|
||||
|
||||
writeRegister(REG_OP_MODE, FSK_STANDBY_MODE); // FSK standby mode
|
||||
|
||||
//writeRegister(REG_FIFO_THRESH, 0x80); // condition to start packet tx
|
||||
//config1 = readRegister(REG_SYNC_CONFIG);
|
||||
//config1 = config1 & B00111111;
|
||||
//writeRegister(REG_SYNC_CONFIG,config1);
|
||||
|
||||
delay(100);
|
||||
|
||||
st0 = readRegister(REG_OP_MODE); // Reading config mode
|
||||
if( st0 == FSK_STANDBY_MODE )
|
||||
{ // FSK mode
|
||||
state = 0;
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.println(F("## FSK set with success ##"));
|
||||
Serial.println();
|
||||
#endif
|
||||
} else { // LoRa mode
|
||||
state = 1;
|
||||
Serial.println( st0 );
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.println(F("** There has been an error while setting FSK **"));
|
||||
Serial.println();
|
||||
#endif
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
/* Function: Sets FSK bitrate
|
||||
* Returns: 0 for success, >0 in case of error
|
||||
* Parameters: bps: requested bitrate
|
||||
* (raw data rate, for Mancester encoding, the effective bitrate is bps/2)
|
||||
*/
|
||||
|
||||
uint8_t SX1278FSK::setBitrate(float bps)
|
||||
{
|
||||
// TODO: Check if FSK mode is active
|
||||
|
||||
// check if bitrate is allowed allowed bitrate
|
||||
if((bps < 1200) || (bps > 300000)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// set mode to FSK STANDBY
|
||||
writeRegister(REG_OP_MODE, FSK_STANDBY_MODE);
|
||||
|
||||
// set bit rate
|
||||
uint16_t bitRate = (SX127X_CRYSTAL_FREQ * 1.0) / bps;
|
||||
writeRegister(REG_BITRATE_MSB, (bitRate & 0xFF00) >> 8);
|
||||
writeRegister(REG_BITRATE_LSB, (bitRate & 0x00FF));
|
||||
|
||||
// also set fractional part
|
||||
uint16_t fracRate = (SX127X_CRYSTAL_FREQ * 16.0) / bps - bitRate * 16 + 0.5;
|
||||
writeRegister(REG_BIT_RATE_FRAC, fracRate&0x0F);
|
||||
return 0;
|
||||
}
|
||||
/* Function: Gets configured bitrate
|
||||
* Returns bitrate in bit/second
|
||||
*/
|
||||
float SX1278FSK::getBitrate()
|
||||
{
|
||||
uint8_t fmsb = readRegister(REG_BITRATE_MSB);
|
||||
uint8_t flsb = readRegister(REG_BITRATE_LSB);
|
||||
uint8_t ffrac = readRegister(REG_BIT_RATE_FRAC) & 0x0F;
|
||||
return SX127X_CRYSTAL_FREQ / ( (fmsb<<8) + flsb + ffrac / 16.0 );
|
||||
}
|
||||
|
||||
//typedef struct rxbwset { float bw; uint8_t mant; uint8_t rxp; } st_rxbwsettings;
|
||||
|
||||
uint8_t SX1278FSK::setRxBandwidth(float bw)
|
||||
{
|
||||
// TODO: Check if in FSK mode
|
||||
//
|
||||
if(bw<2600 || bw>250000) { return 1; /* invalid */ }
|
||||
|
||||
uint8_t rxbwexp = 1;
|
||||
bw = SX127X_CRYSTAL_FREQ / bw / 8;
|
||||
while(bw>31) { rxbwexp++; bw/=2.0; }
|
||||
uint8_t rxbwmant = bw<17?0 : bw<21? 1:2;
|
||||
|
||||
// set mode to FSK STANDBY
|
||||
writeRegister(REG_OP_MODE, FSK_STANDBY_MODE);
|
||||
|
||||
writeRegister(REG_RX_BW, rxbwexp | (rxbwmant<<3));
|
||||
return 0;
|
||||
}
|
||||
|
||||
float SX1278FSK::getRxBandwidth()
|
||||
{
|
||||
uint8_t rxbw = readRegister(REG_RX_BW);
|
||||
uint8_t rxbwexp = rxbw&0x07;
|
||||
uint8_t rxbwmant = (rxbw>>3)&0x03;
|
||||
rxbwmant = 16 + 4*rxbwmant;
|
||||
return SX127X_CRYSTAL_FREQ / ( rxbwmant << (rxbwexp+2));
|
||||
}
|
||||
|
||||
uint8_t SX1278FSK::setAFCBandwidth(float bw)
|
||||
{
|
||||
// TODO: Check if in FSK mode
|
||||
//
|
||||
if(bw<2600 || bw>250000) { return 1; /* invalid */ }
|
||||
|
||||
uint8_t rxbwexp = 1;
|
||||
bw = SX127X_CRYSTAL_FREQ / bw / 8;
|
||||
while(bw>31) { rxbwexp++; bw/=2.0; }
|
||||
uint8_t rxbwmant = bw<17?0 : bw<21? 1:2;
|
||||
|
||||
// set mode to FSK STANDBY
|
||||
writeRegister(REG_OP_MODE, FSK_STANDBY_MODE);
|
||||
|
||||
writeRegister(REG_AFC_BW, rxbwexp | (rxbwmant<<3));
|
||||
return 0;
|
||||
}
|
||||
|
||||
float SX1278FSK::getAFCBandwidth()
|
||||
{
|
||||
uint8_t rxbw = readRegister(REG_AFC_BW);
|
||||
uint8_t rxbwexp = rxbw&0x07;
|
||||
uint8_t rxbwmant = (rxbw>>3)&0x03;
|
||||
rxbwmant = 16 + 4*rxbwmant;
|
||||
return SX127X_CRYSTAL_FREQ / ( rxbwmant << (rxbwexp+2));
|
||||
}
|
||||
|
||||
|
||||
uint8_t SX1278FSK::setFrequency(float freq) {
|
||||
|
||||
// set mode to FSK STANDBY
|
||||
writeRegister(REG_OP_MODE, FSK_STANDBY_MODE);
|
||||
|
||||
uint32_t frf = freq * 1.0 * (1<<19) / SX127X_CRYSTAL_FREQ;
|
||||
writeRegister(REG_FRF_MSB, (frf&0xff0000)>>16);
|
||||
writeRegister(REG_FRF_MID, (frf&0x00ff00)>>8);
|
||||
writeRegister(REG_FRF_LSB, (frf&0x0000ff));
|
||||
return 0;
|
||||
}
|
||||
|
||||
float SX1278FSK::getFrequency() {
|
||||
uint8_t fmsb = readRegister(REG_FRF_MSB);
|
||||
uint8_t fmid = readRegister(REG_FRF_MID);
|
||||
uint8_t flsb = readRegister(REG_FRF_LSB);
|
||||
return ((fmsb<<16)|(fmid<<8)|flsb) * 1.0 / (1<<19) * SX127X_CRYSTAL_FREQ;
|
||||
}
|
||||
|
||||
|
||||
static int gaintab[]={-999,0,-6,-12,-24,-36,-48,-999};
|
||||
int SX1278FSK::getLNAGain() {
|
||||
int gain = (readRegister(REG_LNA)>>5)&0x07;
|
||||
return gaintab[gain];
|
||||
}
|
||||
uint8_t SX1278FSK::setLNAGain(int gain) {
|
||||
uint8_t g=1;
|
||||
while(gain<gaintab[g] && g<6) {g++; }
|
||||
writeRegister(REG_LNA, g<<5);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t SX1278FSK::getRxConf() {
|
||||
return readRegister(REG_RX_CONFIG);
|
||||
}
|
||||
uint8_t SX1278FSK::setRxConf(uint8_t conf) {
|
||||
writeRegister(REG_RX_CONFIG, conf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t SX1278FSK::setSyncConf(uint8_t conf, int len, const uint8_t *syncpattern) {
|
||||
int res=0;
|
||||
writeRegister(REG_SYNC_CONFIG, conf);
|
||||
if(len>8) return 1;
|
||||
for(int i=0; i<len; i++) {
|
||||
writeRegister(REG_SYNC_VALUE1+i, syncpattern[i]);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
uint8_t SX1278FSK::getSyncConf() {
|
||||
return sx1278.readRegister(REG_SYNC_CONFIG);
|
||||
}
|
||||
|
||||
uint8_t SX1278FSK::setPreambleDetect(uint8_t conf) {
|
||||
sx1278.writeRegister(REG_PREAMBLE_DETECT, conf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t SX1278FSK::getPreambleDetect() {
|
||||
return sx1278.readRegister(REG_PREAMBLE_DETECT);
|
||||
}
|
||||
|
||||
uint8_t SX1278FSK::setPacketConfig(uint8_t conf1, uint8_t conf2)
|
||||
{
|
||||
uint8_t ret=0;
|
||||
sx1278.writeRegister(REG_PACKET_CONFIG1, conf1);
|
||||
sx1278.writeRegister(REG_PACKET_CONFIG2, conf2);
|
||||
return ret;
|
||||
};
|
||||
uint16_t SX1278FSK::getPacketConfig() {
|
||||
uint8_t c1 = sx1278.readRegister(REG_PACKET_CONFIG1);
|
||||
uint8_t c2 = sx1278.readRegister(REG_PACKET_CONFIG2);
|
||||
return (c2<<8)|c1;
|
||||
}
|
||||
|
||||
/*
|
||||
Function: Gets the preamble length from the module.
|
||||
Returns: preamble length
|
||||
*/
|
||||
uint16_t SX1278FSK::getPreambleLength()
|
||||
{
|
||||
uint16_t p_length;
|
||||
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.println();
|
||||
Serial.println(F("Starting 'getPreambleLength'"));
|
||||
#endif
|
||||
|
||||
p_length = readRegister(REG_PREAMBLE_MSB_FSK);
|
||||
p_length = (p_length<<8) | readRegister(REG_PREAMBLE_LSB_FSK);
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.print(F("## Preamble length configured is "));
|
||||
Serial.print(p_length, HEX);
|
||||
Serial.print(F(" ##"));
|
||||
Serial.println();
|
||||
#endif
|
||||
return p_length;
|
||||
}
|
||||
|
||||
/*
|
||||
Function: Sets the preamble length in the module
|
||||
Returns: Integer that determines if there has been any error
|
||||
state = 2 --> The command has not been executed
|
||||
state = 1 --> There has been an error while executing the command
|
||||
state = 0 --> The command has been executed with no errors
|
||||
Parameters:
|
||||
l: length value to set as preamble length.
|
||||
*/
|
||||
uint8_t SX1278FSK::setPreambleLength(uint16_t l)
|
||||
{
|
||||
byte st0;
|
||||
int8_t state = 2;
|
||||
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.println();
|
||||
Serial.println(F("Starting 'setPreambleLength'"));
|
||||
#endif
|
||||
|
||||
st0 = readRegister(REG_OP_MODE); // Save the previous status
|
||||
|
||||
writeRegister(REG_OP_MODE, FSK_STANDBY_MODE); // Set Standby mode to write in registers
|
||||
// Storing MSB preamble length in FSK mode
|
||||
writeRegister(REG_PREAMBLE_MSB_FSK, l>>8);
|
||||
writeRegister(REG_PREAMBLE_LSB_FSK, l&0xFF);
|
||||
|
||||
state = 0;
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.print(F("## Preamble length "));
|
||||
Serial.print(l, HEX);
|
||||
Serial.println(F(" has been successfully set ##"));
|
||||
Serial.println();
|
||||
#endif
|
||||
|
||||
if(st0 != FSK_STANDBY_MODE) {
|
||||
writeRegister(REG_OP_MODE, st0); // Getting back to previous status
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
/*
|
||||
Function: Gets the payload length from the module.
|
||||
Returns: configured length; -1 on error
|
||||
*/
|
||||
int SX1278FSK::getPayloadLength()
|
||||
{
|
||||
int length;
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.println();
|
||||
Serial.println(F("Starting 'getPayloadLength'"));
|
||||
#endif
|
||||
length = readRegister(REG_PAYLOAD_LENGTH_FSK);
|
||||
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.print(F("## Payload length configured is "));
|
||||
Serial.print(length);
|
||||
Serial.println(F(" ##"));
|
||||
#endif
|
||||
return length;
|
||||
}
|
||||
|
||||
/*
|
||||
Function: Sets the payload length from the module.
|
||||
Returns: 0 for ok, otherwise error
|
||||
// TODO: Larger than 255 bytes?
|
||||
*/
|
||||
uint8_t SX1278FSK::setPayloadLength(int len)
|
||||
{
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.print(F("Starting 'setPayloadLength'"));
|
||||
Serial.println(len);
|
||||
#endif
|
||||
uint8_t conf2 = readRegister(REG_PACKET_CONFIG2);
|
||||
conf2 = (conf2 & 0xF8) | ( (len>>8)&0x7 );
|
||||
writeRegister(REG_PACKET_CONFIG2, conf2);
|
||||
writeRegister(REG_PAYLOAD_LENGTH_FSK, len&0xFF);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Function: Gets the current value of RSSI.
|
||||
Returns: RSSI value
|
||||
*/
|
||||
int16_t SX1278FSK::getRSSI()
|
||||
{
|
||||
int16_t RSSI;
|
||||
//int rssi_mean = 0;
|
||||
int total = 1;
|
||||
|
||||
/// FSK mode
|
||||
// get mean value of RSSI
|
||||
for(int i = 0; i < total; i++)
|
||||
{
|
||||
RSSI = readRegister(REG_RSSI_VALUE_FSK);
|
||||
//rssi_mean += _RSSI;
|
||||
}
|
||||
//rssi_mean = rssi_mean / total;
|
||||
//RSSI = rssi_mean;
|
||||
|
||||
#if (SX1278FSK_debug_mode > 0)
|
||||
Serial.print(F("## RSSI value is "));
|
||||
Serial.print(RSSI);
|
||||
Serial.println(F(" ##"));
|
||||
#endif
|
||||
return RSSI;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Function: Gets the current supply limit of the power amplifier, protecting battery chemistries.
|
||||
Returns: Integer that determines if there has been any error
|
||||
state = 2 --> The command has not been executed
|
||||
state = 1 --> There has been an error while executing the command
|
||||
state = 0 --> The command has been executed with no errors
|
||||
Parameters:
|
||||
rate: value to compute the maximum current supply. Maximum current is 45+5*'rate' [mA]
|
||||
*/
|
||||
int SX1278FSK::getMaxCurrent()
|
||||
{
|
||||
int value;
|
||||
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.println();
|
||||
Serial.println(F("Starting 'getMaxCurrent'"));
|
||||
#endif
|
||||
|
||||
value = readRegister(REG_OCP);
|
||||
|
||||
// extract only the OcpTrim value from the OCP register
|
||||
value &= B00011111;
|
||||
|
||||
if( value <= 15 ) {
|
||||
value = (45 + (5 * value));
|
||||
} else if( value <= 27 ) {
|
||||
value = (-30 + (10 * value));
|
||||
} else {
|
||||
value = 240;
|
||||
}
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.print(F("## Maximum current supply configured is "));
|
||||
Serial.print(value, DEC);
|
||||
Serial.println(F(" mA ##"));
|
||||
Serial.println();
|
||||
#endif
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
Function: Limits the current supply of the power amplifier, protecting battery chemistries.
|
||||
Returns: Integer that determines if there has been any error
|
||||
state = 2 --> The command has not been executed
|
||||
state = 1 --> There has been an error while executing the command
|
||||
state = 0 --> The command has been executed with no errors
|
||||
state = -1 --> Forbidden parameter value for this function
|
||||
Parameters:
|
||||
rate: value to compute the maximum current supply. Range: 0x00 to 0x1B. The
|
||||
Maximum current is:
|
||||
Imax = 45+5*OcpTrim [mA] if OcpTrim <= 15 (120 mA) /
|
||||
Imax = -30+10*OcpTrim [mA] if 15 < OcpTrim <= 27 (130 to 240 mA)
|
||||
Imax = 240mA for higher settings
|
||||
*/
|
||||
int8_t SX1278FSK::setMaxCurrent(uint8_t rate)
|
||||
{
|
||||
int8_t state = 2;
|
||||
byte st0;
|
||||
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.println();
|
||||
Serial.println(F("Starting 'setMaxCurrent'"));
|
||||
#endif
|
||||
|
||||
// Maximum rate value = 0x1B, because maximum current supply = 240 mA
|
||||
if (rate > 0x1B)
|
||||
{
|
||||
state = -1;
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.print(F("** Maximum current supply is 240 mA, "));
|
||||
Serial.println(F("so maximum parameter value must be 27 (DEC) or 0x1B (HEX) **"));
|
||||
Serial.println();
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
// Enable Over Current Protection
|
||||
rate |= B00100000;
|
||||
|
||||
state = 1;
|
||||
st0 = readRegister(REG_OP_MODE); // Save the previous status
|
||||
writeRegister(REG_OP_MODE, FSK_STANDBY_MODE); // Set FSK Standby mode to write in registers
|
||||
writeRegister(REG_OCP, rate); // Modifying maximum current supply
|
||||
if(st0 != FSK_STANDBY_MODE) {
|
||||
writeRegister(REG_OP_MODE, st0); // Getting back to previous status
|
||||
}
|
||||
state = 0;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Function: Configures the module to receive information.
|
||||
Returns: Integer that determines if there has been any error
|
||||
state = 2 --> The command has not been executed
|
||||
state = 1 --> There has been an error while executing the command
|
||||
state = 0 --> The command has been executed with no errors
|
||||
*/
|
||||
uint8_t SX1278FSK::receive()
|
||||
{
|
||||
uint8_t state = 1;
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.println();
|
||||
Serial.println(F("Starting 'receive'"));
|
||||
#endif
|
||||
// TODO: Is there anything else to be done?
|
||||
//
|
||||
writeRegister(REG_OP_MODE, FSK_RX_MODE);
|
||||
state = 0;
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.println(F("## Receiving FSK mode activated with success ##"));
|
||||
#endif
|
||||
return state;
|
||||
}
|
||||
|
||||
/*
|
||||
Function: Configures the module to receive a packet
|
||||
Returns: Integer that determines if there has been any error
|
||||
state = 2 --> The command has not been executed
|
||||
state = 1 --> There has been an error while executing the command
|
||||
state = 0 --> The command has been executed with no errors
|
||||
Parameters:
|
||||
wait: timeout in ms
|
||||
data: memory where to place received data
|
||||
*/
|
||||
uint8_t SX1278FSK::receivePacketTimeout(uint32_t wait, byte *data)
|
||||
{
|
||||
int di=0;
|
||||
uint8_t state = 2;
|
||||
unsigned long previous;
|
||||
byte value = 0x00;
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.println();
|
||||
Serial.println(F("Starting 'receivePacketTimeout'"));
|
||||
#endif
|
||||
// set RX mode
|
||||
state = receive();
|
||||
if(state != 0) { return state; }
|
||||
|
||||
boolean p_received = false;
|
||||
|
||||
#if (SX1278FSK_debug_mode > 0)
|
||||
Serial.println(F("RX mode sucessfully activated"));
|
||||
#endif
|
||||
previous = millis();
|
||||
/// FSK mode
|
||||
value = readRegister(REG_IRQ_FLAGS2);
|
||||
byte ready=0;
|
||||
// while not yet done or FIFO not yet empty
|
||||
while( (!ready || bitRead(value,6)==0) && (millis() - previous < wait) )
|
||||
{
|
||||
if( bitRead(value,2)==1 ) ready=1;
|
||||
if( bitRead(value, 6) == 0 ) { // FIFO not empty
|
||||
data[di++] = readRegister(REG_FIFO);
|
||||
if(di==1) {
|
||||
int rssi=getRSSI();
|
||||
Serial.print("Test: RSSI="); Serial.println(rssi);
|
||||
}
|
||||
if(di>520) {
|
||||
// TODO
|
||||
Serial.println("TOO MUCH DATA");
|
||||
break;
|
||||
}
|
||||
previous = millis(); // reset timeout after receiving data
|
||||
}
|
||||
value = readRegister(REG_IRQ_FLAGS2);
|
||||
}
|
||||
if( !ready || bitRead(value, 6)==0) {
|
||||
#if 1&&(SX1278FSK_debug_mode > 0)
|
||||
Serial.println(F("** The timeout has expired **"));
|
||||
Serial.println();
|
||||
#endif
|
||||
writeRegister(REG_OP_MODE, FSK_STANDBY_MODE); // Setting standby FSK mode
|
||||
return 1; // TIMEOUT
|
||||
}
|
||||
|
||||
#if (SX1278FSK_debug_mode > 0)
|
||||
Serial.println(F("## Packet received:"));
|
||||
for(unsigned int i = 0; i < di; i++)
|
||||
{
|
||||
Serial.print(data[i], HEX); // Printing payload
|
||||
Serial.print("|");
|
||||
}
|
||||
Serial.println(F(" ##"));
|
||||
#endif
|
||||
state = 0;
|
||||
// Initializing flags
|
||||
clearIRQFlags();
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
#if 0
|
||||
/*
|
||||
Function: It gets the temperature from the measurement block module.
|
||||
Returns: Integer that determines if there has been any error
|
||||
state = 2 --> The command has not been executed
|
||||
state = 1 --> There has been an error while executing the command
|
||||
state = 0 --> The command has been executed with no errors
|
||||
*/
|
||||
uint8_t SX1278FSK::getTemp()
|
||||
{
|
||||
byte st0;
|
||||
uint8_t state = 2;
|
||||
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.println();
|
||||
Serial.println(F("Starting 'getTemp'"));
|
||||
#endif
|
||||
|
||||
st0 = readRegister(REG_OP_MODE); // Save the previous status
|
||||
|
||||
if( _modem == LORA )
|
||||
{ // Allowing access to FSK registers while in LoRa standby mode
|
||||
writeRegister(REG_OP_MODE, LORA_STANDBY_FSK_REGS_MODE);
|
||||
}
|
||||
|
||||
state = 1;
|
||||
// Saving temperature value
|
||||
_temp = readRegister(REG_TEMP);
|
||||
if( _temp & 0x80 ) // The SNR sign bit is 1
|
||||
{
|
||||
// Invert and divide by 4
|
||||
_temp = ( ( ~_temp + 1 ) & 0xFF );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Divide by 4
|
||||
_temp = ( _temp & 0xFF );
|
||||
}
|
||||
|
||||
|
||||
#if (SX1278FSK_debug_mode > 1)
|
||||
Serial.print(F("## Temperature is: "));
|
||||
Serial.print(_temp);
|
||||
Serial.println(F(" ##"));
|
||||
Serial.println();
|
||||
#endif
|
||||
|
||||
if( _modem == LORA )
|
||||
{
|
||||
writeRegister(REG_OP_MODE, st0); // Getting back to previous status
|
||||
}
|
||||
|
||||
state = 0;
|
||||
return state;
|
||||
}
|
||||
|
||||
/*
|
||||
Function: It prints the registers related to RX
|
||||
Returns: Integer that determines if there has been any error
|
||||
state = 2 --> The command has not been executed
|
||||
state = 1 --> There has been an error while executing the command
|
||||
state = 0 --> The command has been executed with no errors
|
||||
*/
|
||||
void SX1278FSK::showRxRegisters()
|
||||
{
|
||||
Serial.println(F("\n--- Show RX register ---"));
|
||||
|
||||
// variable
|
||||
byte reg;
|
||||
|
||||
for(int i = 0x00; i < 0x80; i++)
|
||||
{
|
||||
reg = readRegister(i);
|
||||
Serial.print(F("Reg 0x"));
|
||||
Serial.print(i, HEX);
|
||||
Serial.print(F(":"));
|
||||
Serial.print(reg, HEX);
|
||||
Serial.println();
|
||||
delay(100);
|
||||
}
|
||||
|
||||
Serial.println(F("------------------------"));
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
SX1278FSK sx1278 = SX1278FSK();
|
|
@ -0,0 +1,271 @@
|
|||
/*
|
||||
* Functions for using SX127x in FSK mode (mainly receive)
|
||||
* Copyright (C) 2019 Hansi Reiser, dl9rdz
|
||||
*
|
||||
* Partially based on the SX1278 libraray for managing Semtech modules
|
||||
* Copyright (C) 2015 Wireless Open Source
|
||||
* http://wirelessopensource.com
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#ifndef SX1278FSK_h
|
||||
#define SX1278FSK_h
|
||||
|
||||
/******************************************************************************
|
||||
* Includes
|
||||
******************************************************************************/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <Arduino.h>
|
||||
#include <SPI.h>
|
||||
|
||||
#ifndef inttypes_h
|
||||
#include <inttypes.h>
|
||||
#endif
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* Definitions & Declarations
|
||||
*****************************************************************************/
|
||||
|
||||
#define SX127X_CRYSTAL_FREQ 32000000
|
||||
|
||||
#define SX1278FSK_debug_mode 0
|
||||
|
||||
#define SX1278_SS SS
|
||||
|
||||
//! MACROS //
|
||||
#define bitRead(value, bit) (((value) >> (bit)) & 0x01) // read a bit
|
||||
#define bitSet(value, bit) ((value) |= (1UL << (bit))) // set bit to '1'
|
||||
#define bitClear(value, bit) ((value) &= ~(1UL << (bit))) // set bit to '0'
|
||||
|
||||
|
||||
//! REGISTERS //
|
||||
// FSK Commun LORA
|
||||
#define REG_FIFO 0x00
|
||||
#define REG_OP_MODE 0x01
|
||||
#define REG_BITRATE_MSB 0x02
|
||||
#define REG_BITRATE_LSB 0x03
|
||||
#define REG_FDEV_MSB 0x04
|
||||
#define REG_FDEV_LSB 0x05
|
||||
#define REG_FRF_MSB 0x06
|
||||
#define REG_FRF_MID 0x07
|
||||
#define REG_FRF_LSB 0x08
|
||||
#define REG_PA_CONFIG 0x09
|
||||
#define REG_PA_RAMP 0x0A
|
||||
#define REG_OCP 0x0B
|
||||
#define REG_LNA 0x0C
|
||||
#define REG_RX_CONFIG 0x0D
|
||||
#define REG_FIFO_ADDR_PTR 0x0D
|
||||
#define REG_RSSI_CONFIG 0x0E
|
||||
#define REG_FIFO_TX_BASE_ADDR 0x0E
|
||||
#define REG_RSSI_COLLISION 0x0F
|
||||
#define REG_FIFO_RX_BASE_ADDR 0x0F
|
||||
#define REG_RSSI_THRESH 0x10
|
||||
#define REG_FIFO_RX_CURRENT_ADDR 0x10
|
||||
#define REG_RSSI_VALUE_FSK 0x11
|
||||
#define REG_IRQ_FLAGS_MASK 0x11
|
||||
#define REG_RX_BW 0x12
|
||||
#define REG_IRQ_FLAGS 0x12
|
||||
#define REG_AFC_BW 0x13
|
||||
#define REG_RX_NB_BYTES 0x13
|
||||
#define REG_OOK_PEAK 0x14
|
||||
#define REG_RX_HEADER_CNT_VALUE_MSB 0x14
|
||||
#define REG_OOK_FIX 0x15
|
||||
#define REG_RX_HEADER_CNT_VALUE_LSB 0x15
|
||||
#define REG_OOK_AVG 0x16
|
||||
#define REG_RX_PACKET_CNT_VALUE_MSB 0x16
|
||||
#define REG_RX_PACKET_CNT_VALUE_LSB 0x17
|
||||
#define REG_MODEM_STAT 0x18
|
||||
#define REG_PKT_SNR_VALUE 0x19
|
||||
#define REG_AFC_FEI 0x1A
|
||||
#define REG_PKT_RSSI_VALUE 0x1A
|
||||
#define REG_AFC_MSB 0x1B
|
||||
#define REG_RSSI_VALUE_LORA 0x1B
|
||||
#define REG_AFC_LSB 0x1C
|
||||
#define REG_HOP_CHANNEL 0x1C
|
||||
#define REG_FEI_MSB 0x1D
|
||||
#define REG_MODEM_CONFIG1 0x1D
|
||||
#define REG_FEI_LSB 0x1E
|
||||
#define REG_MODEM_CONFIG2 0x1E
|
||||
#define REG_PREAMBLE_DETECT 0x1F
|
||||
#define REG_SYMB_TIMEOUT_LSB 0x1F
|
||||
#define REG_RX_TIMEOUT1 0x20
|
||||
#define REG_PREAMBLE_MSB_LORA 0x20
|
||||
#define REG_RX_TIMEOUT2 0x21
|
||||
#define REG_PREAMBLE_LSB_LORA 0x21
|
||||
#define REG_RX_TIMEOUT3 0x22
|
||||
#define REG_PAYLOAD_LENGTH_LORA 0x22
|
||||
#define REG_RX_DELAY 0x23
|
||||
#define REG_MAX_PAYLOAD_LENGTH 0x23
|
||||
#define REG_OSC 0x24
|
||||
#define REG_HOP_PERIOD 0x24
|
||||
#define REG_PREAMBLE_MSB_FSK 0x25
|
||||
#define REG_FIFO_RX_BYTE_ADDR 0x25
|
||||
#define REG_PREAMBLE_LSB_FSK 0x26
|
||||
#define REG_MODEM_CONFIG3 0x26
|
||||
#define REG_SYNC_CONFIG 0x27
|
||||
#define REG_SYNC_VALUE1 0x28
|
||||
#define REG_FEI_MSB 0x28
|
||||
#define REG_SYNC_VALUE2 0x29
|
||||
#define REG_FEI_MID 0x29
|
||||
#define REG_SYNC_VALUE3 0x2A
|
||||
#define REG_FEI_LSB 0x2A
|
||||
#define REG_SYNC_VALUE4 0x2B
|
||||
#define REG_SYNC_VALUE5 0x2C
|
||||
#define REG_RSSI_WIDEBAND 0x2C
|
||||
#define REG_SYNC_VALUE6 0x2D
|
||||
#define REG_SYNC_VALUE7 0x2E
|
||||
#define REG_SYNC_VALUE8 0x2F
|
||||
#define REG_PACKET_CONFIG1 0x30
|
||||
#define REG_PACKET_CONFIG2 0x31
|
||||
#define REG_DETECT_OPTIMIZE 0x31
|
||||
#define REG_PAYLOAD_LENGTH_FSK 0x32
|
||||
#define REG_NODE_ADRS 0x33
|
||||
#define REG_INVERT_IQ 0x33
|
||||
#define REG_BROADCAST_ADRS 0x34
|
||||
#define REG_FIFO_THRESH 0x35
|
||||
#define REG_SEQ_CONFIG1 0x36
|
||||
#define REG_SEQ_CONFIG2 0x37
|
||||
#define REG_DETECTION_THRESHOLD 0x37
|
||||
#define REG_TIMER_RESOL 0x38
|
||||
#define REG_TIMER1_COEF 0x39
|
||||
#define REG_SYNC_WORD 0x39
|
||||
#define REG_TIMER2_COEF 0x3A
|
||||
#define REG_IMAGE_CAL 0x3B
|
||||
#define REG_TEMP 0x3C
|
||||
#define REG_LOW_BAT 0x3D
|
||||
#define REG_IRQ_FLAGS1 0x3E
|
||||
#define REG_IRQ_FLAGS2 0x3F
|
||||
#define REG_DIO_MAPPING1 0x40
|
||||
#define REG_DIO_MAPPING2 0x41
|
||||
#define REG_VERSION 0x42
|
||||
#define REG_PLL_HOP 0x44
|
||||
#define REG_TCXO 0x4B
|
||||
#define REG_PA_DAC 0x4D
|
||||
#define REG_FORMER_TEMP 0x5B
|
||||
#define REG_BIT_RATE_FRAC 0x5D
|
||||
#define REG_AGC_REF 0x61
|
||||
#define REG_AGC_THRESH1 0x62
|
||||
#define REG_AGC_THRESH2 0x63
|
||||
#define REG_AGC_THRESH3 0x64
|
||||
#define REG_PLL 0x70
|
||||
|
||||
|
||||
//FSK MODES:
|
||||
const uint8_t FSK_SLEEP_MODE = 0x00;
|
||||
const uint8_t FSK_STANDBY_MODE = 0x01;
|
||||
const uint8_t FSK_TX_MODE = 0x03;
|
||||
const uint8_t FSK_RX_MODE = 0x05;
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* SX1278FSK Class
|
||||
* Functions and variables for managing SX127x transceiver chips in FSK mode,
|
||||
* mainly for receiving radiosonde transmissions
|
||||
******************************************************************************/
|
||||
class SX1278FSK
|
||||
{
|
||||
public:
|
||||
// class constructor
|
||||
SX1278FSK();
|
||||
|
||||
// Turn on SX1278 module (return 0 on sucess, 1 otherwise)
|
||||
uint8_t ON();
|
||||
|
||||
// Turn off SX1278 module
|
||||
void OFF();
|
||||
|
||||
// Read internal register
|
||||
byte readRegister(byte address);
|
||||
|
||||
// Write internal register
|
||||
void writeRegister(byte address, byte data);
|
||||
|
||||
// Clear IRQ flags
|
||||
void clearIRQFlags();
|
||||
|
||||
// Activate FSK mode (return 0 on success, 1 otherwise)
|
||||
uint8_t setFSK();
|
||||
|
||||
// Configures bitrate register (closest approximation to requested bitrate)
|
||||
uint8_t setBitrate(float bps);
|
||||
float getBitrate();
|
||||
|
||||
// Configures RX bandwidth (next largest supported bandwith if exact value not possible)
|
||||
uint8_t setRxBandwidth(float bps);
|
||||
float getRxBandwidth();
|
||||
|
||||
// Configures AFC bandwidth (next largest supported bandwith if exact value not possible)
|
||||
uint8_t setAFCBandwidth(float bps);
|
||||
float getAFCBandwidth();
|
||||
|
||||
// Configures RX frequency (closest approximation to requested frequency)
|
||||
uint8_t setFrequency(float freq);
|
||||
float getFrequency();
|
||||
|
||||
int getLNAGain();
|
||||
uint8_t setLNAGain(int gain);
|
||||
|
||||
uint8_t getRxConf();
|
||||
uint8_t setRxConf(uint8_t conf);
|
||||
|
||||
uint8_t setSyncConf(uint8_t conf, int len, const uint8_t *syncpattern);
|
||||
uint8_t getSyncConf();
|
||||
|
||||
uint8_t setPreambleDetect(uint8_t conf);
|
||||
uint8_t getPreambleDetect();
|
||||
|
||||
uint8_t setPacketConfig(uint8_t conf1, uint8_t conf2);
|
||||
uint16_t getPacketConfig();
|
||||
|
||||
// Get configured preamble length (used for TX only?)
|
||||
uint16_t getPreambleLength();
|
||||
|
||||
// Sets the preamble length.
|
||||
uint8_t setPreambleLength(uint16_t l);
|
||||
|
||||
// Gets the payload length (expected length for receive)
|
||||
int getPayloadLength();
|
||||
uint8_t setPayloadLength(int len);
|
||||
|
||||
// Get current RSSI value
|
||||
int16_t getRSSI();
|
||||
|
||||
// Get the maximum current supply by the module.
|
||||
int getMaxCurrent();
|
||||
|
||||
// Set the maximum current supply by the module.
|
||||
int8_t setMaxCurrent(uint8_t rate);
|
||||
|
||||
// Put the module in reception mode.
|
||||
//return '0' on success, '1' otherwise
|
||||
uint8_t receive();
|
||||
|
||||
// Receive a packet
|
||||
uint8_t receivePacketTimeout(uint32_t wait, byte *data);
|
||||
|
||||
|
||||
|
||||
#if 0
|
||||
//! It gets the internal temperature of the module.
|
||||
/*!
|
||||
It stores in global '_temp' variable the module temperature.
|
||||
\return '0' on success, '1' otherwise
|
||||
*/
|
||||
uint8_t getTemp();
|
||||
|
||||
//! It prints the registers related to RX via USB
|
||||
/*!
|
||||
* \return void
|
||||
*/
|
||||
void showRxRegisters();
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
extern SX1278FSK sx1278;
|
||||
|
||||
#endif
|
|
@ -0,0 +1,282 @@
|
|||
|
||||
/* DFM decoder functions */
|
||||
#include "DFM.h"
|
||||
#include "SX1278FSK.h"
|
||||
|
||||
#define DFM_DEBUG 1
|
||||
|
||||
#if DFM_DEBUG
|
||||
#define DFM_DBG(x) x
|
||||
#else
|
||||
#define DFM_DBG(x)
|
||||
#endif
|
||||
|
||||
int DFM::setup(int inverse)
|
||||
{
|
||||
#if DFM_DEBUG
|
||||
Serial.println("Setup sx1278 for DFM sonde");
|
||||
#endif
|
||||
if(sx1278.ON()!=0) {
|
||||
DFM_DBG(Serial.println("Setting SX1278 power on FAILED"));
|
||||
return 1;
|
||||
}
|
||||
if(sx1278.setFSK()!=0) {
|
||||
DFM_DBG(Serial.println("Setting FSM mode FAILED"));
|
||||
return 1;
|
||||
}
|
||||
if(sx1278.setBitrate(2500)!=0) {
|
||||
DFM_DBG(Serial.println("Setting bitrate 2500bit/s FAILED"));
|
||||
return 1;
|
||||
}
|
||||
#if DFM_DEBUG
|
||||
float br = sx1278.getBitrate();
|
||||
Serial.print("Exact bitrate is ");
|
||||
Serial.println(br);
|
||||
#endif
|
||||
|
||||
if(sx1278.setAFCBandwidth(25000)!=0) {
|
||||
DFM_DBG(Serial.println("Setting AFC bandwidth 25 kHz FAILED"));
|
||||
return 1;
|
||||
}
|
||||
if(sx1278.setRxBandwidth(12000)!=0) {
|
||||
DFM_DBG(Serial.println("Setting RX bandwidth 12kHz FAILED"));
|
||||
return 1;
|
||||
}
|
||||
// Enable auto-AFC, auto-AGC, RX Trigger by preamble
|
||||
if(sx1278.setRxConf(0x1E)!=0) {
|
||||
DFM_DBG(Serial.println("Setting RX Config FAILED"));
|
||||
return 1;
|
||||
}
|
||||
// Set autostart_RX to 01, preamble 0, SYNC detect==on, syncsize=3 (==4 byte
|
||||
//char header[] = "0110.0101 0110.0110 1010.0101 1010.1010";
|
||||
|
||||
const char *SYNC=inverse?"\x9A\x99\x5A\x55":"\x65\x66\xA5\xAA";
|
||||
if(sx1278.setSyncConf(0x53, 4, (const uint8_t *)SYNC)!=0) {
|
||||
DFM_DBG(Serial.println("Setting SYNC Config FAILED"));
|
||||
return 1;
|
||||
}
|
||||
if(sx1278.setPreambleDetect(0xA8)!=0) {
|
||||
DFM_DBG(Serial.println("Setting PreambleDetect FAILED"));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Packet config 1: fixed len, mancecer, no crc, no address filter
|
||||
// Packet config 2: packet mode, no home ctrl, no beackn, msb(packetlen)=0)
|
||||
if(sx1278.setPacketConfig(0x28, 0x40)!=0) {
|
||||
DFM_DBG(Serial.println("Setting Packet config FAILED"));
|
||||
return 1;
|
||||
}
|
||||
DFM_DBG(Serial.println("Setting SX1278 config for DFM finished\n"); Serial.println());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int DFM::setFrequency(float frequency) {
|
||||
return sx1278.setFrequency(frequency);
|
||||
}
|
||||
|
||||
#define bit(value,bitpos) ((value>>(7-bitpos))&0x01)
|
||||
// Input: str: packed data, MSB first
|
||||
void DFM::deinterleave(uint8_t *str, int L, uint8_t *block) {
|
||||
int i, j;
|
||||
for (j = 0; j < B; j++) { // L = 7 (CFG), 13 (DAT1, DAT2)
|
||||
for (i = 0; i < L; i++) {
|
||||
block[B*i+j] = bit( str[(L*j+i)/8], (L*j+i)&7 )?0:1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t DFM::bits2val(const uint8_t *bits, int len) {
|
||||
uint32_t val = 0;
|
||||
for (int j = 0; j < len; j++) {
|
||||
val |= (bits[j] << (len-1-j));
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
// Error correction for hamming code
|
||||
// returns 0: ok >0: 1 error was corrected -1: uncorrectable error
|
||||
int DFM::check(uint8_t code[8]) {
|
||||
int i, j;
|
||||
uint32_t synval = 0;
|
||||
uint8_t syndrom[4];
|
||||
int ret=0;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
syndrom[i] = 0;
|
||||
for (j = 0; j < 8; j++) {
|
||||
syndrom[i] ^= H[i][j] & code[j];
|
||||
}
|
||||
}
|
||||
synval = bits2val(syndrom, 4);
|
||||
if (synval) {
|
||||
ret = -1;
|
||||
for (j = 0; j < 8; j++) { // 1-bit-error
|
||||
if (synval == He[j]) { // reicht auf databits zu pruefen, d.h.
|
||||
ret = j+1; // (systematischer Code) He[0..3]
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else ret = 0;
|
||||
if (ret > 0) code[ret-1] ^= 0x1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Extended (8,4) Hamming code
|
||||
// Return number of corrected bits, -1 if uncorrectable error
|
||||
int DFM::hamming(uint8_t *ham, int L, uint8_t *sym) {
|
||||
int i, j;
|
||||
int ret = 0; // DFM: length L = 7 or 13
|
||||
for (i = 0; i < L; i++) { // L bytes (4bit data, 4bit parity)
|
||||
if (use_ecc) {
|
||||
int res = check(ham+8*i);
|
||||
if(ret>=0 && res>=0) ret += res; else ret=-1;
|
||||
}
|
||||
// systematic Hamming code: copy bits 0..3
|
||||
for (j = 0; j < 4; j++) {
|
||||
sym[4*i+j] = ham[8*i+j];
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
DFM::DFM() {
|
||||
}
|
||||
|
||||
void DFM::printRaw(char *label, int len, int ret, uint8_t *data)
|
||||
{
|
||||
Serial.print(label); Serial.print("(");
|
||||
Serial.print(ret);
|
||||
Serial.print("):");
|
||||
int i;
|
||||
for(i=0; i<len/2; i++) {
|
||||
char str[10];
|
||||
snprintf(str, 10, "%02X", data[i]);
|
||||
Serial.print(str);
|
||||
}
|
||||
Serial.print(data[i]&0x0F, HEX);
|
||||
Serial.print(" ");
|
||||
}
|
||||
|
||||
int DFM::decodeCFG(uint8_t *cfg)
|
||||
{
|
||||
static int lowid, highid, idgood=0, type=0;
|
||||
if((cfg[0]>>4)==0x06 && type==0) { // DFM-6 ID
|
||||
lowid = ((cfg[0]&0x0F)<<20) | (cfg[1]<<12) | (cfg[2]<<4) | (cfg[3]&0x0f);
|
||||
Serial.print("DFM-06 ID: "); Serial.print(lowid, HEX);
|
||||
}
|
||||
if((cfg[0]>>4)==0x0A) { // DMF-9 ID
|
||||
type=9;
|
||||
if(cfg[3]==1) {
|
||||
lowid = (cfg[1]<<8) | cfg[2];
|
||||
idgood |= 1;
|
||||
} else {
|
||||
highid = (cfg[1]<<8) | cfg[2];
|
||||
idgood |= 2;
|
||||
}
|
||||
if(idgood==3) {
|
||||
uint32_t dfmid = (highid<<16) | lowid;
|
||||
Serial.print("DFM-09 ID: "); Serial.print(dfmid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DFM::decodeDAT(uint8_t *dat)
|
||||
{
|
||||
Serial.print(" DAT["); Serial.print(dat[6]); Serial.print("]: ");
|
||||
switch(dat[6]) {
|
||||
case 0:
|
||||
Serial.print("Packet counter: "); Serial.print(dat[3]);
|
||||
break;
|
||||
case 1:
|
||||
{
|
||||
int val = (((uint16_t)dat[4])<<8) + (uint16_t)dat[5];
|
||||
Serial.print("UTC-msec: "); Serial.print(val);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
{
|
||||
float lat, vh;
|
||||
lat = ((uint32_t)dat[0]<<24) + ((uint32_t)dat[1]<<16) + ((uint32_t)dat[2]<<8) + ((uint32_t)dat[3]);
|
||||
vh = (dat[4]<<8) + dat[5];
|
||||
Serial.print("GPS-lat: "); Serial.print(lat*0.0000001);
|
||||
Serial.print(", hor-V: "); Serial.print(vh*0.01);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
{
|
||||
float lon, dir;
|
||||
lon = ((uint32_t)dat[0]<<24) + ((uint32_t)dat[1]<<16) + ((uint32_t)dat[2]<<8) + (uint32_t)dat[3];
|
||||
dir = ((uint16_t)dat[4]<<8) + dat[5];
|
||||
Serial.print("GPS-lon: "); Serial.print(lon*0.0000001);
|
||||
Serial.print(", dir: "); Serial.print(dir*0.01);
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
{
|
||||
float hei, vv;
|
||||
hei = ((uint32_t)dat[0]<<24) + ((uint32_t)dat[1]<<16) + ((uint32_t)dat[2]<<8) + dat[3];
|
||||
vv = (int16_t)( (dat[4]<<8) | dat[5] );
|
||||
Serial.print("GPS-height: "); Serial.print(hei*0.01);
|
||||
Serial.print(", vv: "); Serial.print(vv*0.01);
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
{
|
||||
int y = (dat[0]<<4) + (dat[1]>>4);
|
||||
int m = dat[1]&0x0F;
|
||||
int d = dat[2]>>3;
|
||||
int h = ((dat[2]&0x07)<<2) + (dat[3]>>6);
|
||||
int mi = (dat[3]&0x3F);
|
||||
char buf[100];
|
||||
snprintf(buf, 100, "%04d-%02d-%02d %02d:%02dz", y, m, d, h, mi);
|
||||
Serial.print("Date: "); Serial.print(buf);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Serial.print("(?)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int DFM::bitsToBytes(uint8_t *bits, uint8_t *bytes, int len)
|
||||
{
|
||||
int i;
|
||||
for(i=0; i<len*4; i++) {
|
||||
//Serial.print(bits[i]?"1":"0");
|
||||
bytes[i/8] = (bytes[i/8]<<1) | (bits[i]?1:0);
|
||||
}
|
||||
bytes[(i-1)/8] &= 0x0F;
|
||||
}
|
||||
|
||||
int DFM::receiveFrame() {
|
||||
byte data[33];
|
||||
sx1278.setPayloadLength(33); // Expect 33 bytes (7+13+13 bytes
|
||||
|
||||
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
|
||||
int e = sx1278.receivePacketTimeout(1000, data);
|
||||
if(e) { return 1; } //if timeout... return 1
|
||||
|
||||
deinterleave(data, 7, hamming_conf);
|
||||
deinterleave(data+7, 13, hamming_dat1);
|
||||
deinterleave(data+20, 13, hamming_dat2);
|
||||
|
||||
int ret0 = hamming(hamming_conf, 7, block_conf);
|
||||
int ret1 = hamming(hamming_dat1, 13, block_dat1);
|
||||
int ret2 = hamming(hamming_dat2, 13, block_dat2);
|
||||
|
||||
byte byte_conf[4], byte_dat1[7], byte_dat2[7];
|
||||
bitsToBytes(block_conf, byte_conf, 7);
|
||||
bitsToBytes(block_dat1, byte_dat1, 13);
|
||||
bitsToBytes(block_dat2, byte_dat2, 13);
|
||||
|
||||
printRaw("CFG", 7, ret0, byte_conf);
|
||||
printRaw("DAT", 13, ret1, byte_dat1);
|
||||
printRaw("DAT", 13, ret2, byte_dat2);
|
||||
decodeCFG(byte_conf);
|
||||
decodeDAT(byte_dat1);
|
||||
decodeDAT(byte_dat2);
|
||||
}
|
||||
|
||||
DFM dfm = DFM();
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* DFM.h
|
||||
* Functions for decoding DFM sondes with SX127x chips
|
||||
* Copyright (C) 2019 Hansi Reiser, dl9rdz
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#ifndef DFM_h
|
||||
#define DFM_h
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <Arduino.h>
|
||||
#ifndef inttypes_h
|
||||
#include <inttypes.h>
|
||||
#endif
|
||||
|
||||
#define DFM_NORMAL 0
|
||||
#define DFM_INVERSE 1
|
||||
|
||||
/* Main class */
|
||||
class DFM
|
||||
{
|
||||
private:
|
||||
void deinterleave(uint8_t *str, int L, uint8_t *block);
|
||||
uint32_t bits2val(const uint8_t *bits, int len);
|
||||
int check(uint8_t code[8]);
|
||||
int hamming(uint8_t *ham, int L, uint8_t *sym);
|
||||
void printRaw(char *prefix, int len, int ret, uint8_t* data);
|
||||
int decodeCFG(uint8_t *cfg);
|
||||
int decodeDAT(uint8_t *dat);
|
||||
int bitsToBytes(uint8_t *bits, uint8_t *bytes, int len);
|
||||
|
||||
#define B 8
|
||||
#define S 4
|
||||
uint8_t hamming_conf[ 7*B]; // 7*8=56
|
||||
uint8_t hamming_dat1[13*B]; // 13*8=104
|
||||
uint8_t hamming_dat2[13*B];
|
||||
|
||||
uint8_t block_conf[ 7*S]; // 7*4=28
|
||||
uint8_t block_dat1[13*S]; // 13*4=52
|
||||
uint8_t block_dat2[13*S];
|
||||
|
||||
uint8_t H[4][8] = // extended Hamming(8,4) particy check matrix
|
||||
{{ 0, 1, 1, 1, 1, 0, 0, 0},
|
||||
{ 1, 0, 1, 1, 0, 1, 0, 0},
|
||||
{ 1, 1, 0, 1, 0, 0, 1, 0},
|
||||
{ 1, 1, 1, 0, 0, 0, 0, 1}};
|
||||
uint8_t He[8] = { 0x7, 0xB, 0xD, 0xE, 0x8, 0x4, 0x2, 0x1}; // Spalten von H:
|
||||
// 1-bit-error-Syndrome
|
||||
|
||||
public:
|
||||
DFM();
|
||||
int setup(int inverse);
|
||||
int setFrequency(float frequency);
|
||||
int receiveFrame();
|
||||
|
||||
int use_ecc = 1;
|
||||
};
|
||||
|
||||
extern DFM dfm;
|
||||
|
||||
#endif
|
|
@ -0,0 +1,476 @@
|
|||
|
||||
/* RS41 decoder functions */
|
||||
#include "RS41.h"
|
||||
#include "SX1278FSK.h"
|
||||
#include "rsc.h"
|
||||
#include "Sonde.h"
|
||||
|
||||
#define RS41_DEBUG 1
|
||||
|
||||
#if RS41_DEBUG
|
||||
#define RS41_DBG(x) x
|
||||
#else
|
||||
#define RS41_DBG(x)
|
||||
#endif
|
||||
|
||||
static uint16_t CRCTAB[256];
|
||||
|
||||
#define X2C_DIVR(a, b) ((b) != 0.0f ? (a)/(b) : (a))
|
||||
#define X2C_DIVL(a, b) ((a)/(b))
|
||||
static uint32_t X2C_LSH(uint32_t a, int32_t length, int32_t n)
|
||||
{
|
||||
uint32_t m;
|
||||
|
||||
m = 0;
|
||||
m = (length == 32) ? 0xFFFFFFFFl : (1 << length) - 1;
|
||||
if (n > 0) {
|
||||
if (n >= (int32_t)length)
|
||||
return 0;
|
||||
return (a << n) & m;
|
||||
}
|
||||
|
||||
if (n <= (int32_t)-length)
|
||||
return 0;
|
||||
return (a >> -n) & m;
|
||||
}
|
||||
|
||||
static double atang2(double x, double y)
|
||||
{
|
||||
double w;
|
||||
if (fabs(x)>fabs(y)) {
|
||||
w = (double)atan((float)(X2C_DIVL(y,x)));
|
||||
if (x<0.0) {
|
||||
if (y>0.0) w = 3.1415926535898+w;
|
||||
else w = w-3.1415926535898;
|
||||
}
|
||||
}
|
||||
else if (y!=0.0) {
|
||||
w = (double)(1.5707963267949f-atan((float)(X2C_DIVL(x,
|
||||
y))));
|
||||
if (y<0.0) w = w-3.1415926535898;
|
||||
}
|
||||
else w = 0.0;
|
||||
return w;
|
||||
} /* end atang2() */
|
||||
|
||||
|
||||
static void Gencrctab(void)
|
||||
{
|
||||
uint16_t j;
|
||||
uint16_t i;
|
||||
uint16_t crc;
|
||||
for (i = 0U; i<=255U; i++) {
|
||||
crc = (uint16_t)(i*256U);
|
||||
for (j = 0U; j<=7U; j++) {
|
||||
if ((0x8000U & crc)) crc = X2C_LSH(crc,16,1)^0x1021U;
|
||||
else crc = X2C_LSH(crc,16,1);
|
||||
} /* end for */
|
||||
CRCTAB[i] = X2C_LSH(crc,16,-8)|X2C_LSH(crc,16,8);
|
||||
} /* end for */
|
||||
} /* end Gencrctab() */
|
||||
|
||||
int RS41::setup()
|
||||
{
|
||||
#if RS41_DEBUG
|
||||
Serial.println("Setup sx1278 for RS41 sonde");
|
||||
#endif
|
||||
Gencrctab();
|
||||
initrsc();
|
||||
|
||||
if(sx1278.ON()!=0) {
|
||||
RS41_DBG(Serial.println("Setting SX1278 power on FAILED"));
|
||||
return 1;
|
||||
}
|
||||
if(sx1278.setFSK()!=0) {
|
||||
RS41_DBG(Serial.println("Setting FSM mode FAILED"));
|
||||
return 1;
|
||||
}
|
||||
if(sx1278.setBitrate(4800)!=0) {
|
||||
RS41_DBG(Serial.println("Setting bitrate 4800bit/s FAILED"));
|
||||
return 1;
|
||||
}
|
||||
#if RS41_DEBUG
|
||||
float br = sx1278.getBitrate();
|
||||
Serial.print("Exact bitrate is ");
|
||||
Serial.println(br);
|
||||
#endif
|
||||
|
||||
if(sx1278.setAFCBandwidth(25000)!=0) {
|
||||
RS41_DBG(Serial.println("Setting AFC bandwidth 25 kHz FAILED"));
|
||||
return 1;
|
||||
}
|
||||
if(sx1278.setRxBandwidth(12000)!=0) {
|
||||
RS41_DBG(Serial.println("Setting RX bandwidth 12kHz FAILED"));
|
||||
return 1;
|
||||
}
|
||||
// Enable auto-AFC, auto-AGC, RX Trigger by preamble
|
||||
if(sx1278.setRxConf(0x1E)!=0) {
|
||||
RS41_DBG(Serial.println("Setting RX Config FAILED"));
|
||||
return 1;
|
||||
}
|
||||
// Set autostart_RX to 01, preamble 0, SYNC detect==on, syncsize=3 (==4 byte
|
||||
//char header[] = "0110.0101 0110.0110 1010.0101 1010.1010";
|
||||
|
||||
//const char *SYNC="\x10\xB6\xCA\x11\x22\x96\x12\xF8";
|
||||
const char *SYNC="\x08\x6D\x53\x88\x44\x69\x48\x1F";
|
||||
if(sx1278.setSyncConf(0x57, 8, (const uint8_t *)SYNC)!=0) {
|
||||
RS41_DBG(Serial.println("Setting SYNC Config FAILED"));
|
||||
return 1;
|
||||
}
|
||||
if(sx1278.setPreambleDetect(0xA8)!=0) {
|
||||
RS41_DBG(Serial.println("Setting PreambleDetect FAILED"));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Packet config 1: fixed len, no mancecer, no crc, no address filter
|
||||
// Packet config 2: packet mode, no home ctrl, no beackn, msb(packetlen)=0)
|
||||
if(sx1278.setPacketConfig(0x08, 0x40)!=0) {
|
||||
RS41_DBG(Serial.println("Setting Packet config FAILED"));
|
||||
return 1;
|
||||
}
|
||||
RS41_DBG(Serial.println("Setting SX1278 config for RS41 finished\n"); Serial.println());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RS41::setFrequency(float frequency) {
|
||||
return sx1278.setFrequency(frequency);
|
||||
}
|
||||
|
||||
uint32_t RS41::bits2val(const uint8_t *bits, int len) {
|
||||
uint32_t val = 0;
|
||||
for (int j = 0; j < len; j++) {
|
||||
val |= (bits[j] << (len-1-j));
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
RS41::RS41() {
|
||||
}
|
||||
|
||||
/* RS41 reed solomon decoder, from dxlAPRS
|
||||
*/
|
||||
static int32_t reedsolomon41(byte buf[], uint32_t buf_len, uint32_t len2)
|
||||
{
|
||||
uint32_t i;
|
||||
int32_t res1;
|
||||
/*tb1, */
|
||||
int32_t res;
|
||||
char b1[256];
|
||||
char b[256];
|
||||
uint32_t eraspos[24];
|
||||
uint32_t tmp;
|
||||
for (i = 0UL; i<=255UL; i++) {
|
||||
b[i] = 0;
|
||||
b1[i] = 0;
|
||||
} /* end for */
|
||||
tmp = len2;
|
||||
i = 0UL;
|
||||
if (i<=tmp) for (;; i++) {
|
||||
b[230UL-i] = buf[i*2UL+56UL];
|
||||
b1[230UL-i] = buf[i*2UL+57UL];
|
||||
if (i==tmp) break;
|
||||
} /* end for */
|
||||
for (i = 0UL; i<=23UL; i++) {
|
||||
b[254UL-i] = buf[i+8UL];
|
||||
b1[254UL-i] = buf[i+32UL];
|
||||
} /* end for */
|
||||
res = decodersc(b, eraspos, 0);
|
||||
res1 = decodersc(b1, eraspos, 0);
|
||||
if (res>0L && res<=12L) {
|
||||
tmp = len2;
|
||||
i = 0UL;
|
||||
if (i<=tmp) for (;; i++) {
|
||||
buf[i*2UL+56UL] = b[230UL-i];
|
||||
if (i==tmp) break;
|
||||
} /* end for */
|
||||
for (i = 0UL; i<=23UL; i++) {
|
||||
buf[i+8UL] = b[254UL-i];
|
||||
} /* end for */
|
||||
}
|
||||
if (res1>0L && res1<=12L) {
|
||||
tmp = len2;
|
||||
i = 0UL;
|
||||
if (i<=tmp) for (;; i++) {
|
||||
buf[i*2UL+57UL] = b1[230UL-i];
|
||||
if (i==tmp) break;
|
||||
} /* end for */
|
||||
for (i = 0UL; i<=23UL; i++) {
|
||||
buf[i+32UL] = b1[254UL-i];
|
||||
} /* end for */
|
||||
}
|
||||
if (res<0L || res1<0L) return -1L;
|
||||
else return res+res1;
|
||||
return 0;
|
||||
} /* end reedsolomon41() */
|
||||
|
||||
|
||||
|
||||
|
||||
static char crcrs(const byte frame[], uint32_t frame_len,
|
||||
int32_t from, int32_t to)
|
||||
{
|
||||
uint16_t crc;
|
||||
int32_t i;
|
||||
int32_t tmp;
|
||||
crc = 0xFFFFU;
|
||||
tmp = to-3L;
|
||||
i = from;
|
||||
if (i<=tmp) for (;; i++) {
|
||||
crc = X2C_LSH(crc,16,-8)^CRCTAB[(uint32_t)((crc^(uint16_t)(uint8_t)frame[i])&0xFFU)];
|
||||
if (i==tmp) break;
|
||||
} /* end for */
|
||||
return frame[to-1L]==(char)crc && frame[to-2L]==(char)X2C_LSH(crc,
|
||||
16,-8);
|
||||
} /* end crcrs() */
|
||||
|
||||
static int32_t getint32(const byte frame[], uint32_t frame_len,
|
||||
uint32_t p)
|
||||
{
|
||||
uint32_t n;
|
||||
uint32_t i;
|
||||
n = 0UL;
|
||||
for (i = 3UL;; i--) {
|
||||
n = n*256UL+(uint32_t)(uint8_t)frame[p+i];
|
||||
if (i==0UL) break;
|
||||
} /* end for */
|
||||
return (int32_t)n;
|
||||
} /* end getint32() */
|
||||
|
||||
|
||||
static uint32_t getcard16(const byte frame[], uint32_t frame_len,
|
||||
uint32_t p)
|
||||
{
|
||||
return (uint32_t)(uint8_t)frame[p]+256UL*(uint32_t)(uint8_t)
|
||||
frame[p+1UL];
|
||||
} /* end getcard16() */
|
||||
|
||||
|
||||
static int32_t getint16(const byte frame[], uint32_t frame_len,
|
||||
uint32_t p)
|
||||
{
|
||||
uint32_t n;
|
||||
n = (uint32_t)(uint8_t)frame[p]+256UL*(uint32_t)(uint8_t)
|
||||
frame[p+1UL];
|
||||
if (n>=32768UL) return (int32_t)(n-65536UL);
|
||||
return (int32_t)n;
|
||||
} /* end getint16() */
|
||||
|
||||
static void wgs84r(double x, double y, double z,
|
||||
double * lat, double * long0,
|
||||
double * heig)
|
||||
{
|
||||
double sl;
|
||||
double ct;
|
||||
double st;
|
||||
double t;
|
||||
double rh;
|
||||
double xh;
|
||||
double h;
|
||||
h = x*x+y*y;
|
||||
if (h>0.0) {
|
||||
rh = (double)sqrt((float)h);
|
||||
xh = x+rh;
|
||||
*long0 = atang2(xh, y)*2.0;
|
||||
if (*long0>3.1415926535898) *long0 = *long0-6.2831853071796;
|
||||
t = (double)atan((float)(X2C_DIVL(z*1.003364089821,
|
||||
rh)));
|
||||
st = (double)sin((float)t);
|
||||
ct = (double)cos((float)t);
|
||||
*lat = (double)atan((float)
|
||||
(X2C_DIVL(z+4.2841311513312E+4*st*st*st,
|
||||
rh-4.269767270718E+4*ct*ct*ct)));
|
||||
sl = (double)sin((float)*lat);
|
||||
*heig = X2C_DIVL(rh,(double)cos((float)*lat))-(double)(X2C_DIVR(6.378137E+6f,
|
||||
sqrt((float)(1.0-6.6943799901413E-3*sl*sl))));
|
||||
}
|
||||
else {
|
||||
*lat = 0.0;
|
||||
*long0 = 0.0;
|
||||
*heig = 0.0;
|
||||
}
|
||||
/* lat:=atan(z/(rh*(1.0 - E2))); */
|
||||
/* heig:=sqrt(h + z*z) - EARTHA; */
|
||||
} /* end wgs84r() */
|
||||
static void posrs41(const byte b[], uint32_t b_len, uint32_t p)
|
||||
{
|
||||
double dir;
|
||||
double vu;
|
||||
double ve;
|
||||
double vn;
|
||||
double vz;
|
||||
double vy;
|
||||
double vx;
|
||||
double heig;
|
||||
double long0;
|
||||
double lat;
|
||||
double z;
|
||||
double y;
|
||||
double x;
|
||||
x = (double)getint32(b, b_len, p)*0.01;
|
||||
y = (double)getint32(b, b_len, p+4UL)*0.01;
|
||||
z = (double)getint32(b, b_len, p+8UL)*0.01;
|
||||
wgs84r(x, y, z, &lat, &long0, &heig);
|
||||
Serial.print(" ");
|
||||
si.lat = (float)(X2C_DIVL(lat,1.7453292519943E-2));
|
||||
Serial.print(si.lat);
|
||||
Serial.print(" ");
|
||||
si.lon = (float)(X2C_DIVL(long0,1.7453292519943E-2));
|
||||
Serial.print(si.lon);
|
||||
if (heig<1.E+5 && heig>(-1.E+5)) {
|
||||
Serial.print(" ");
|
||||
Serial.print((uint32_t)heig);
|
||||
Serial.print("m");
|
||||
}
|
||||
/*speed */
|
||||
vx = (double)getint16(b, b_len, p+12UL)*0.01;
|
||||
vy = (double)getint16(b, b_len, p+14UL)*0.01;
|
||||
vz = (double)getint16(b, b_len, p+16UL)*0.01;
|
||||
vn = (-(vx*(double)sin((float)lat)*(double)
|
||||
cos((float)long0))-vy*(double)
|
||||
sin((float)lat)*(double)sin((float)
|
||||
long0))+vz*(double)cos((float)lat);
|
||||
ve = -(vx*(double)sin((float)long0))+vy*(double)
|
||||
cos((float)long0);
|
||||
vu = vx*(double)cos((float)lat)*(double)
|
||||
cos((float)long0)+vy*(double)
|
||||
cos((float)lat)*(double)sin((float)
|
||||
long0)+vz*(double)sin((float)lat);
|
||||
dir = X2C_DIVL(atang2(vn, ve),1.7453292519943E-2);
|
||||
if (dir<0.0) dir = 360.0+dir;
|
||||
Serial.print(" ");
|
||||
si.hs = sqrt((float)(vn*vn+ve*ve))*3.6f;
|
||||
Serial.print(si.hs);
|
||||
Serial.print("km/h ");
|
||||
Serial.print(dir);
|
||||
Serial.print("deg ");
|
||||
Serial.print((float)vu);
|
||||
si.vs = vu;
|
||||
Serial.print("m/s ");
|
||||
Serial.print(getcard16(b, b_len, p+18UL)&255UL);
|
||||
Serial.print("Sats");
|
||||
si.hei = heig;
|
||||
si.validPos = true;
|
||||
} /* end posrs41() */
|
||||
|
||||
|
||||
|
||||
|
||||
void RS41::decode41(byte *data, int MAXLEN)
|
||||
{
|
||||
char buf[128];
|
||||
|
||||
int32_t corr = reedsolomon41(data, 560, 131); // try short frame first
|
||||
if(corr<0) {
|
||||
corr = reedsolomon41(data, 560, 230); // try long frame
|
||||
}
|
||||
Serial.print("RS result:");
|
||||
Serial.print(corr);
|
||||
Serial.println();
|
||||
int p = 57; // 8 byte header, 48 byte RS
|
||||
while(p<MAXLEN) { /* why 555? */
|
||||
uint8_t typ = data[p++];
|
||||
uint32_t len = data[p++]+2UL;
|
||||
if(p+len>MAXLEN) break;
|
||||
|
||||
#if 1
|
||||
// DEBUG OUTPUT
|
||||
Serial.print("@");
|
||||
Serial.print(p-2);
|
||||
Serial.print(": ID:");
|
||||
Serial.print(typ, HEX);
|
||||
Serial.print(", len=");
|
||||
Serial.print(len);
|
||||
Serial.print(": ");
|
||||
for(int i=0; i<len-1; i++) {
|
||||
char buf[3];
|
||||
snprintf(buf, 4, "%02X|", data[p+i]);
|
||||
Serial.print(buf);
|
||||
}
|
||||
#endif
|
||||
// check CRC
|
||||
if(!crcrs(data, 560, p, p+len)) {
|
||||
Serial.println("###CRC ERROR###");
|
||||
} else {
|
||||
switch(typ) {
|
||||
case 'y': // name
|
||||
{
|
||||
Serial.print("#");
|
||||
uint16_t fnr = data[p]+(data[p+1]<<8);
|
||||
Serial.print(fnr);
|
||||
Serial.print("; RS41 ID ");
|
||||
snprintf(buf, 10, "%.8s ", data+p+2);
|
||||
Serial.print(buf);
|
||||
strcpy(si.type, "RS41");
|
||||
strncpy(si.id, (const char *)(data+p+2), 8);
|
||||
si.id[8]=0;
|
||||
si.validID=true;
|
||||
}
|
||||
// TODO: some more data
|
||||
break;
|
||||
case '|': // date
|
||||
break;
|
||||
case '{': // pos
|
||||
posrs41(data+p, len, 0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}}
|
||||
p += len;
|
||||
Serial.println();
|
||||
}
|
||||
}
|
||||
void RS41::printRaw(uint8_t *data, int len)
|
||||
{
|
||||
char buf[3];
|
||||
int i;
|
||||
for(i=0; i<len; i++) {
|
||||
snprintf(buf, 3, "%02X", data[i]);
|
||||
Serial.print(buf);
|
||||
}
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
int RS41::bitsToBytes(uint8_t *bits, uint8_t *bytes, int len)
|
||||
{
|
||||
int i;
|
||||
for(i=0; i<len*4; i++) {
|
||||
bytes[i/8] = (bytes[i/8]<<1) | (bits[i]?1:0);
|
||||
}
|
||||
bytes[(i-1)/8] &= 0x0F;
|
||||
}
|
||||
|
||||
static unsigned char lookup[16] = {
|
||||
0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe,
|
||||
0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf, };
|
||||
|
||||
uint8_t reverse(uint8_t n) {
|
||||
return (lookup[n&0x0f] << 4) | lookup[n>>4];
|
||||
}
|
||||
|
||||
|
||||
static uint8_t scramble[64] = {150U,131U,62U,81U,177U,73U,8U,152U,50U,5U,89U,
|
||||
14U,249U,68U,198U,38U,33U,96U,194U,234U,121U,93U,109U,161U,
|
||||
84U,105U,71U,12U,220U,232U,92U,241U,247U,118U,130U,127U,7U,
|
||||
153U,162U,44U,147U,124U,48U,99U,245U,16U,46U,97U,208U,188U,
|
||||
180U,182U,6U,170U,244U,35U,120U,110U,59U,174U,191U,123U,76U,
|
||||
193U};
|
||||
|
||||
static byte data[800];
|
||||
|
||||
#define MAXLEN (320)
|
||||
int RS41::receiveFrame() {
|
||||
//sx1278.setPayloadLength(518-8); // Expect 320-8 bytes or 518-8 bytes (8 byte header)
|
||||
sx1278.setPayloadLength(MAXLEN-8); // Expect 320-8 bytes or 518-8 bytes (8 byte header)
|
||||
|
||||
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
|
||||
int e = sx1278.receivePacketTimeout(3000, data+8);
|
||||
if(e) { Serial.println("TIMEOUT"); return 1; } //if timeout... return 1
|
||||
|
||||
for(int i=0; i<MAXLEN; i++) { data[i] = reverse(data[i]); }
|
||||
//printRaw(data, MAXLEN);
|
||||
for(int i=0; i<MAXLEN; i++) { data[i] = data[i] ^ scramble[i&0x3F]; }
|
||||
//printRaw(data, MAXLEN);
|
||||
decode41(data, MAXLEN);
|
||||
}
|
||||
|
||||
RS41 rs41 = RS41();
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* RS41.h
|
||||
* Functions for decoding RS41 sondes with SX127x chips
|
||||
* Copyright (C) 2019 Hansi Reiser, dl9rdz
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#ifndef RS41_h
|
||||
#define RS41_h
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <Arduino.h>
|
||||
#ifndef inttypes_h
|
||||
#include <inttypes.h>
|
||||
#endif
|
||||
|
||||
/* Main class */
|
||||
class RS41
|
||||
{
|
||||
private:
|
||||
uint32_t bits2val(const uint8_t *bits, int len);
|
||||
void printRaw(uint8_t *data, int len);
|
||||
int bitsToBytes(uint8_t *bits, uint8_t *bytes, int len);
|
||||
void decode41(byte *data, int maxlen);
|
||||
|
||||
#define B 8
|
||||
#define S 4
|
||||
uint8_t hamming_conf[ 7*B]; // 7*8=56
|
||||
uint8_t hamming_dat1[13*B]; // 13*8=104
|
||||
uint8_t hamming_dat2[13*B];
|
||||
|
||||
uint8_t block_conf[ 7*S]; // 7*4=28
|
||||
uint8_t block_dat1[13*S]; // 13*4=52
|
||||
uint8_t block_dat2[13*S];
|
||||
|
||||
uint8_t H[4][8] = // extended Hamming(8,4) particy check matrix
|
||||
{{ 0, 1, 1, 1, 1, 0, 0, 0},
|
||||
{ 1, 0, 1, 1, 0, 1, 0, 0},
|
||||
{ 1, 1, 0, 1, 0, 0, 1, 0},
|
||||
{ 1, 1, 1, 0, 0, 0, 0, 1}};
|
||||
uint8_t He[8] = { 0x7, 0xB, 0xD, 0xE, 0x8, 0x4, 0x2, 0x1}; // Spalten von H:
|
||||
// 1-bit-error-Syndrome
|
||||
|
||||
public:
|
||||
RS41();
|
||||
int setup();
|
||||
int setFrequency(float frequency);
|
||||
int receiveFrame();
|
||||
|
||||
int use_ecc = 1;
|
||||
};
|
||||
|
||||
extern RS41 rs41;
|
||||
|
||||
#endif
|
|
@ -0,0 +1,85 @@
|
|||
#include <U8x8lib.h>
|
||||
#include <U8g2lib.h>
|
||||
|
||||
#include "Sonde.h"
|
||||
|
||||
extern U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8;
|
||||
|
||||
SondeInfo si = { "RS41", 403.450, "P1234567", true, 48.1234, 14.9876, 543, 3.97, -0.5, true, 120 };
|
||||
|
||||
static unsigned char kmh_tiles[] U8X8_PROGMEM = {
|
||||
0x1F, 0x04, 0x0A, 0x11, 0x00, 0x1F, 0x02, 0x04, 0x42, 0x3F, 0x10, 0x08, 0xFC, 0x22, 0x20, 0xF8
|
||||
};
|
||||
static unsigned char ms_tiles[] U8X8_PROGMEM = {
|
||||
0x1F, 0x02, 0x04, 0x02, 0x1F, 0x40, 0x20, 0x10, 0x08, 0x04, 0x12, 0xA4, 0xA4, 0xA4, 0x40, 0x00
|
||||
};
|
||||
|
||||
void Sonde::updateDisplayPos() {
|
||||
char buf[16];
|
||||
u8x8.setFont(u8x8_font_7x14_1x2_r);
|
||||
if(si.validPos) {
|
||||
snprintf(buf, 16, "%2.5f", si.lat);
|
||||
u8x8.drawString(0,2,buf);
|
||||
snprintf(buf, 16, "%2.5f", si.lon);
|
||||
u8x8.drawString(0,4,buf);
|
||||
} else {
|
||||
u8x8.drawString(0,2,"<??> ");
|
||||
u8x8.drawString(0,4,"<??> ");
|
||||
}
|
||||
}
|
||||
|
||||
void Sonde::updateDisplayPos2() {
|
||||
char buf[16];
|
||||
u8x8.setFont(u8x8_font_chroma48medium8_r);
|
||||
if(!si.validPos) {
|
||||
u8x8.drawString(10,2," ");
|
||||
u8x8.drawString(10,3," ");
|
||||
u8x8.drawString(10,4," ");
|
||||
return;
|
||||
}
|
||||
snprintf(buf, 16, si.hei>999?"%5.0fm":"%3.1fm", si.hei);
|
||||
u8x8.drawString((10+6-strlen(buf)),2,buf);
|
||||
snprintf(buf, 16, si.hs>99?"%3.0f":"%2.1f", si.hs);
|
||||
u8x8.drawString((10+4-strlen(buf)),3,buf);
|
||||
snprintf(buf, 16, "%+2.1f", si.vs);
|
||||
u8x8.drawString((10+4-strlen(buf)),4,buf);
|
||||
u8x8.drawTile(14,3,2,kmh_tiles);
|
||||
u8x8.drawTile(14,4,2,ms_tiles);
|
||||
}
|
||||
|
||||
void Sonde::updateDisplayID() {
|
||||
u8x8.setFont(u8x8_font_chroma48medium8_r);
|
||||
if(si.validID) {
|
||||
u8x8.drawString(0,1, si.id);
|
||||
} else {
|
||||
u8x8.drawString(0,1, "nnnnnnnn ");
|
||||
}
|
||||
}
|
||||
|
||||
void Sonde::updateDisplayRSSI() {
|
||||
char buf[16];
|
||||
u8x8.setFont(u8x8_font_7x14_1x2_r);
|
||||
snprintf(buf, 16, "%ddB ", si.rssi);
|
||||
u8x8.drawString(0,6,buf);
|
||||
}
|
||||
|
||||
void Sonde::updateDisplayRXConfig() {
|
||||
char buf[16];
|
||||
u8x8.setFont(u8x8_font_chroma48medium8_r);
|
||||
u8x8.drawString(0,0, si.type);
|
||||
snprintf(buf, 16, "%3.3f MHz", si.freq);
|
||||
u8x8.drawString(5,0, buf);
|
||||
|
||||
}
|
||||
|
||||
void Sonde::updateDisplay()
|
||||
{
|
||||
char buf[16];
|
||||
updateDisplayRXConfig();
|
||||
updateDisplayID();
|
||||
updateDisplayPos();
|
||||
updateDisplayPos2();
|
||||
updateDisplayRSSI();
|
||||
}
|
||||
|
||||
Sonde sonde = Sonde();
|
|
@ -0,0 +1,41 @@
|
|||
|
||||
|
||||
#ifndef Sonde_h
|
||||
#define Sonde_H
|
||||
|
||||
typedef struct st_sondeinfo {
|
||||
// receiver configuration
|
||||
char type[5];
|
||||
float freq;
|
||||
// decoded ID
|
||||
char id[10];
|
||||
bool validID;
|
||||
// decoded position
|
||||
float lat;
|
||||
float lon;
|
||||
float hei;
|
||||
float vs;
|
||||
float hs;
|
||||
bool validPos;
|
||||
// RSSI from receiver
|
||||
int rssi;
|
||||
} SondeInfo;
|
||||
|
||||
extern SondeInfo si;
|
||||
|
||||
class Sonde
|
||||
{
|
||||
private:
|
||||
public:
|
||||
void updateDisplayPos();
|
||||
void updateDisplayPos2();
|
||||
void updateDisplayID();
|
||||
void updateDisplayRSSI();
|
||||
void updateDisplayRXConfig();
|
||||
void updateDisplay();
|
||||
};
|
||||
|
||||
extern Sonde sonde;
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* dxlAPRS toolchain
|
||||
*
|
||||
* Copyright (C) Christian Rabler <oe5dxl@oevsv.at>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#ifndef inttypes_h
|
||||
#include <inttypes.h>
|
||||
#endif
|
||||
|
||||
#define N 255
|
||||
#define R 24
|
||||
#define K (N-R)
|
||||
|
||||
void *init_rs_char(int symsize,int gfpoly,int fcr,int prim,int nroots,int pad);
|
||||
int decode_rs_char(void *arg,
|
||||
unsigned char *data, int *eras_pos, int no_eras);
|
||||
|
||||
void *rs;
|
||||
|
||||
void initrsc()
|
||||
{
|
||||
rs = init_rs_char( 8, 0x11d, 0, 1, R, 0);
|
||||
}
|
||||
|
||||
|
||||
int decodersc(char *data, uint32_t *eras_pos, uint32_t no_eras)
|
||||
{
|
||||
return decode_rs_char(rs, (unsigned char *)data, (int *)eras_pos, no_eras);
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* dxlAPRS toolchain
|
||||
*
|
||||
* Copyright (C) Christian Rabler <oe5dxl@oevsv.at>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
#ifndef rsc_H_
|
||||
#define rsc_H_
|
||||
|
||||
|
||||
long decodersc(char [], uint32_t [], uint32_t);
|
||||
|
||||
void initrsc(void);
|
||||
|
||||
|
||||
#endif /* rsc_H_ */
|
|
@ -0,0 +1,436 @@
|
|||
/*
|
||||
* Copyright 2016 Hannes Schmelzer, OE5HPM
|
||||
* doing several cleanups and architecture changes, no functional change yet
|
||||
*
|
||||
* General purpose Reed-Solomon decoder for 8-bit symbols or less
|
||||
* Copyright 2003 Phil Karn, KA9Q
|
||||
* May be used under the terms of the GNU Lesser General Public License (LGPL)
|
||||
*
|
||||
* The guts of the Reed-Solomon decoder, meant to be #included
|
||||
* into a function body with the following typedefs, macros and variables supplied
|
||||
* according to the code parameters:
|
||||
|
||||
* data_t - a typedef for the data symbol
|
||||
* data_t data[] - array of rs->nn data and parity symbols to be corrected in place
|
||||
* retval - an integer lvalue into which the decoder's return code is written
|
||||
* NROOTS - the number of roots in the RS code generator polynomial,
|
||||
* which is the same as the number of parity symbols in a block.
|
||||
Integer variable or literal.
|
||||
* rs->nn - the total number of symbols in a RS block. Integer variable or literal.
|
||||
* rs->pad - the number of pad symbols in a block. Integer variable or literal.
|
||||
* rs->alpha_to - The address of an array of rs->nn elements to convert Galois field
|
||||
* elements in index (log) form to polynomial form. Read only.
|
||||
* rs->index_of - The address of an array of rs->nn elements to convert Galois field
|
||||
* elements in polynomial form to index (log) form. Read only.
|
||||
* MODNN - a function to reduce its argument modulo rs->nn. May be inline or a macro.
|
||||
* rs->fcr - An integer literal or variable specifying the first consecutive root of the
|
||||
* Reed-Solomon generator polynomial. Integer variable or literal.
|
||||
* rs->prim - The primitive root of the generator poly. Integer variable or literal.
|
||||
* DEBUG - If set to 1 or more, do various internal consistency checking. Leave this
|
||||
* undefined for production code
|
||||
|
||||
* The memset(), memmove(), and memcpy() functions are used. The appropriate header
|
||||
* file declaring these functions (usually <string.h>) must be included by the calling
|
||||
* program.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct rs {
|
||||
unsigned int magic; /* struct magic */
|
||||
int mm; /* Bits per symbol */
|
||||
int nn; /* Symbols per block (= (1<<mm)-1) */
|
||||
unsigned char *alpha_to; /* log lookup table */
|
||||
unsigned char *index_of; /* Antilog lookup table */
|
||||
unsigned char *genpoly; /* Generator polynomial */
|
||||
int nroots; /*
|
||||
* Number of generator
|
||||
* roots = number of parity symbols
|
||||
*/
|
||||
int fcr; /* First consecutive root, index form */
|
||||
int prim; /* Primitive element, index form */
|
||||
int iprim; /* prim-th root of 1, index form */
|
||||
int pad; /* Padding bytes in shortened block */
|
||||
};
|
||||
|
||||
static inline int modnn(struct rs *rs,int x)
|
||||
{
|
||||
while (x >= rs->nn) {
|
||||
x -= rs->nn;
|
||||
x = (x >> rs->mm) + (x & rs->nn);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
#define MODNN(x) modnn(rs, x)
|
||||
#define MIN(a,b) ((a) < (b) ? (a) : (b))
|
||||
#define MAGIC 0xABCD6722
|
||||
|
||||
void free_rs_char(void *arg)
|
||||
{
|
||||
struct rs *rs = (struct rs *)arg;
|
||||
|
||||
if (rs == NULL)
|
||||
return;
|
||||
if (rs->magic != MAGIC)
|
||||
return;
|
||||
|
||||
if (rs->alpha_to != NULL)
|
||||
free(rs->alpha_to);
|
||||
if (rs->index_of != NULL)
|
||||
free(rs->index_of);
|
||||
if (rs->genpoly != NULL)
|
||||
free(rs->genpoly);
|
||||
free(rs);
|
||||
}
|
||||
|
||||
/* Initialize a Reed-Solomon codec
|
||||
* symsize = symbol size, bits
|
||||
* gfpoly = Field generator polynomial coefficients
|
||||
* fcr = first root of RS code generator polynomial, index form
|
||||
* prim = primitive element to generate polynomial roots
|
||||
* nroots = RS code generator polynomial degree (number of roots)
|
||||
* pad = padding bytes at front of shortened block
|
||||
*/
|
||||
void *init_rs_char(int symsize, int gfpoly, int fcr, int prim,
|
||||
int nroots, int pad)
|
||||
{
|
||||
struct rs *rs;
|
||||
|
||||
int i, j, sr,root,iprim;
|
||||
|
||||
/* Check parameter ranges */
|
||||
if (symsize < 0 || symsize > 8*sizeof(unsigned char))
|
||||
return NULL;
|
||||
if (fcr < 0 || fcr >= (1<<symsize))
|
||||
return NULL;
|
||||
if (prim <= 0 || prim >= (1<<symsize))
|
||||
return NULL;
|
||||
if (nroots < 0 || nroots >= (1<<symsize))
|
||||
return NULL;
|
||||
if (pad < 0 || pad >= ((1<<symsize) -1 - nroots))
|
||||
return NULL;
|
||||
|
||||
rs = (struct rs*)malloc(sizeof(*rs));
|
||||
if (rs == NULL) {
|
||||
printf("%s: cannot allocate memory!\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
memset(rs, 0, sizeof(*rs));
|
||||
rs->magic = MAGIC;
|
||||
|
||||
rs->mm = symsize;
|
||||
rs->nn = (1<<symsize)-1;
|
||||
rs->pad = pad;
|
||||
|
||||
rs->alpha_to = (unsigned char *)malloc(sizeof(unsigned char)*(rs->nn+1));
|
||||
if (rs->alpha_to == NULL) {
|
||||
free(rs);
|
||||
return NULL;
|
||||
}
|
||||
rs->index_of = (unsigned char *)malloc(sizeof(unsigned char)*(rs->nn+1));
|
||||
if (rs->index_of == NULL) {
|
||||
free(rs->alpha_to);
|
||||
free(rs);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Generate Galois field lookup tables */
|
||||
rs->index_of[0] = rs->nn; /* log(zero) = -inf */
|
||||
rs->alpha_to[rs->nn] = 0; /* alpha**-inf = 0 */
|
||||
sr = 1;
|
||||
for (i = 0; i < rs->nn; i++) {
|
||||
rs->index_of[sr] = i;
|
||||
rs->alpha_to[i] = sr;
|
||||
sr <<= 1;
|
||||
if (sr & (1<<symsize))
|
||||
sr ^= gfpoly;
|
||||
sr &= rs->nn;
|
||||
}
|
||||
if (sr != 1) {
|
||||
/* field generator polynomial is not primitive! */
|
||||
free(rs->alpha_to);
|
||||
free(rs->index_of);
|
||||
free(rs);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Form RS code generator polynomial from its roots */
|
||||
rs->genpoly = (unsigned char *)malloc(sizeof(unsigned char)*(nroots+1));
|
||||
if(rs->genpoly == NULL) {
|
||||
free(rs->alpha_to);
|
||||
free(rs->index_of);
|
||||
free(rs);
|
||||
return NULL;
|
||||
}
|
||||
rs->fcr = fcr;
|
||||
rs->prim = prim;
|
||||
rs->nroots = nroots;
|
||||
|
||||
/* Find prim-th root of 1, used in decoding */
|
||||
for (iprim = 1; (iprim % prim) != 0; iprim += rs->nn)
|
||||
;
|
||||
rs->iprim = iprim / prim;
|
||||
|
||||
rs->genpoly[0] = 1;
|
||||
for (i = 0, root = fcr*prim; i < nroots; i++, root += prim) {
|
||||
rs->genpoly[i+1] = 1;
|
||||
|
||||
/* Multiply rs->genpoly[] by @**(root + x) */
|
||||
for (j = i; j > 0; j--) {
|
||||
if (rs->genpoly[j] != 0)
|
||||
rs->genpoly[j] = rs->genpoly[j-1] ^ rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[j]] + root)];
|
||||
else
|
||||
rs->genpoly[j] = rs->genpoly[j-1];
|
||||
}
|
||||
/* rs->genpoly[0] can never be zero */
|
||||
rs->genpoly[0] = rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[0]] + root)];
|
||||
}
|
||||
/* convert rs->genpoly[] to index form for quicker encoding */
|
||||
for (i = 0; i <= nroots; i++)
|
||||
rs->genpoly[i] = rs->index_of[rs->genpoly[i]];
|
||||
|
||||
return rs;
|
||||
}
|
||||
|
||||
int decode_rs_char(void *arg,
|
||||
unsigned char *data, int *eras_pos, int no_eras)
|
||||
{
|
||||
struct rs *rs = (struct rs *)arg;
|
||||
|
||||
if (rs == NULL)
|
||||
return -1;
|
||||
if (rs->magic != MAGIC)
|
||||
return -1;
|
||||
|
||||
int retval;
|
||||
int deg_lambda, el, deg_omega;
|
||||
int i, j, r,k;
|
||||
|
||||
unsigned char u,q,tmp,num1,num2,den,discr_r;
|
||||
unsigned char lambda[rs->nroots+1], s[rs->nroots]; /* Err+Eras Locator poly
|
||||
* and syndrome poly */
|
||||
unsigned char b[rs->nroots+1], t[rs->nroots+1], omega[rs->nroots+1];
|
||||
unsigned char root[rs->nroots], reg[rs->nroots+1], loc[rs->nroots];
|
||||
int syn_error, count;
|
||||
|
||||
/* form the syndromes; i.e., evaluate data(x) at roots of g(x) */
|
||||
for (i = 0; i < rs->nroots; i++)
|
||||
s[i] = data[0];
|
||||
|
||||
for (j = 1; j < rs->nn-rs->pad; j++) {
|
||||
for(i=0;i<rs->nroots;i++) {
|
||||
if(s[i] == 0) {
|
||||
s[i] = data[j];
|
||||
} else {
|
||||
s[i] = data[j] ^ rs->alpha_to[MODNN(rs->index_of[s[i]] + (rs->fcr+i)*rs->prim)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Convert syndromes to index form, checking for nonzero condition */
|
||||
syn_error = 0;
|
||||
for (i = 0; i < rs->nroots; i++) {
|
||||
syn_error |= s[i];
|
||||
s[i] = rs->index_of[s[i]];
|
||||
}
|
||||
|
||||
if (!syn_error) {
|
||||
/* if syndrome is zero, data[] is a codeword and there are no
|
||||
* errors to correct. So return data[] unmodified
|
||||
*/
|
||||
count = 0;
|
||||
goto finish;
|
||||
}
|
||||
memset(&lambda[1], 0, rs->nroots*sizeof(lambda[0]));
|
||||
lambda[0] = 1;
|
||||
|
||||
if (no_eras > 0) {
|
||||
/* Init lambda to be the erasure locator polynomial */
|
||||
lambda[1] = rs->alpha_to[MODNN(rs->prim*(rs->nn-1-eras_pos[0]))];
|
||||
for (i = 1; i < no_eras; i++) {
|
||||
u = MODNN(rs->prim*(rs->nn-1-eras_pos[i]));
|
||||
for (j = i+1; j > 0; j--) {
|
||||
tmp = rs->index_of[lambda[j - 1]];
|
||||
if(tmp != rs->nn)
|
||||
lambda[j] ^= rs->alpha_to[MODNN(u + tmp)];
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG >= 1
|
||||
/* Test code that verifies the erasure locator polynomial just constructed
|
||||
Needed only for decoder debugging. */
|
||||
|
||||
/* find roots of the erasure location polynomial */
|
||||
for(i=1;i<=no_eras;i++)
|
||||
reg[i] = rs->index_of[lambda[i]];
|
||||
|
||||
count = 0;
|
||||
for (i = 1,k=rs->iprim-1; i <= rs->nn; i++,k = MODNN(k+rs->iprim)) {
|
||||
q = 1;
|
||||
for (j = 1; j <= no_eras; j++)
|
||||
if (reg[j] != rs->nn) {
|
||||
reg[j] = MODNN(reg[j] + j);
|
||||
q ^= rs->alpha_to[reg[j]];
|
||||
}
|
||||
if (q != 0)
|
||||
continue;
|
||||
/* store root and error location number indices */
|
||||
root[count] = i;
|
||||
loc[count] = k;
|
||||
count++;
|
||||
}
|
||||
if (count != no_eras) {
|
||||
printf("count = %d no_eras = %d\n lambda(x) is WRONG\n",count,no_eras);
|
||||
count = -1;
|
||||
goto finish;
|
||||
}
|
||||
#if DEBUG >= 2
|
||||
printf("\n Erasure positions as determined by roots of Eras Loc Poly:\n");
|
||||
for (i = 0; i < count; i++)
|
||||
printf("%d ", loc[i]);
|
||||
printf("\n");
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
for (i = 0; i < rs->nroots+1; i++)
|
||||
b[i] = rs->index_of[lambda[i]];
|
||||
|
||||
/*
|
||||
* Begin Berlekamp-Massey algorithm to determine error+erasure
|
||||
* locator polynomial
|
||||
*/
|
||||
r = no_eras;
|
||||
el = no_eras;
|
||||
while (++r <= rs->nroots) { /* r is the step number */
|
||||
/* Compute discrepancy at the r-th step in poly-form */
|
||||
discr_r = 0;
|
||||
for (i = 0; i < r; i++) {
|
||||
if ((lambda[i] != 0) && (s[r-i-1] != rs->nn)) {
|
||||
discr_r ^= rs->alpha_to[MODNN(rs->index_of[lambda[i]] + s[r-i-1])];
|
||||
}
|
||||
}
|
||||
discr_r = rs->index_of[discr_r]; /* Index form */
|
||||
if (discr_r == rs->nn) {
|
||||
/* 2 lines below: B(x) <-- x*B(x) */
|
||||
memmove(&b[1],b,rs->nroots*sizeof(b[0]));
|
||||
b[0] = rs->nn;
|
||||
} else {
|
||||
/* 7 lines below: T(x) <-- lambda(x) - discr_r*x*b(x) */
|
||||
t[0] = lambda[0];
|
||||
for (i = 0 ; i < rs->nroots; i++) {
|
||||
if(b[i] != rs->nn)
|
||||
t[i+1] = lambda[i+1] ^ rs->alpha_to[MODNN(discr_r + b[i])];
|
||||
else
|
||||
t[i+1] = lambda[i+1];
|
||||
}
|
||||
if (2 * el <= r + no_eras - 1) {
|
||||
el = r + no_eras - el;
|
||||
/*
|
||||
* 2 lines below: B(x) <-- inv(discr_r) *
|
||||
* lambda(x)
|
||||
*/
|
||||
for (i = 0; i <= rs->nroots; i++)
|
||||
b[i] = (lambda[i] == 0) ? rs->nn : MODNN(rs->index_of[lambda[i]] - discr_r + rs->nn);
|
||||
} else {
|
||||
/* 2 lines below: B(x) <-- x*B(x) */
|
||||
memmove(&b[1],b,rs->nroots*sizeof(b[0]));
|
||||
b[0] = rs->nn;
|
||||
}
|
||||
memcpy(lambda,t,(rs->nroots+1)*sizeof(t[0]));
|
||||
}
|
||||
}
|
||||
|
||||
/* Convert lambda to index form and compute deg(lambda(x)) */
|
||||
deg_lambda = 0;
|
||||
for (i = 0;i < rs->nroots+1; i++){
|
||||
lambda[i] = rs->index_of[lambda[i]];
|
||||
if(lambda[i] != rs->nn)
|
||||
deg_lambda = i;
|
||||
}
|
||||
/* Find roots of the error+erasure locator polynomial by Chien search */
|
||||
memcpy(®[1], &lambda[1], rs->nroots*sizeof(reg[0]));
|
||||
count = 0; /* Number of roots of lambda(x) */
|
||||
for (i = 1,k=rs->iprim-1; i <= rs->nn; i++,k = MODNN(k+rs->iprim)) {
|
||||
q = 1; /* lambda[0] is always 0 */
|
||||
for (j = deg_lambda; j > 0; j--) {
|
||||
if (reg[j] != rs->nn) {
|
||||
reg[j] = MODNN(reg[j] + j);
|
||||
q ^= rs->alpha_to[reg[j]];
|
||||
}
|
||||
}
|
||||
if (q != 0)
|
||||
continue; /* Not a root */
|
||||
/* store root (index-form) and error location number */
|
||||
#if DEBUG>=2
|
||||
printf("count %d root %d loc %d\n",count,i,k);
|
||||
#endif
|
||||
root[count] = i;
|
||||
loc[count] = k;
|
||||
/* If we've already found max possible roots,
|
||||
* abort the search to save time
|
||||
*/
|
||||
if(++count == deg_lambda)
|
||||
break;
|
||||
}
|
||||
if (deg_lambda != count) {
|
||||
/*
|
||||
* deg(lambda) unequal to number of roots => uncorrectable
|
||||
* error detected
|
||||
*/
|
||||
count = -1;
|
||||
goto finish;
|
||||
}
|
||||
/*
|
||||
* Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo
|
||||
* x**rs->nroots). in index form. Also find deg(omega).
|
||||
*/
|
||||
deg_omega = deg_lambda-1;
|
||||
for (i = 0; i <= deg_omega;i++) {
|
||||
tmp = 0;
|
||||
for (j = i; j >= 0; j--) {
|
||||
if ((s[i - j] != rs->nn) && (lambda[j] != rs->nn))
|
||||
tmp ^= rs->alpha_to[MODNN(s[i - j] + lambda[j])];
|
||||
}
|
||||
omega[i] = rs->index_of[tmp];
|
||||
}
|
||||
/*
|
||||
* Compute error values in poly-form. num1 = omega(inv(X(l))), num2 =
|
||||
* inv(X(l))**(rs->fcr-1) and den = lambda_pr(inv(X(l))) all in poly-form
|
||||
*/
|
||||
for (j = count-1; j >=0; j--) {
|
||||
num1 = 0;
|
||||
for (i = deg_omega; i >= 0; i--) {
|
||||
if (omega[i] != rs->nn)
|
||||
num1 ^= rs->alpha_to[MODNN(omega[i] + i * root[j])];
|
||||
}
|
||||
num2 = rs->alpha_to[MODNN(root[j] * (rs->fcr - 1) + rs->nn)];
|
||||
den = 0;
|
||||
|
||||
/* lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] */
|
||||
for (i = MIN(deg_lambda, rs->nroots-1) & ~1; i >= 0; i -=2) {
|
||||
if(lambda[i+1] != rs->nn)
|
||||
den ^= rs->alpha_to[MODNN(lambda[i+1] + i * root[j])];
|
||||
}
|
||||
#if DEBUG >= 1
|
||||
if (den == 0) {
|
||||
printf("\n ERROR: denominator = 0\n");
|
||||
count = -1;
|
||||
goto finish;
|
||||
}
|
||||
#endif
|
||||
/* Apply error to data */
|
||||
if (num1 != 0 && loc[j] >= rs->pad) {
|
||||
data[loc[j]-rs->pad] ^= rs->alpha_to[MODNN(rs->index_of[num1] + rs->index_of[num2] + rs->nn - rs->index_of[den])];
|
||||
}
|
||||
}
|
||||
finish:
|
||||
if(eras_pos != NULL) {
|
||||
for (i = 0; i < count; i++)
|
||||
eras_pos[i] = loc[i];
|
||||
}
|
||||
retval = count;
|
||||
|
||||
return retval;
|
||||
}
|
Ładowanie…
Reference in New Issue