mirror of
https://github.com/whowechina/geki_pico.git
synced 2024-11-28 01:10:49 +01:00
Firmware initial version
This commit is contained in:
parent
9b89bd06a5
commit
edb04529f1
4
firmware/.gitignore
vendored
Normal file
4
firmware/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
build
|
||||
pico-examples
|
||||
pico-sdk
|
||||
.vscode
|
11
firmware/CMakeLists.txt
Normal file
11
firmware/CMakeLists.txt
Normal file
@ -0,0 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.12)
|
||||
|
||||
# Pull in SDK (must set be before project)
|
||||
include(pico_sdk_import.cmake)
|
||||
|
||||
project(geki_pico C CXX ASM)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
pico_sdk_init()
|
||||
|
||||
add_subdirectory(src)
|
674
firmware/LICENSE
Normal file
674
firmware/LICENSE
Normal file
@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
73
firmware/pico_sdk_import.cmake
Normal file
73
firmware/pico_sdk_import.cmake
Normal file
@ -0,0 +1,73 @@
|
||||
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
|
||||
|
||||
# This can be dropped into an external project to help locate this SDK
|
||||
# It should be include()ed prior to project()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
|
||||
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
|
||||
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
|
||||
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
|
||||
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
|
||||
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
|
||||
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
|
||||
|
||||
if (NOT PICO_SDK_PATH)
|
||||
if (PICO_SDK_FETCH_FROM_GIT)
|
||||
include(FetchContent)
|
||||
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
|
||||
if (PICO_SDK_FETCH_FROM_GIT_PATH)
|
||||
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
|
||||
endif ()
|
||||
# GIT_SUBMODULES_RECURSE was added in 3.17
|
||||
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
|
||||
FetchContent_Declare(
|
||||
pico_sdk
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG master
|
||||
GIT_SUBMODULES_RECURSE FALSE
|
||||
)
|
||||
else ()
|
||||
FetchContent_Declare(
|
||||
pico_sdk
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG master
|
||||
)
|
||||
endif ()
|
||||
|
||||
if (NOT pico_sdk)
|
||||
message("Downloading Raspberry Pi Pico SDK")
|
||||
FetchContent_Populate(pico_sdk)
|
||||
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
|
||||
endif ()
|
||||
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
|
||||
else ()
|
||||
message(FATAL_ERROR
|
||||
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
|
||||
)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
|
||||
if (NOT EXISTS ${PICO_SDK_PATH})
|
||||
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
|
||||
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
|
||||
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
|
||||
|
||||
include(${PICO_SDK_INIT_CMAKE_FILE})
|
36
firmware/src/CMakeLists.txt
Normal file
36
firmware/src/CMakeLists.txt
Normal file
@ -0,0 +1,36 @@
|
||||
set(BTSTACK_ROOT ${PICO_SDK_PATH}/lib/btstack)
|
||||
set(LWIP_ROOT ${PICO_SDK_PATH}/lib/lwip)
|
||||
|
||||
function(make_firmware board board_def)
|
||||
pico_sdk_init()
|
||||
add_executable(${board}
|
||||
main.c light.c button.c gimbal.c sound.c wad.c vl53l0x.c save.c config.c commands.c
|
||||
cli.c usb_descriptors.c)
|
||||
target_compile_definitions(${board} PUBLIC ${board_def})
|
||||
pico_enable_stdio_usb(${board} 1)
|
||||
pico_enable_stdio_uart(${board} 0)
|
||||
|
||||
pico_generate_pio_header(${board} ${CMAKE_CURRENT_LIST_DIR}/ws2812.pio)
|
||||
|
||||
target_compile_options(${board} PRIVATE -Wall -Werror -Wfatal-errors -O3)
|
||||
target_include_directories(${board} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
|
||||
target_include_directories(${board} PRIVATE
|
||||
${BTSTACK_ROOT}/src
|
||||
${LWIP_ROOT}/src/include)
|
||||
|
||||
target_link_libraries(${board} PRIVATE
|
||||
pico_multicore pico_stdlib hardware_pio hardware_pwm hardware_flash
|
||||
hardware_adc hardware_i2c hardware_watchdog
|
||||
tinyusb_device tinyusb_board)
|
||||
|
||||
pico_add_extra_outputs(${board})
|
||||
|
||||
add_custom_command(TARGET ${board} PRE_BUILD
|
||||
COMMAND touch ${CMAKE_CURRENT_SOURCE_DIR}/cli.c)
|
||||
|
||||
add_custom_command(TARGET ${board} POST_BUILD
|
||||
COMMAND cp ${board}.uf2 ${CMAKE_CURRENT_LIST_DIR}/..)
|
||||
endfunction()
|
||||
|
||||
make_firmware(geki_pico BOARD_GEKI_PICO)
|
||||
|
24
firmware/src/board_defs.h
Normal file
24
firmware/src/board_defs.h
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Geki Controller Board Definitions
|
||||
* WHowe <github.com/whowechina>
|
||||
*/
|
||||
|
||||
#if defined BOARD_GEKI_PICO
|
||||
|
||||
#define RGB_PIN 16
|
||||
|
||||
#define RGB_ORDER GRB // or RGB
|
||||
|
||||
#define BUTTON_DEF { 12, 11, 10, 5, 4, 3, 13, 2 }
|
||||
#define SOUND_DEF { 8, 6 }
|
||||
#define WAD_DEF { i2c1, i2c0 }
|
||||
#define WAD_GPIO_DEF { 18, 19, 0, 1 }
|
||||
|
||||
#define AXIS_MUX_PIN_A 21
|
||||
#define AXIS_MUX_PIN_B 20
|
||||
#define ADC_CHANNEL 0
|
||||
|
||||
#define NKRO_KEYMAP "awsdjikl123"
|
||||
#else
|
||||
|
||||
#endif
|
76
firmware/src/button.c
Normal file
76
firmware/src/button.c
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Controller Buttons
|
||||
* WHowe <github.com/whowechina>
|
||||
*
|
||||
*/
|
||||
|
||||
#include "button.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "hardware/gpio.h"
|
||||
#include "hardware/timer.h"
|
||||
#include "hardware/pwm.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "board_defs.h"
|
||||
|
||||
static const uint8_t button_gpio[] = BUTTON_DEF;
|
||||
|
||||
#define BUTTON_NUM (sizeof(button_gpio))
|
||||
|
||||
static bool sw_val[BUTTON_NUM]; /* true if pressed */
|
||||
static uint64_t sw_freeze_time[BUTTON_NUM];
|
||||
|
||||
void button_init()
|
||||
{
|
||||
for (int i = 0; i < BUTTON_NUM; i++)
|
||||
{
|
||||
sw_val[i] = false;
|
||||
sw_freeze_time[i] = 0;
|
||||
int8_t gpio = button_gpio[i];
|
||||
gpio_init(gpio);
|
||||
gpio_set_function(gpio, GPIO_FUNC_SIO);
|
||||
gpio_set_dir(gpio, GPIO_IN);
|
||||
gpio_pull_up(gpio);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t button_num()
|
||||
{
|
||||
return BUTTON_NUM;
|
||||
}
|
||||
|
||||
static uint16_t button_reading;
|
||||
|
||||
/* If a switch flips, it freezes for a while */
|
||||
#define DEBOUNCE_FREEZE_TIME_US 3000
|
||||
void button_update()
|
||||
{
|
||||
uint64_t now = time_us_64();
|
||||
uint16_t buttons = 0;
|
||||
|
||||
for (int i = BUTTON_NUM - 1; i >= 0; i--) {
|
||||
bool sw_pressed = !gpio_get(button_gpio[i]);
|
||||
|
||||
if (now >= sw_freeze_time[i]) {
|
||||
if (sw_pressed != sw_val[i]) {
|
||||
sw_val[i] = sw_pressed;
|
||||
sw_freeze_time[i] = now + DEBOUNCE_FREEZE_TIME_US;
|
||||
}
|
||||
}
|
||||
|
||||
buttons <<= 1;
|
||||
if (sw_val[i]) {
|
||||
buttons |= 1;
|
||||
}
|
||||
}
|
||||
|
||||
button_reading = buttons;
|
||||
}
|
||||
|
||||
uint16_t button_read()
|
||||
{
|
||||
return button_reading;
|
||||
}
|
18
firmware/src/button.h
Normal file
18
firmware/src/button.h
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Controller Buttons
|
||||
* WHowe <github.com/whowechina>
|
||||
*/
|
||||
|
||||
#ifndef BUTTONS_H
|
||||
#define BUTTONS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "hardware/flash.h"
|
||||
|
||||
void button_init();
|
||||
uint8_t button_num();
|
||||
void button_update();
|
||||
uint16_t button_read();
|
||||
|
||||
#endif
|
218
firmware/src/cli.c
Normal file
218
firmware/src/cli.c
Normal file
@ -0,0 +1,218 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "pico/stdio.h"
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/bootrom.h"
|
||||
#include "cli.h"
|
||||
#include "save.h"
|
||||
|
||||
#define MAX_COMMANDS 32
|
||||
#define MAX_PARAMETERS 6
|
||||
#define MAX_PARAMETER_LENGTH 20
|
||||
|
||||
const char *cli_prompt = "cli>";
|
||||
const char *cli_logo = "CLI";
|
||||
|
||||
static const char *commands[MAX_COMMANDS];
|
||||
static const char *helps[MAX_COMMANDS];
|
||||
static cmd_handler_t handlers[MAX_COMMANDS];
|
||||
static int max_cmd_len = 0;
|
||||
|
||||
static int num_commands = 0;
|
||||
|
||||
void cli_register(const char *cmd, cmd_handler_t handler, const char *help)
|
||||
{
|
||||
if (num_commands < MAX_COMMANDS) {
|
||||
commands[num_commands] = cmd;
|
||||
handlers[num_commands] = handler;
|
||||
helps[num_commands] = help;
|
||||
num_commands++;
|
||||
if (strlen(cmd) > max_cmd_len) {
|
||||
max_cmd_len = strlen(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return -1 if not matched, return -2 if ambiguous
|
||||
int cli_match_prefix(const char *str[], int num, const char *prefix)
|
||||
{
|
||||
int match = -1;
|
||||
bool found = false;
|
||||
|
||||
for (int i = 0; (i < num) && str[i]; i++) {
|
||||
if (strncasecmp(str[i], prefix, strlen(prefix)) == 0) {
|
||||
if (found) {
|
||||
return -2;
|
||||
}
|
||||
found = true;
|
||||
match = i;
|
||||
}
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
const char *built_time = __DATE__ " " __TIME__;
|
||||
static void handle_help(int argc, char *argv[])
|
||||
{
|
||||
printf("%s", cli_logo);
|
||||
printf("\tSN: %016llx\n", board_id_64());
|
||||
printf("\tBuilt: %s\n\n", built_time);
|
||||
printf("Available commands:\n");
|
||||
for (int i = 0; i < num_commands; i++) {
|
||||
printf("%*s: %s\n", max_cmd_len + 2, commands[i], helps[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static int fps[2];
|
||||
void cli_fps_count(int core)
|
||||
{
|
||||
static uint32_t last[2] = {0};
|
||||
static int counter[2] = {0};
|
||||
|
||||
counter[core]++;
|
||||
|
||||
uint32_t now = time_us_32();
|
||||
if (now - last[core] < 1000000) {
|
||||
return;
|
||||
}
|
||||
last[core] = now;
|
||||
fps[core] = counter[core];
|
||||
counter[core] = 0;
|
||||
}
|
||||
|
||||
static void handle_fps(int argc, char *argv[])
|
||||
{
|
||||
printf("FPS: core 0: %d, core 1: %d\n", fps[0], fps[1]);
|
||||
}
|
||||
static void handle_update(int argc, char *argv[])
|
||||
{
|
||||
printf("Boot into update mode.\n");
|
||||
fflush(stdout);
|
||||
sleep_ms(100);
|
||||
reset_usb_boot(0, 2);
|
||||
}
|
||||
|
||||
int cli_extract_non_neg_int(const char *param, int len)
|
||||
{
|
||||
if (len == 0) {
|
||||
len = strlen(param);
|
||||
}
|
||||
int result = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (!isdigit((uint8_t)param[i])) {
|
||||
return -1;
|
||||
}
|
||||
result = result * 10 + param[i] - '0';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static char cmd_buf[256];
|
||||
static int cmd_len = 0;
|
||||
|
||||
static void process_cmd()
|
||||
{
|
||||
char *argv[MAX_PARAMETERS];
|
||||
int argc;
|
||||
|
||||
char *cmd = strtok(cmd_buf, " \n");
|
||||
|
||||
if (strlen(cmd) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
argc = 0;
|
||||
while ((argc < MAX_PARAMETERS) &&
|
||||
(argv[argc] = strtok(NULL, " ,\n")) != NULL) {
|
||||
argc++;
|
||||
}
|
||||
|
||||
int match = cli_match_prefix(commands, num_commands, cmd);
|
||||
if (match == -2) {
|
||||
printf("Ambiguous command.\n");
|
||||
return;
|
||||
} else if (match == -1) {
|
||||
printf("Unknown command.\n");
|
||||
handle_help(0, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
handlers[match](argc, argv);
|
||||
}
|
||||
|
||||
void cli_run()
|
||||
{
|
||||
static bool was_connected = false;
|
||||
static uint64_t connect_time = 0;
|
||||
static bool welcomed = false;
|
||||
bool connected = stdio_usb_connected();
|
||||
bool just_connected = connected && !was_connected;
|
||||
was_connected = connected;
|
||||
if (!connected) {
|
||||
return;
|
||||
}
|
||||
if (just_connected) {
|
||||
connect_time = time_us_64();
|
||||
welcomed = false;
|
||||
return;
|
||||
}
|
||||
if (!welcomed && (time_us_64() - connect_time > 200000)) {
|
||||
welcomed = true;
|
||||
cmd_len = 0;
|
||||
handle_help(0, NULL);
|
||||
printf("\n%s", cli_prompt);
|
||||
}
|
||||
int c = getchar_timeout_us(0);
|
||||
if (c == EOF) {
|
||||
return;
|
||||
}
|
||||
if (c == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == '\b' || c == 127) { // both backspace and delete
|
||||
if (cmd_len > 0) {
|
||||
cmd_len--;
|
||||
printf("\b \b");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ((c != '\n') && (c != '\r')) {
|
||||
|
||||
if (cmd_len < sizeof(cmd_buf) - 2) {
|
||||
cmd_buf[cmd_len] = c;
|
||||
printf("%c", c);
|
||||
cmd_len++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
cmd_buf[cmd_len] = '\0';
|
||||
cmd_len = 0;
|
||||
|
||||
printf("\n");
|
||||
|
||||
process_cmd();
|
||||
|
||||
printf(cli_prompt);
|
||||
}
|
||||
|
||||
void cli_init(const char *prompt, const char *logo)
|
||||
{
|
||||
if (prompt) {
|
||||
cli_prompt = prompt;
|
||||
}
|
||||
if (logo) {
|
||||
cli_logo = logo;
|
||||
}
|
||||
|
||||
cli_register("?", handle_help, "Display this help message.");
|
||||
cli_register("fps", handle_fps, "Display FPS.");
|
||||
cli_register("update", handle_update, "Update firmware.");
|
||||
}
|
21
firmware/src/cli.h
Normal file
21
firmware/src/cli.h
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Geki Controller Command Line Framework
|
||||
* WHowe <github.com/whowechina>
|
||||
*/
|
||||
|
||||
#ifndef CLI_H
|
||||
#define CLI_H
|
||||
|
||||
|
||||
typedef void (*cmd_handler_t)(int argc, char *argv[]);
|
||||
|
||||
void cli_init(const char *prompt, const char *logo);
|
||||
void cli_register(const char *cmd, cmd_handler_t handler, const char *help);
|
||||
void cli_run();
|
||||
void cli_fps_count(int core);
|
||||
|
||||
int cli_extract_non_neg_int(const char *param, int len);
|
||||
int cli_match_prefix(const char *str[], int num, const char *prefix);
|
||||
|
||||
extern const char *built_time;
|
||||
#endif
|
389
firmware/src/commands.c
Normal file
389
firmware/src/commands.c
Normal file
@ -0,0 +1,389 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "pico/stdio.h"
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "save.h"
|
||||
#include "cli.h"
|
||||
|
||||
#include "gimbal.h"
|
||||
|
||||
#include "usb_descriptors.h"
|
||||
|
||||
#define SENSE_LIMIT_MAX 9
|
||||
#define SENSE_LIMIT_MIN -9
|
||||
|
||||
static void disp_axis()
|
||||
{
|
||||
}
|
||||
|
||||
static void disp_hid()
|
||||
{
|
||||
printf("[HID]\n");
|
||||
printf(" Joy: %s, NKRO: %s.\n",
|
||||
geki_cfg->hid.joy ? "on" : "off",
|
||||
geki_cfg->hid.nkro ? "on" : "off" );
|
||||
}
|
||||
|
||||
static inline int sprintf_hsv_rgb(char *buf, const rgb_hsv_t *color)
|
||||
{
|
||||
return sprintf(buf, "%s(%d,%d,%d)", color->rgb_hsv ? "hsv" : "rgb",
|
||||
color->val[0], color->val[1], color->val[2]);
|
||||
}
|
||||
|
||||
static const char *color_str(const rgb_hsv_t *color, bool left_right)
|
||||
{
|
||||
static char buf[64];
|
||||
int count = 0;
|
||||
|
||||
if (left_right) {
|
||||
count += sprintf(buf + count, "LEFT ");
|
||||
}
|
||||
|
||||
count += sprintf_hsv_rgb(buf + count, color);
|
||||
|
||||
if (left_right) {
|
||||
count += sprintf(buf + count, ", RIGHT ");
|
||||
count += sprintf_hsv_rgb(buf + count, color + 1);
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void disp_light()
|
||||
{
|
||||
printf("[Light]\n");
|
||||
printf(" Level: %d.\n", geki_cfg->light.level);
|
||||
printf(" Colors:\n");
|
||||
printf(" base0: %s\n", color_str(geki_cfg->light.base[0], true));
|
||||
printf(" base1: %s\n", color_str(geki_cfg->light.base[0], true));
|
||||
printf(" button: %s\n", color_str(geki_cfg->light.button, true));
|
||||
printf(" boost: %s\n", color_str(geki_cfg->light.boost, true));
|
||||
printf(" steer: %s\n", color_str(geki_cfg->light.steer, true));
|
||||
printf(" aux_on: %s\n", color_str(&geki_cfg->light.aux_on, false));
|
||||
printf(" aux_off: %s\n", color_str(&geki_cfg->light.aux_off, false));
|
||||
}
|
||||
|
||||
static void disp_sound()
|
||||
{
|
||||
printf("[Sound]\n");
|
||||
printf(" Status: %s.\n", geki_cfg->sound.enabled ? "on" : "off");
|
||||
}
|
||||
|
||||
static void disp_gimbal()
|
||||
{
|
||||
printf("[Gimbal]\n");
|
||||
printf(" %s, %s, raw %d-%d.\n",
|
||||
geki_cfg->gimbal.invert ? "invert" : "normal",
|
||||
geki_cfg->gimbal.analog ? "analog" : "digital",
|
||||
geki_cfg->gimbal.min, geki_cfg->gimbal.max);
|
||||
}
|
||||
|
||||
void handle_display(int argc, char *argv[])
|
||||
{
|
||||
const char *usage = "Usage: display [axis|light|sound|hid|gimbal]\n";
|
||||
if (argc > 1) {
|
||||
printf(usage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (argc == 0) {
|
||||
disp_axis();
|
||||
disp_light();
|
||||
disp_gimbal();
|
||||
disp_sound();
|
||||
disp_hid();
|
||||
return;
|
||||
}
|
||||
|
||||
const char *choices[] = {"axis", "light", "gimbal", "sound", "hid"};
|
||||
switch (cli_match_prefix(choices, count_of(choices), argv[0])) {
|
||||
case 0:
|
||||
disp_axis();
|
||||
break;
|
||||
case 1:
|
||||
disp_light();
|
||||
break;
|
||||
case 2:
|
||||
disp_gimbal();
|
||||
break;
|
||||
case 3:
|
||||
disp_sound();
|
||||
break;
|
||||
case 4:
|
||||
disp_hid();
|
||||
break;
|
||||
default:
|
||||
printf(usage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int fps[2];
|
||||
void fps_count(int core)
|
||||
{
|
||||
static uint32_t last[2] = {0};
|
||||
static int counter[2] = {0};
|
||||
|
||||
counter[core]++;
|
||||
|
||||
uint32_t now = time_us_32();
|
||||
if (now - last[core] < 1000000) {
|
||||
return;
|
||||
}
|
||||
last[core] = now;
|
||||
fps[core] = counter[core];
|
||||
counter[core] = 0;
|
||||
}
|
||||
|
||||
static void handle_level(int argc, char *argv[])
|
||||
{
|
||||
const char *usage = "Usage: level <0..255>\n";
|
||||
if (argc != 1) {
|
||||
printf(usage);
|
||||
return;
|
||||
}
|
||||
|
||||
int level = cli_extract_non_neg_int(argv[0], 0);
|
||||
if ((level < 0) || (level > 255)) {
|
||||
printf(usage);
|
||||
return;
|
||||
}
|
||||
|
||||
geki_cfg->light.level = level;
|
||||
config_changed();
|
||||
disp_light();
|
||||
}
|
||||
|
||||
static void handle_hid(int argc, char *argv[])
|
||||
{
|
||||
const char *usage = "Usage: hid <joy|nkro|both>\n";
|
||||
if (argc != 1) {
|
||||
printf(usage);
|
||||
return;
|
||||
}
|
||||
|
||||
const char *choices[] = {"joy", "nkro", "both"};
|
||||
int match = cli_match_prefix(choices, 3, argv[0]);
|
||||
if (match < 0) {
|
||||
printf(usage);
|
||||
return;
|
||||
}
|
||||
|
||||
geki_cfg->hid.joy = ((match == 0) || (match == 2)) ? 1 : 0;
|
||||
geki_cfg->hid.nkro = ((match == 1) || (match == 2)) ? 1 : 0;
|
||||
config_changed();
|
||||
disp_hid();
|
||||
}
|
||||
|
||||
static void calibrate_range(uint32_t seconds)
|
||||
{
|
||||
uint32_t mins = 2048;
|
||||
uint32_t maxs = 2048;
|
||||
|
||||
uint64_t start = time_us_64();
|
||||
|
||||
while (time_us_64() - start < seconds * 1000000) {
|
||||
uint16_t val = gimbal_raw();
|
||||
printf("%4d\n", val);
|
||||
if (val < mins) {
|
||||
mins -= (mins - val) / 2;
|
||||
} else if (val > maxs) {
|
||||
maxs += (val - maxs) / 2;
|
||||
}
|
||||
sleep_ms(7);
|
||||
}
|
||||
|
||||
geki_cfg->gimbal.min = mins;
|
||||
geki_cfg->gimbal.max = maxs;
|
||||
}
|
||||
|
||||
static void gimbal_calibrate()
|
||||
{
|
||||
printf("Slowly move the stick in full range.\n");
|
||||
printf("Now calibrating ...");
|
||||
fflush(stdout);
|
||||
|
||||
calibrate_range(5);
|
||||
printf(" done.\n");
|
||||
}
|
||||
|
||||
static void gimbal_invert(const char *param)
|
||||
{
|
||||
const char *usage = "Usage: gimbal invert <on|off>\n";
|
||||
|
||||
int invert = cli_match_prefix((const char *[]){"off", "on"}, 2, param);
|
||||
if (invert < 0) {
|
||||
printf(usage);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("param:%s, invert:%d\n", param, invert);
|
||||
|
||||
geki_cfg->gimbal.invert = invert;
|
||||
}
|
||||
|
||||
static void gimbal_analog(const char *param)
|
||||
{
|
||||
const char *usage = "Usage: gimbal analog <on|off>\n";
|
||||
int analog = cli_match_prefix((const char *[]){"off", "on"}, 2, param);
|
||||
if (analog < 0) {
|
||||
printf(usage);
|
||||
return;
|
||||
}
|
||||
|
||||
geki_cfg->gimbal.analog = analog;
|
||||
}
|
||||
|
||||
static void handle_gimbal(int argc, char *argv[])
|
||||
{
|
||||
const char *usage = "Usage: gimbal calibrate\n"
|
||||
" gimbal invert <on|off>\n"
|
||||
" gimbal analog <on|off>\n";
|
||||
if (argc == 1) {
|
||||
if (strncasecmp(argv[0], "calibrate", strlen(argv[0])) != 0) {
|
||||
printf(usage);
|
||||
return;
|
||||
}
|
||||
gimbal_calibrate();
|
||||
} else if (argc == 2) {
|
||||
int op = cli_match_prefix((const char *[]){"invert", "analog"}, 2, argv[0]);
|
||||
if (op == 0) {
|
||||
gimbal_invert(argv[1]);
|
||||
} else if (op == 1) {
|
||||
gimbal_analog(argv[1]);
|
||||
} else {
|
||||
printf(usage);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
printf(usage);
|
||||
return;
|
||||
}
|
||||
|
||||
config_changed();
|
||||
disp_gimbal();
|
||||
}
|
||||
|
||||
static bool extract_color(rgb_hsv_t *color, char *argv[4])
|
||||
{
|
||||
int rgb_hsv = cli_match_prefix((const char *[]){"rgb", "hsv"}, 2, argv[0]);
|
||||
if (rgb_hsv < 0) {
|
||||
return false;
|
||||
}
|
||||
color->rgb_hsv = rgb_hsv;
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int v = cli_extract_non_neg_int(argv[1 + i], 0);
|
||||
if ((v < 0) || (v > 255)) {
|
||||
return false;
|
||||
}
|
||||
color->val[i] = v;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void handle_color(int argc, char *argv[])
|
||||
{
|
||||
const char *usage = "Usage: color <name> [left|right] <rgb|hsv> <0..255> <0..255> <0..255>\n"
|
||||
" name: base0 base1 button boost steer aux_on aux_off\n";
|
||||
if ((argc != 5) && (argc != 6)) {
|
||||
printf(usage);
|
||||
return;
|
||||
}
|
||||
|
||||
rgb_hsv_t *names[] = {
|
||||
&geki_cfg->light.aux_on,
|
||||
&geki_cfg->light.aux_off,
|
||||
geki_cfg->light.base[0],
|
||||
geki_cfg->light.base[1],
|
||||
geki_cfg->light.button,
|
||||
geki_cfg->light.boost,
|
||||
geki_cfg->light.steer,
|
||||
};
|
||||
const char *choices[] = {"aux_on", "aux_off", "base0", "base1", "button", "boost", "steer"};
|
||||
static_assert(count_of(choices) == count_of(names));
|
||||
|
||||
int name = cli_match_prefix(choices, count_of(choices), argv[0]);
|
||||
if (name < 0) {
|
||||
printf(usage);
|
||||
return;
|
||||
}
|
||||
|
||||
bool left = true;
|
||||
bool right = true;
|
||||
if (argc == 6) {
|
||||
int left_right = cli_match_prefix((const char *[]){"left", "right"}, 2, argv[1]);
|
||||
if (left_right < 0) {
|
||||
printf(usage);
|
||||
return;
|
||||
}
|
||||
left = (left_right == 0);
|
||||
right = (left_right == 1);
|
||||
}
|
||||
|
||||
rgb_hsv_t color;
|
||||
if (!extract_color(&color, argv + argc - 4)) {
|
||||
printf(usage);
|
||||
return;
|
||||
}
|
||||
|
||||
rgb_hsv_t *target = names[name];
|
||||
if (left) {
|
||||
target[0] = color;
|
||||
}
|
||||
if ((name >= 2) && right) {
|
||||
target[1] = color;
|
||||
}
|
||||
|
||||
config_changed();
|
||||
disp_light();
|
||||
}
|
||||
|
||||
static void handle_sound(int argc, char *argv[])
|
||||
{
|
||||
const char *usage = "Usage: sound <on|off>\n";
|
||||
if (argc != 1) {
|
||||
printf(usage);
|
||||
return;
|
||||
}
|
||||
|
||||
int on_off = cli_match_prefix((const char *[]){"off", "on"}, 2, argv[0]);
|
||||
if (on_off < 0) {
|
||||
printf(usage);
|
||||
return;
|
||||
}
|
||||
|
||||
geki_cfg->sound.enabled = on_off;
|
||||
config_changed();
|
||||
disp_sound();
|
||||
}
|
||||
|
||||
static void handle_save()
|
||||
{
|
||||
save_request(true);
|
||||
}
|
||||
|
||||
static void handle_factory_reset()
|
||||
{
|
||||
config_factory_reset();
|
||||
printf("Factory reset done.\n");
|
||||
}
|
||||
|
||||
void commands_init()
|
||||
{
|
||||
cli_register("display", handle_display, "Display all config.");
|
||||
cli_register("level", handle_level, "Set LED brightness level.");
|
||||
cli_register("color", handle_color, "Set LED color.");
|
||||
cli_register("hid", handle_hid, "Set HID mode.");
|
||||
cli_register("gimbal", handle_gimbal, "Calibrate the gimbals.");
|
||||
cli_register("sound", handle_sound, "Enable/disable sound.");
|
||||
cli_register("save", handle_save, "Save config to flash.");
|
||||
cli_register("factory", handle_factory_reset, "Reset everything to default.");
|
||||
}
|
11
firmware/src/commands.h
Normal file
11
firmware/src/commands.h
Normal file
@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Geki Controller Command Line Commands
|
||||
* WHowe <github.com/whowechina>
|
||||
*/
|
||||
|
||||
#ifndef COMMANDS_H
|
||||
#define COMMANDS_H
|
||||
|
||||
void commands_init();
|
||||
|
||||
#endif
|
76
firmware/src/config.c
Normal file
76
firmware/src/config.c
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Controller Config and Runtime Data
|
||||
* WHowe <github.com/whowechina>
|
||||
*
|
||||
* Config is a global data structure that stores all the configuration
|
||||
* Runtime is something to share between files.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "save.h"
|
||||
|
||||
geki_cfg_t *geki_cfg;
|
||||
|
||||
static geki_cfg_t default_cfg = {
|
||||
.gimbal = {
|
||||
2000, 2500, 0, 80, 1,
|
||||
},
|
||||
.light = {
|
||||
.level = 128,
|
||||
.base = {
|
||||
{
|
||||
{ 1, { 20, 150, 10 }, },
|
||||
{ 1, { 147, 150, 10 }, },
|
||||
},
|
||||
{
|
||||
{ 1, { 20, 150, 30 }, },
|
||||
{ 1, { 147, 150, 30 }, },
|
||||
},
|
||||
},
|
||||
.button = {
|
||||
{ 1, { 0, 0, 120 } },
|
||||
{ 1, { 0, 0, 120 } },
|
||||
},
|
||||
.boost = {
|
||||
{ 1, { 20, 255, 255 } },
|
||||
{ 1, { 147, 255, 255 } },
|
||||
},
|
||||
.steer = {
|
||||
{ 1, { 80, 255, 255 } },
|
||||
{ 1, { 80, 255, 255 } },
|
||||
},
|
||||
.aux_on = { 0, { 100, 100, 100 } },
|
||||
.aux_off = { 0, { 8, 8, 8 } },
|
||||
.reserved = { 0 },
|
||||
},
|
||||
.sound = {
|
||||
.enabled = true,
|
||||
.reserved = { 0 },
|
||||
},
|
||||
.hid = {
|
||||
.joy = 1,
|
||||
.nkro = 0,
|
||||
},
|
||||
};
|
||||
|
||||
geki_runtime_t *geki_runtime;
|
||||
|
||||
static void config_loaded()
|
||||
{
|
||||
}
|
||||
|
||||
void config_changed()
|
||||
{
|
||||
save_request(false);
|
||||
}
|
||||
|
||||
void config_factory_reset()
|
||||
{
|
||||
*geki_cfg = default_cfg;
|
||||
save_request(true);
|
||||
}
|
||||
|
||||
void config_init()
|
||||
{
|
||||
geki_cfg = (geki_cfg_t *)save_alloc(sizeof(*geki_cfg), &default_cfg, config_loaded);
|
||||
}
|
56
firmware/src/config.h
Normal file
56
firmware/src/config.h
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Controller Config
|
||||
* WHowe <github.com/whowechina>
|
||||
*/
|
||||
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct {
|
||||
uint8_t rgb_hsv; // 0: RGB, 1: HSV
|
||||
uint8_t val[3]; // RGB or HSV
|
||||
} rgb_hsv_t;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
struct {
|
||||
uint16_t min;
|
||||
uint16_t max;
|
||||
uint8_t invert:1;
|
||||
uint8_t threshold:7;
|
||||
uint8_t analog:1;
|
||||
} gimbal;
|
||||
struct {
|
||||
rgb_hsv_t base[2][2];
|
||||
rgb_hsv_t button[2];
|
||||
rgb_hsv_t boost[2];
|
||||
rgb_hsv_t steer[2];
|
||||
rgb_hsv_t aux_on;
|
||||
rgb_hsv_t aux_off;
|
||||
uint8_t level;
|
||||
uint8_t reserved[15];
|
||||
} light;
|
||||
struct {
|
||||
bool enabled;
|
||||
uint8_t reserved[3];
|
||||
} sound;
|
||||
struct {
|
||||
uint8_t joy : 4;
|
||||
uint8_t nkro : 4;
|
||||
} hid;
|
||||
} geki_cfg_t;
|
||||
|
||||
typedef struct {
|
||||
uint16_t fps[2];
|
||||
} geki_runtime_t;
|
||||
|
||||
extern geki_cfg_t *geki_cfg;
|
||||
extern geki_runtime_t *geki_runtime;
|
||||
|
||||
void config_init();
|
||||
void config_changed(); // Notify the config has changed
|
||||
void config_factory_reset(); // Reset the config to factory default
|
||||
|
||||
#endif
|
92
firmware/src/gimbal.c
Normal file
92
firmware/src/gimbal.c
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Left and Right Gimbal Inputs
|
||||
* WHowe <github.com/whowechina>
|
||||
*
|
||||
*/
|
||||
|
||||
#include "gimbal.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "pico/stdio.h"
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "hardware/gpio.h"
|
||||
#include "hardware/adc.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "board_defs.h"
|
||||
|
||||
void gimbal_init()
|
||||
{
|
||||
gpio_init(AXIS_MUX_PIN_A);
|
||||
gpio_set_dir(AXIS_MUX_PIN_A, GPIO_OUT);
|
||||
|
||||
gpio_init(AXIS_MUX_PIN_B);
|
||||
gpio_set_dir(AXIS_MUX_PIN_B, GPIO_OUT);
|
||||
|
||||
adc_init();
|
||||
adc_gpio_init(26 + ADC_CHANNEL);
|
||||
adc_select_input(ADC_CHANNEL);
|
||||
}
|
||||
|
||||
uint8_t gimbal_read()
|
||||
{
|
||||
uint16_t val = gimbal_average();
|
||||
const uint16_t min = geki_cfg->gimbal.min;
|
||||
const uint16_t max = geki_cfg->gimbal.max;
|
||||
|
||||
if (val < min) {
|
||||
val = min;
|
||||
} else if (val > max) {
|
||||
val = max;
|
||||
}
|
||||
|
||||
uint16_t range = max - min;
|
||||
if (!range) {
|
||||
range = 100;
|
||||
}
|
||||
|
||||
uint8_t result = (val - min) * 255 / range;
|
||||
if (geki_cfg->gimbal.invert) {
|
||||
result = 255 - result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uint16_t gimbal_raw()
|
||||
{
|
||||
static uint16_t last_read = 2048;
|
||||
const uint16_t rate_limit = 5;
|
||||
|
||||
uint16_t val = adc_read();
|
||||
if (val > last_read + rate_limit) {
|
||||
last_read += rate_limit;
|
||||
} else if (val < last_read - rate_limit) {
|
||||
last_read -= rate_limit;
|
||||
} else {
|
||||
last_read = val;
|
||||
}
|
||||
|
||||
return last_read;
|
||||
}
|
||||
|
||||
#define GIMBAL_AVERAGE_COUNT 32
|
||||
uint16_t gimbal_average()
|
||||
{
|
||||
static uint16_t buf[GIMBAL_AVERAGE_COUNT] = {0};
|
||||
static int index = 0;
|
||||
index = (index + 1) % GIMBAL_AVERAGE_COUNT;
|
||||
buf[index] = gimbal_raw();
|
||||
|
||||
uint32_t sum = 0;
|
||||
for (int i = 0; i < GIMBAL_AVERAGE_COUNT; i++) {
|
||||
sum += buf[i];
|
||||
}
|
||||
|
||||
return sum / GIMBAL_AVERAGE_COUNT;
|
||||
}
|
18
firmware/src/gimbal.h
Normal file
18
firmware/src/gimbal.h
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Left and Right Gimbal Inputs
|
||||
* WHowe <github.com/whowechina>
|
||||
*/
|
||||
|
||||
#ifndef GIMBAL_H
|
||||
#define GIMBAL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
void gimbal_init();
|
||||
|
||||
uint8_t gimbal_read();
|
||||
uint16_t gimbal_raw();
|
||||
uint16_t gimbal_average();
|
||||
|
||||
#endif
|
163
firmware/src/light.c
Normal file
163
firmware/src/light.c
Normal file
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* WS2812B Lights Control (Base + Left and Right Gimbals)
|
||||
* WHowe <github.com/whowechina>
|
||||
*
|
||||
*/
|
||||
|
||||
#include "light.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "bsp/board.h"
|
||||
#include "hardware/pio.h"
|
||||
#include "hardware/timer.h"
|
||||
|
||||
#include "ws2812.pio.h"
|
||||
|
||||
#include "board_defs.h"
|
||||
#include "config.h"
|
||||
|
||||
static uint32_t buf_rgb[37]; // left 3 + right 3 + button 4 * 7 + indicator 5
|
||||
static bool bind[37] = { 0 };
|
||||
|
||||
#define _MAP_LED(x) _MAKE_MAPPER(x)
|
||||
#define _MAKE_MAPPER(x) MAP_LED_##x
|
||||
#define MAP_LED_RGB { c1 = r; c2 = g; c3 = b; }
|
||||
#define MAP_LED_GRB { c1 = g; c2 = r; c3 = b; }
|
||||
|
||||
#define REMAP_BUTTON_RGB _MAP_LED(BUTTON_RGB_ORDER)
|
||||
#define REMAP_TT_RGB _MAP_LED(TT_RGB_ORDER)
|
||||
|
||||
static inline uint32_t _rgb32(uint32_t c1, uint32_t c2, uint32_t c3, bool gamma_fix)
|
||||
{
|
||||
if (gamma_fix) {
|
||||
c1 = ((c1 + 1) * (c1 + 1) - 1) >> 8;
|
||||
c2 = ((c2 + 1) * (c2 + 1) - 1) >> 8;
|
||||
c3 = ((c3 + 1) * (c3 + 1) - 1) >> 8;
|
||||
}
|
||||
|
||||
return (c1 << 16) | (c2 << 8) | (c3 << 0);
|
||||
}
|
||||
|
||||
uint32_t rgb32(uint32_t r, uint32_t g, uint32_t b, bool gamma_fix)
|
||||
{
|
||||
#if BUTTON_RGB_ORDER == GRB
|
||||
return _rgb32(g, r, b, gamma_fix);
|
||||
#else
|
||||
return _rgb32(r, g, b, gamma_fix);
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t rgb32_from_hsv(uint8_t h, uint8_t s, uint8_t v)
|
||||
{
|
||||
uint32_t region, remainder, p, q, t;
|
||||
|
||||
if (s == 0) {
|
||||
return v << 16 | v << 8 | v;
|
||||
}
|
||||
|
||||
region = h / 43;
|
||||
remainder = (h % 43) * 6;
|
||||
|
||||
p = (v * (255 - s)) >> 8;
|
||||
q = (v * (255 - ((s * remainder) >> 8))) >> 8;
|
||||
t = (v * (255 - ((s * (255 - remainder)) >> 8))) >> 8;
|
||||
|
||||
switch (region) {
|
||||
case 0:
|
||||
return v << 16 | t << 8 | p;
|
||||
case 1:
|
||||
return q << 16 | v << 8 | p;
|
||||
case 2:
|
||||
return p << 16 | v << 8 | t;
|
||||
case 3:
|
||||
return p << 16 | q << 8 | v;
|
||||
case 4:
|
||||
return t << 16 | p << 8 | v;
|
||||
default:
|
||||
return v << 16 | p << 8 | q;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t load_color(const rgb_hsv_t *color)
|
||||
{
|
||||
if (color->rgb_hsv == 0) {
|
||||
return rgb32(color->val[0], color->val[1], color->val[2], false);
|
||||
} else {
|
||||
return rgb32_from_hsv(color->val[0], color->val[1], color->val[2]);
|
||||
}
|
||||
}
|
||||
|
||||
static void drive_led()
|
||||
{
|
||||
for (int i = 0; i < count_of(buf_rgb); i++) { \
|
||||
pio_sm_put_blocking(pio0, 0, buf_rgb[i] << 8u); \
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint32_t apply_level(uint32_t color)
|
||||
{
|
||||
unsigned r = (color >> 16) & 0xff;
|
||||
unsigned g = (color >> 8) & 0xff;
|
||||
unsigned b = color & 0xff;
|
||||
|
||||
r = r * geki_cfg->light.level / 255;
|
||||
g = g * geki_cfg->light.level / 255;
|
||||
b = b * geki_cfg->light.level / 255;
|
||||
|
||||
return r << 16 | g << 8 | b;
|
||||
}
|
||||
|
||||
void light_init()
|
||||
{
|
||||
uint offset = pio_add_program(pio0, &ws2812_program);
|
||||
ws2812_program_init(pio0, 0, offset, RGB_PIN, 800000, false);
|
||||
}
|
||||
|
||||
static void light_effect()
|
||||
{
|
||||
static uint32_t loop = 0;
|
||||
loop++;
|
||||
|
||||
for (int i = 0; i < count_of(buf_rgb); i++) {
|
||||
uint32_t hue = (loop + i * 255 / count_of(buf_rgb)) % 255;
|
||||
if (!bind[i]) {
|
||||
buf_rgb[i] = rgb32_from_hsv(hue, 255, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void light_update()
|
||||
{
|
||||
static uint64_t last = 0;
|
||||
uint64_t now = time_us_64();
|
||||
if (now - last < 5000) { // 200Hz
|
||||
return;
|
||||
}
|
||||
|
||||
last = now;
|
||||
|
||||
light_effect();
|
||||
drive_led();
|
||||
}
|
||||
|
||||
void light_set(uint8_t index, uint32_t color)
|
||||
{
|
||||
if (index >= count_of(buf_rgb)) {
|
||||
return;
|
||||
}
|
||||
buf_rgb[index] = apply_level(color);
|
||||
bind[index] = true;
|
||||
}
|
||||
|
||||
void light_unset(uint8_t index)
|
||||
{
|
||||
if (index >= count_of(buf_rgb)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bind[index] = false;
|
||||
}
|
25
firmware/src/light.h
Normal file
25
firmware/src/light.h
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* WS2812B Lights Control (Base + Left and Right Gimbals)
|
||||
* WHowe <github.com/whowechina>
|
||||
*/
|
||||
|
||||
#ifndef LIGHT_H
|
||||
#define LIGHT_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
void light_init();
|
||||
void light_update();
|
||||
|
||||
uint32_t rgb32(uint32_t r, uint32_t g, uint32_t b, bool gamma_fix);
|
||||
uint32_t rgb32_from_hsv(uint8_t h, uint8_t s, uint8_t v);
|
||||
uint32_t load_color(const rgb_hsv_t *color);
|
||||
|
||||
void light_set(uint8_t index, uint32_t color);
|
||||
void light_unset(uint8_t index);
|
||||
|
||||
#endif
|
302
firmware/src/main.c
Normal file
302
firmware/src/main.c
Normal file
@ -0,0 +1,302 @@
|
||||
/*
|
||||
* Controller Main
|
||||
* WHowe <github.com/whowechina>
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "pico/stdio.h"
|
||||
#include "pico/stdlib.h"
|
||||
#include "bsp/board.h"
|
||||
#include "pico/multicore.h"
|
||||
#include "pico/bootrom.h"
|
||||
|
||||
#include "hardware/gpio.h"
|
||||
#include "hardware/sync.h"
|
||||
#include "hardware/structs/ioqspi.h"
|
||||
#include "hardware/structs/sio.h"
|
||||
|
||||
#include "tusb.h"
|
||||
#include "usb_descriptors.h"
|
||||
|
||||
#include "board_defs.h"
|
||||
|
||||
#include "save.h"
|
||||
#include "config.h"
|
||||
#include "cli.h"
|
||||
#include "commands.h"
|
||||
|
||||
#include "light.h"
|
||||
#include "button.h"
|
||||
#include "gimbal.h"
|
||||
#include "wad.h"
|
||||
#include "sound.h"
|
||||
|
||||
struct __attribute__((packed)) {
|
||||
uint16_t buttons;
|
||||
uint8_t HAT;
|
||||
uint8_t lx;
|
||||
uint8_t ly;
|
||||
uint8_t rx;
|
||||
uint8_t ry;
|
||||
uint8_t vendor;
|
||||
} hid_joy;
|
||||
|
||||
struct __attribute__((packed)) {
|
||||
uint8_t modifier;
|
||||
uint8_t keymap[15];
|
||||
} hid_nkro, sent_hid_nkro;
|
||||
|
||||
void report_usb_hid()
|
||||
{
|
||||
if (tud_hid_ready()) {
|
||||
hid_joy.HAT = 0x08;
|
||||
hid_joy.vendor = 0;
|
||||
if (geki_cfg->hid.joy) {
|
||||
tud_hid_n_report(0x00, 0, &hid_joy, sizeof(hid_joy));
|
||||
}
|
||||
if (geki_cfg->hid.nkro &&
|
||||
(memcmp(&hid_nkro, &sent_hid_nkro, sizeof(hid_nkro)) != 0)) {
|
||||
sent_hid_nkro = hid_nkro;
|
||||
tud_hid_n_report(0x02, 0, &sent_hid_nkro, sizeof(sent_hid_nkro));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define SWITCH_BIT_Y (1U << 0)
|
||||
#define SWITCH_BIT_B (1U << 1)
|
||||
#define SWITCH_BIT_A (1U << 2)
|
||||
#define SWITCH_BIT_X (1U << 3)
|
||||
#define SWITCH_BIT_L (1U << 4)
|
||||
#define SWITCH_BIT_R (1U << 5)
|
||||
#define SWITCH_BIT_ZL (1U << 6)
|
||||
#define SWITCH_BIT_ZR (1U << 7)
|
||||
#define SWITCH_BIT_MINUS (1U << 8)
|
||||
#define SWITCH_BIT_PLUS (1U << 9)
|
||||
#define SWITCH_BIT_L3 (1U << 10)
|
||||
#define SWITCH_BIT_R3 (1U << 11)
|
||||
#define SWITCH_BIT_HOME (1U << 12)
|
||||
|
||||
static void gen_joy_report()
|
||||
{
|
||||
hid_joy.lx = gimbal_read();
|
||||
|
||||
uint16_t button = button_read();
|
||||
hid_joy.buttons = 0;
|
||||
hid_joy.buttons |= (button & 0x01) ? SWITCH_BIT_L : 0;
|
||||
hid_joy.buttons |= (button & 0x02) ? SWITCH_BIT_R : 0;
|
||||
if (button & 0x08) {
|
||||
hid_joy.buttons |= (button & 0x04) ? SWITCH_BIT_MINUS : 0;
|
||||
hid_joy.buttons |= (button & 0x10) ? SWITCH_BIT_PLUS : 0;
|
||||
} else {
|
||||
hid_joy.buttons |= (button & 0x04) ? SWITCH_BIT_B : 0;
|
||||
hid_joy.buttons |= (button & 0x10) ? SWITCH_BIT_A : 0;
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t keycode_table[128][2] = { HID_ASCII_TO_KEYCODE };
|
||||
const uint8_t keymap[38 + 1] = NKRO_KEYMAP; // 32 keys, 6 air keys, 1 terminator
|
||||
static void gen_nkro_report()
|
||||
{
|
||||
for (int i = 0; i < 6; i++) {
|
||||
uint8_t code = keycode_table[keymap[32 + i]][1];
|
||||
uint8_t byte = code / 8;
|
||||
uint8_t bit = code % 8;
|
||||
if (hid_joy.buttons & (1 << i)) {
|
||||
hid_nkro.keymap[byte] |= (1 << bit);
|
||||
} else {
|
||||
hid_nkro.keymap[byte] &= ~(1 << bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t last_hid_time = 0;
|
||||
|
||||
static void run_lights()
|
||||
{
|
||||
int gimbal = gimbal_read();
|
||||
gimbal = gimbal * 5 / 256;
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
light_set(16 + i, (i == gimbal) ? 0x00ff00 : 0);
|
||||
}
|
||||
|
||||
uint32_t colors[6] = {0x400000, 0x004000, 0x000040,
|
||||
0x400000, 0x004000, 0x000040 };
|
||||
uint16_t button = button_read();
|
||||
for (int i = 0; i < 6; i++) {
|
||||
uint32_t color = colors[i];
|
||||
if (button & (1 << i)) {
|
||||
color = 0x808080;
|
||||
}
|
||||
int index = 4 + i * 4 + (i > 2 ? 5 : 0);
|
||||
light_set(index, color);
|
||||
light_set(index + 1, color);
|
||||
light_set(index + 2, color);
|
||||
light_set(index + 3, color);
|
||||
}
|
||||
|
||||
if (button & 0x40) {
|
||||
light_set(0, 0x808080);
|
||||
} else {
|
||||
light_set(0, 0);
|
||||
}
|
||||
|
||||
if (button & 0x80) {
|
||||
light_set(36, 0x808080);
|
||||
} else {
|
||||
light_set(36, 0);
|
||||
}
|
||||
|
||||
if (wad_read_left()) {
|
||||
light_set(1, 0x804000);
|
||||
light_set(2, 0x804000);
|
||||
light_set(3, 0x804000);
|
||||
} else {
|
||||
light_set(1, 0);
|
||||
light_set(2, 0);
|
||||
light_set(3, 0);
|
||||
}
|
||||
|
||||
if (wad_read_right()) {
|
||||
light_set(33, 0x004080);
|
||||
light_set(34, 0x004080);
|
||||
light_set(35, 0x004080);
|
||||
} else {
|
||||
light_set(33, 0);
|
||||
light_set(34, 0);
|
||||
light_set(35, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void run_sound()
|
||||
{
|
||||
sound_set(0, wad_read_left());
|
||||
sound_set(1, wad_read_right());
|
||||
}
|
||||
|
||||
static mutex_t core1_io_lock;
|
||||
static void core1_loop()
|
||||
{
|
||||
while (1) {
|
||||
if (mutex_try_enter(&core1_io_lock, NULL)) {
|
||||
run_lights();
|
||||
run_sound();
|
||||
light_update();
|
||||
mutex_exit(&core1_io_lock);
|
||||
}
|
||||
cli_fps_count(1);
|
||||
sleep_us(700);
|
||||
}
|
||||
}
|
||||
|
||||
static void core0_loop()
|
||||
{
|
||||
while(1) {
|
||||
tud_task();
|
||||
|
||||
cli_run();
|
||||
|
||||
save_loop();
|
||||
cli_fps_count(0);
|
||||
|
||||
button_update();
|
||||
wad_update();
|
||||
gen_joy_report();
|
||||
gen_nkro_report();
|
||||
report_usb_hid();
|
||||
|
||||
sleep_us(900);
|
||||
}
|
||||
}
|
||||
|
||||
/* if certain key pressed when booting, enter update mode */
|
||||
static void update_check()
|
||||
{
|
||||
const uint8_t pins[] = BUTTON_DEF; // keypad 00 and *
|
||||
bool all_pressed = true;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
uint8_t gpio = pins[sizeof(pins) - 2 + i];
|
||||
gpio_init(gpio);
|
||||
gpio_set_function(gpio, GPIO_FUNC_SIO);
|
||||
gpio_set_dir(gpio, GPIO_IN);
|
||||
gpio_pull_up(gpio);
|
||||
sleep_ms(1);
|
||||
if (gpio_get(gpio)) {
|
||||
all_pressed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (all_pressed) {
|
||||
sleep_ms(100);
|
||||
reset_usb_boot(0, 2);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void init()
|
||||
{
|
||||
sleep_ms(50);
|
||||
board_init();
|
||||
|
||||
update_check();
|
||||
|
||||
tusb_init();
|
||||
stdio_init_all();
|
||||
|
||||
config_init();
|
||||
mutex_init(&core1_io_lock);
|
||||
save_init(0xca44caac, &core1_io_lock);
|
||||
|
||||
light_init();
|
||||
button_init();
|
||||
gimbal_init();
|
||||
wad_init();
|
||||
sound_init();
|
||||
|
||||
cli_init("geki_pico>", "\n << Geki Pico Controller >>\n"
|
||||
" https://github.com/whowechina\n\n");
|
||||
|
||||
commands_init();
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
init();
|
||||
multicore_launch_core1(core1_loop);
|
||||
core0_loop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
struct __attribute__((packed)) {
|
||||
uint16_t buttons;
|
||||
uint8_t HAT;
|
||||
uint32_t axis;
|
||||
} hid_joy_out = {0};
|
||||
|
||||
// Invoked when received GET_REPORT control request
|
||||
// Application must fill buffer report's content and return its length.
|
||||
// Return zero will cause the stack to STALL request
|
||||
uint16_t tud_hid_get_report_cb(uint8_t itf, uint8_t report_id,
|
||||
hid_report_type_t report_type, uint8_t *buffer,
|
||||
uint16_t reqlen)
|
||||
{
|
||||
printf("Get from USB %d-%d\n", report_id, report_type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Invoked when received SET_REPORT control request or
|
||||
// received data on OUT endpoint ( Report ID = 0, Type = 0 )
|
||||
void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id,
|
||||
hid_report_type_t report_type, uint8_t const *buffer,
|
||||
uint16_t bufsize)
|
||||
{
|
||||
if (report_type == HID_REPORT_TYPE_OUTPUT) {
|
||||
last_hid_time = time_us_64();
|
||||
return;
|
||||
}
|
||||
}
|
178
firmware/src/save.c
Normal file
178
firmware/src/save.c
Normal file
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Controller Config Save and Load
|
||||
* WHowe <github.com/whowechina>
|
||||
*
|
||||
* Config is stored in last sector of flash
|
||||
*/
|
||||
|
||||
#include "save.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <memory.h>
|
||||
|
||||
|
||||
#include "pico/bootrom.h"
|
||||
#include "pico/stdio.h"
|
||||
|
||||
#include "hardware/flash.h"
|
||||
#include "pico/multicore.h"
|
||||
#include "pico/unique_id.h"
|
||||
|
||||
static struct {
|
||||
size_t size;
|
||||
size_t offset;
|
||||
void (*after_load)();
|
||||
} modules[8] = {0};
|
||||
static int module_num = 0;
|
||||
|
||||
static uint32_t my_magic = 0xcafec00e;
|
||||
|
||||
#define SAVE_TIMEOUT_US 5000000
|
||||
|
||||
#define SAVE_SECTOR_OFFSET (PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE)
|
||||
|
||||
typedef struct __attribute ((packed)) {
|
||||
uint32_t magic;
|
||||
uint8_t data[FLASH_PAGE_SIZE - 4];
|
||||
} page_t;
|
||||
|
||||
static page_t old_data = {0};
|
||||
static page_t new_data = {0};
|
||||
static page_t default_data = {0};
|
||||
static int data_page = -1;
|
||||
|
||||
static bool requesting_save = false;
|
||||
static uint64_t requesting_time = 0;
|
||||
|
||||
static mutex_t *io_lock;
|
||||
|
||||
static void save_program()
|
||||
{
|
||||
old_data = new_data;
|
||||
|
||||
data_page = (data_page + 1) % (FLASH_SECTOR_SIZE / FLASH_PAGE_SIZE);
|
||||
printf("\nProgram Flash %d %8lx\n", data_page, old_data.magic);
|
||||
if (mutex_enter_timeout_us(io_lock, 100000)) {
|
||||
sleep_ms(10); /* wait for all io operations to finish */
|
||||
uint32_t ints = save_and_disable_interrupts();
|
||||
if (data_page == 0) {
|
||||
flash_range_erase(SAVE_SECTOR_OFFSET, FLASH_SECTOR_SIZE);
|
||||
}
|
||||
flash_range_program(SAVE_SECTOR_OFFSET + data_page * FLASH_PAGE_SIZE,
|
||||
(uint8_t *)&old_data, FLASH_PAGE_SIZE);
|
||||
restore_interrupts(ints);
|
||||
mutex_exit(io_lock);
|
||||
} else {
|
||||
printf("Program Flash Failed.\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void load_default()
|
||||
{
|
||||
printf("Load Default\n");
|
||||
new_data = default_data;
|
||||
new_data.magic = my_magic;
|
||||
}
|
||||
|
||||
static const page_t *get_page(int id)
|
||||
{
|
||||
int addr = XIP_BASE + SAVE_SECTOR_OFFSET;
|
||||
return (page_t *)(addr + FLASH_PAGE_SIZE * id);
|
||||
}
|
||||
|
||||
static void save_load()
|
||||
{
|
||||
for (int i = 0; i < FLASH_SECTOR_SIZE / FLASH_PAGE_SIZE; i++) {
|
||||
if (get_page(i)->magic != my_magic) {
|
||||
break;
|
||||
}
|
||||
data_page = i;
|
||||
}
|
||||
|
||||
if (data_page < 0) {
|
||||
load_default();
|
||||
save_request(false);
|
||||
return;
|
||||
}
|
||||
|
||||
old_data = *get_page(data_page);
|
||||
new_data = old_data;
|
||||
printf("Page Loaded %d %8lx\n", data_page, new_data.magic);
|
||||
}
|
||||
|
||||
static void save_loaded()
|
||||
{
|
||||
for (int i = 0; i < module_num; i++) {
|
||||
modules[i].after_load();
|
||||
}
|
||||
}
|
||||
|
||||
static union __attribute__((packed)) {
|
||||
pico_unique_board_id_t id;
|
||||
struct {
|
||||
uint32_t id32h;
|
||||
uint32_t id32l;
|
||||
};
|
||||
uint64_t id64;
|
||||
} board_id;
|
||||
|
||||
uint32_t board_id_32()
|
||||
{
|
||||
pico_get_unique_board_id(&board_id.id);
|
||||
return board_id.id32h ^ board_id.id32l;
|
||||
}
|
||||
|
||||
uint64_t board_id_64()
|
||||
{
|
||||
pico_get_unique_board_id(&board_id.id);
|
||||
return board_id.id64;
|
||||
}
|
||||
|
||||
void save_init(uint32_t magic, mutex_t *locker)
|
||||
{
|
||||
my_magic = magic;
|
||||
io_lock = locker;
|
||||
save_load();
|
||||
save_loop();
|
||||
save_loaded();
|
||||
}
|
||||
|
||||
void save_loop()
|
||||
{
|
||||
if (requesting_save && (time_us_64() - requesting_time > SAVE_TIMEOUT_US)) {
|
||||
requesting_save = false;
|
||||
/* only when data is actually changed */
|
||||
if (memcmp(&old_data, &new_data, sizeof(old_data)) == 0) {
|
||||
return;
|
||||
}
|
||||
save_program();
|
||||
}
|
||||
}
|
||||
|
||||
void *save_alloc(size_t size, void *def, void (*after_load)())
|
||||
{
|
||||
modules[module_num].size = size;
|
||||
size_t offset = module_num > 0 ? modules[module_num - 1].offset + size : 0;
|
||||
modules[module_num].offset = offset;
|
||||
modules[module_num].after_load = after_load;
|
||||
module_num++;
|
||||
memcpy(default_data.data + offset, def, size); // backup the default
|
||||
return new_data.data + offset;
|
||||
}
|
||||
|
||||
void save_request(bool immediately)
|
||||
{
|
||||
if (!requesting_save) {
|
||||
printf("Save requested.\n");
|
||||
requesting_save = true;
|
||||
new_data.magic = my_magic;
|
||||
requesting_time = time_us_64();
|
||||
}
|
||||
if (immediately) {
|
||||
requesting_time = 0;
|
||||
save_loop();
|
||||
}
|
||||
}
|
27
firmware/src/save.h
Normal file
27
firmware/src/save.h
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Controller Flash Save and Load
|
||||
* WHowe <github.com/whowechina>
|
||||
*/
|
||||
|
||||
#ifndef SAVE_H
|
||||
#define SAVE_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "pico/multicore.h"
|
||||
|
||||
uint32_t board_id_32();
|
||||
uint64_t board_id_64();
|
||||
|
||||
/* It's safer to lock other I/O ops during saving, so we need a locker */
|
||||
typedef void (*io_locker_func)(bool pause);
|
||||
void save_init(uint32_t magic, mutex_t *lock);
|
||||
|
||||
void save_loop();
|
||||
|
||||
void *save_alloc(size_t size, void *def, void (*after_load)());
|
||||
void save_request(bool immediately);
|
||||
|
||||
#endif
|
42
firmware/src/sound.c
Normal file
42
firmware/src/sound.c
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Sound Feedback
|
||||
* WHowe <github.com/whowechina>
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sound.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "hardware/gpio.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "board_defs.h"
|
||||
|
||||
static const uint8_t sound_gpio[2] = SOUND_DEF;
|
||||
|
||||
void sound_init()
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
uint8_t gpio = sound_gpio[i];
|
||||
gpio_init(gpio);
|
||||
gpio_set_dir(gpio, GPIO_OUT);
|
||||
gpio_put(gpio, false);
|
||||
}
|
||||
}
|
||||
|
||||
void sound_set(int id, bool on)
|
||||
{
|
||||
if (!geki_cfg->sound.enabled) {
|
||||
gpio_put(sound_gpio[id], false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (id >= 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
gpio_put(sound_gpio[id], on);
|
||||
}
|
16
firmware/src/sound.h
Normal file
16
firmware/src/sound.h
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Sound Feedback
|
||||
* WHowe <github.com/whowechina>
|
||||
*/
|
||||
|
||||
#ifndef SOUND_H
|
||||
#define SOUND_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "hardware/flash.h"
|
||||
|
||||
void sound_init();
|
||||
void sound_set(int id, bool on);
|
||||
|
||||
#endif
|
124
firmware/src/tusb_config.h
Normal file
124
firmware/src/tusb_config.h
Normal file
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _TUSB_CONFIG_H_
|
||||
#define _TUSB_CONFIG_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// COMMON CONFIGURATION
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// defined by board.mk
|
||||
#ifndef CFG_TUSB_MCU
|
||||
#error CFG_TUSB_MCU must be defined
|
||||
#endif
|
||||
|
||||
// RHPort number used for device can be defined by board.mk, default to port 0
|
||||
#ifndef BOARD_DEVICE_RHPORT_NUM
|
||||
#define BOARD_DEVICE_RHPORT_NUM 0
|
||||
#endif
|
||||
|
||||
// RHPort max operational speed can defined by board.mk
|
||||
// Default to Highspeed for MCU with internal HighSpeed PHY (can be port
|
||||
// specific), otherwise FullSpeed
|
||||
#ifndef BOARD_DEVICE_RHPORT_SPEED
|
||||
#if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || \
|
||||
CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || CFG_TUSB_MCU == OPT_MCU_NUC505 || \
|
||||
CFG_TUSB_MCU == OPT_MCU_CXD56)
|
||||
#define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_HIGH_SPEED
|
||||
#else
|
||||
#define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Device mode with rhport and speed defined by board.mk
|
||||
#if BOARD_DEVICE_RHPORT_NUM == 0
|
||||
#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED)
|
||||
#elif BOARD_DEVICE_RHPORT_NUM == 1
|
||||
#define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED)
|
||||
#else
|
||||
#error "Incorrect RHPort configuration"
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_OS
|
||||
#define CFG_TUSB_OS OPT_OS_NONE
|
||||
#endif
|
||||
|
||||
// CFG_TUSB_DEBUG is defined by compiler in DEBUG build
|
||||
// #define CFG_TUSB_DEBUG 0
|
||||
|
||||
/* USB DMA on some MCUs can only access a specific SRAM region with restriction
|
||||
* on alignment. Tinyusb use follows macros to declare transferring memory so
|
||||
* that they can be put into those specific section. e.g
|
||||
* - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
|
||||
* - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
|
||||
*/
|
||||
#ifndef CFG_TUSB_MEM_SECTION
|
||||
#define CFG_TUSB_MEM_SECTION
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_MEM_ALIGN
|
||||
#define CFG_TUSB_MEM_ALIGN __attribute__((aligned(4)))
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// DEVICE CONFIGURATION
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
#ifndef CFG_TUD_ENDPOINT0_SIZE
|
||||
#define CFG_TUD_ENDPOINT0_SIZE 64
|
||||
#endif
|
||||
|
||||
//------------- CLASS -------------//
|
||||
#define CFG_TUD_HID 3
|
||||
#define CFG_TUD_CDC 1
|
||||
#define CFG_TUD_MSC 0
|
||||
#define CFG_TUD_MIDI 0
|
||||
#define CFG_TUD_VENDOR 0
|
||||
|
||||
// HID buffer size Should be sufficient to hold ID (if any) + Data
|
||||
#define CFG_TUD_HID_EP_BUFSIZE 64
|
||||
|
||||
#define CFG_TUD_VENDOR 0
|
||||
|
||||
// HID buffer size Should be sufficient to hold ID (if any) + Data
|
||||
#define CFG_TUD_HID_EP_BUFSIZE 64
|
||||
|
||||
// CDC FIFO size of TX and RX
|
||||
#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 128)
|
||||
#define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 128)
|
||||
|
||||
// CDC Endpoint transfer buffer size, more is faster
|
||||
#define CFG_TUD_CDC_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 128)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _TUSB_CONFIG_H_ */
|
227
firmware/src/usb_descriptors.c
Normal file
227
firmware/src/usb_descriptors.c
Normal file
@ -0,0 +1,227 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "usb_descriptors.h"
|
||||
|
||||
#include "tusb.h"
|
||||
|
||||
/* A combination of interfaces must have a unique product id, since PC will save
|
||||
* device driver after the first plug. Same VID/PID with different interface e.g
|
||||
* MSC (first), then CDC (later) will possibly cause system error on PC.
|
||||
*
|
||||
* Auto ProductID layout's Bitmap:
|
||||
* [MSB] HID | MSC | CDC [LSB]
|
||||
*/
|
||||
#define _PID_MAP(itf, n) ((CFG_TUD_##itf) << (n))
|
||||
#define USB_PID \
|
||||
(0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \
|
||||
_PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4))
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Device Descriptors
|
||||
//--------------------------------------------------------------------+
|
||||
tusb_desc_device_t desc_device_joy = {
|
||||
.bLength = sizeof(tusb_desc_device_t),
|
||||
.bDescriptorType = TUSB_DESC_DEVICE,
|
||||
.bcdUSB = 0x0200,
|
||||
.bDeviceClass = 0x00,
|
||||
.bDeviceSubClass = 0x00,
|
||||
.bDeviceProtocol = 0x00,
|
||||
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
|
||||
|
||||
.idVendor = 0x0f0d,
|
||||
.idProduct = 0x0092,
|
||||
.bcdDevice = 0x0100,
|
||||
|
||||
.iManufacturer = 1,
|
||||
.iProduct = 2,
|
||||
.iSerialNumber = 3,
|
||||
|
||||
.bNumConfigurations = 1
|
||||
};
|
||||
|
||||
// Invoked when received GET DEVICE DESCRIPTOR
|
||||
// Application return pointer to descriptor
|
||||
uint8_t const* tud_descriptor_device_cb(void) {
|
||||
return (uint8_t const*)&desc_device_joy;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// HID Report Descriptor
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
uint8_t const desc_hid_report_joy[] = {
|
||||
GEKI_PICO_REPORT_DESC_JOYSTICK,
|
||||
};
|
||||
|
||||
uint8_t const desc_hid_report_led[] = {
|
||||
GEKI_PICO_LED_HEADER,
|
||||
GEKI_PICO_LED_FOOTER
|
||||
};
|
||||
|
||||
uint8_t const desc_hid_report_nkro[] = {
|
||||
GEKI_PICO_REPORT_DESC_NKRO,
|
||||
};
|
||||
|
||||
// Invoked when received GET HID REPORT DESCRIPTOR
|
||||
// Application return pointer to descriptor
|
||||
// Descriptor contents must exist long enough for transfer to complete
|
||||
uint8_t const* tud_hid_descriptor_report_cb(uint8_t itf)
|
||||
{
|
||||
switch (itf) {
|
||||
case 0:
|
||||
return desc_hid_report_joy;
|
||||
case 1:
|
||||
return desc_hid_report_led;
|
||||
case 2:
|
||||
return desc_hid_report_nkro;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
//--------------------------------------------------------------------+
|
||||
// Configuration Descriptor
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
enum { ITF_NUM_JOY, ITF_NUM_LED, ITF_NUM_NKRO,
|
||||
ITF_NUM_CLI, ITF_NUM_CLI_DATA,
|
||||
ITF_NUM_TOTAL };
|
||||
|
||||
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + \
|
||||
TUD_HID_INOUT_DESC_LEN * 1 + \
|
||||
TUD_HID_DESC_LEN * 2 + \
|
||||
TUD_CDC_DESC_LEN * 1)
|
||||
|
||||
#define EPNUM_JOY_OUT 0x01
|
||||
#define EPNUM_JOY_IN 0x81
|
||||
|
||||
#define EPNUM_LED 0x86
|
||||
#define EPNUM_KEY 0x87
|
||||
|
||||
#define EPNUM_CLI_NOTIF 0x89
|
||||
#define EPNUM_CLI_OUT 0x0a
|
||||
#define EPNUM_CLI_IN 0x8a
|
||||
|
||||
#define EPNUM_AIME_NOTIF 0x8b
|
||||
#define EPNUM_AIME_OUT 0x0c
|
||||
#define EPNUM_AIME_IN 0x8c
|
||||
|
||||
uint8_t const desc_configuration_joy[] = {
|
||||
// Config number, interface count, string index, total length, attribute,
|
||||
// power in mA
|
||||
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN,
|
||||
TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 200),
|
||||
|
||||
// Interface number, string index, protocol, report descriptor len, EP In
|
||||
// address, size & polling interval
|
||||
TUD_HID_INOUT_DESCRIPTOR(ITF_NUM_JOY, 4, HID_ITF_PROTOCOL_NONE,
|
||||
sizeof(desc_hid_report_joy), EPNUM_JOY_OUT, EPNUM_JOY_IN,
|
||||
CFG_TUD_HID_EP_BUFSIZE, 1),
|
||||
|
||||
TUD_HID_DESCRIPTOR(ITF_NUM_LED, 5, HID_ITF_PROTOCOL_NONE,
|
||||
sizeof(desc_hid_report_led), EPNUM_LED,
|
||||
CFG_TUD_HID_EP_BUFSIZE, 4),
|
||||
|
||||
TUD_HID_DESCRIPTOR(ITF_NUM_NKRO, 6, HID_ITF_PROTOCOL_NONE,
|
||||
sizeof(desc_hid_report_nkro), EPNUM_KEY,
|
||||
CFG_TUD_HID_EP_BUFSIZE, 1),
|
||||
|
||||
TUD_CDC_DESCRIPTOR(ITF_NUM_CLI, 7, EPNUM_CLI_NOTIF,
|
||||
8, EPNUM_CLI_OUT, EPNUM_CLI_IN, 64),
|
||||
};
|
||||
|
||||
// Invoked when received GET CONFIGURATION DESCRIPTOR
|
||||
// Application return pointer to descriptor
|
||||
// Descriptor contents must exist long enough for transfer to complete
|
||||
uint8_t const* tud_descriptor_configuration_cb(uint8_t index) {
|
||||
return desc_configuration_joy;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// String Descriptors
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
// array of pointer to string descriptors
|
||||
const char *string_desc_arr[] = {
|
||||
(const char[]){0x09, 0x04}, // 0: is supported language is English (0x0409)
|
||||
"WHowe", // 1: Manufacturer
|
||||
"Geki Pico Controller", // 2: Product
|
||||
"123456", // 3: Serial
|
||||
"Geki Pico Joystick",
|
||||
"Geki Pico LED",
|
||||
"Geki Pico NKRO",
|
||||
"Geki Pico CLI Port",
|
||||
};
|
||||
|
||||
// Invoked when received GET STRING DESCRIPTOR request
|
||||
// Application return pointer to descriptor, whose contents must exist long
|
||||
// enough for transfer to complete
|
||||
uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid)
|
||||
{
|
||||
static uint16_t _desc_str[64];
|
||||
|
||||
if (index == 0) {
|
||||
memcpy(&_desc_str[1], string_desc_arr[0], 2);
|
||||
_desc_str[0] = (TUSB_DESC_STRING << 8) | (2 + 2);
|
||||
return _desc_str;
|
||||
}
|
||||
|
||||
const size_t base_num = sizeof(string_desc_arr) / sizeof(string_desc_arr[0]);
|
||||
const char *colors[] = {"Blue", "Red", "Green"};
|
||||
char str[64];
|
||||
|
||||
if (index < base_num) {
|
||||
strcpy(str, string_desc_arr[index]);
|
||||
} else if (index < base_num + 48 + 45) {
|
||||
const char *names[] = {"Key ", "Splitter "};
|
||||
int led = index - base_num;
|
||||
int id = led / 6 + 1;
|
||||
int type = led / 3 % 2;
|
||||
int brg = led % 3;
|
||||
sprintf(str, "%s%02d %s", names[type], id, colors[brg]);
|
||||
} else if (index < base_num + 48 + 45 + 18) {
|
||||
int led = index - base_num - 48 - 45;
|
||||
int id = led / 3 + 1;
|
||||
int brg = led % 3;
|
||||
sprintf(str, "Tower %02d %s", id, colors[brg]);
|
||||
} else {
|
||||
sprintf(str, "Unknown %d", index);
|
||||
}
|
||||
|
||||
uint8_t chr_count = strlen(str);
|
||||
if (chr_count > 63) {
|
||||
chr_count = 63;
|
||||
}
|
||||
|
||||
// Convert ASCII string into UTF-16
|
||||
for (uint8_t i = 0; i < chr_count; i++) {
|
||||
_desc_str[1 + i] = str[i];
|
||||
}
|
||||
|
||||
// first byte is length (including header), second byte is string type
|
||||
_desc_str[0] = (TUSB_DESC_STRING << 8) | (2 * chr_count + 2);
|
||||
|
||||
return _desc_str;
|
||||
}
|
112
firmware/src/usb_descriptors.h
Normal file
112
firmware/src/usb_descriptors.h
Normal file
@ -0,0 +1,112 @@
|
||||
#ifndef USB_DESCRIPTORS_H_
|
||||
#define USB_DESCRIPTORS_H_
|
||||
|
||||
#include "common/tusb_common.h"
|
||||
#include "device/usbd.h"
|
||||
|
||||
enum {
|
||||
REPORT_ID_JOYSTICK = 1,
|
||||
REPORT_ID_LED_SLIDER_16 = 4,
|
||||
REPORT_ID_LED_SLIDER_15 = 5,
|
||||
REPORT_ID_LED_TOWER_6 = 6,
|
||||
REPORT_ID_LED_COMPRESSED = 11,
|
||||
};
|
||||
|
||||
// because they are missing from tusb_hid.h
|
||||
#define HID_STRING_INDEX(x) HID_REPORT_ITEM(x, 7, RI_TYPE_LOCAL, 1)
|
||||
#define HID_STRING_INDEX_N(x, n) HID_REPORT_ITEM(x, 7, RI_TYPE_LOCAL, n)
|
||||
#define HID_STRING_MINIMUM(x) HID_REPORT_ITEM(x, 8, RI_TYPE_LOCAL, 1)
|
||||
#define HID_STRING_MINIMUM_N(x, n) HID_REPORT_ITEM(x, 8, RI_TYPE_LOCAL, n)
|
||||
#define HID_STRING_MAXIMUM(x) HID_REPORT_ITEM(x, 9, RI_TYPE_LOCAL, 1)
|
||||
#define HID_STRING_MAXIMUM_N(x, n) HID_REPORT_ITEM(x, 9, RI_TYPE_LOCAL, n)
|
||||
|
||||
// Joystick Report Descriptor Template - Based off Drewol/rp2040-gamecon
|
||||
// Button Map | X | Y
|
||||
//HID_REPORT_ID(REPORT_ID_JOYSTICK)
|
||||
|
||||
#define GEKI_PICO_REPORT_DESC_JOYSTICK \
|
||||
HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), \
|
||||
HID_USAGE(HID_USAGE_DESKTOP_GAMEPAD), \
|
||||
HID_COLLECTION(HID_COLLECTION_APPLICATION), \
|
||||
HID_LOGICAL_MIN(0), HID_LOGICAL_MAX(1), \
|
||||
HID_PHYSICAL_MIN(0), HID_PHYSICAL_MAX(1), \
|
||||
HID_REPORT_SIZE(1), HID_REPORT_COUNT(16), \
|
||||
HID_USAGE_PAGE(HID_USAGE_PAGE_BUTTON), \
|
||||
HID_USAGE_MIN(1), HID_USAGE_MAX(16), \
|
||||
HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \
|
||||
\
|
||||
HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), \
|
||||
HID_LOGICAL_MAX(7), \
|
||||
HID_PHYSICAL_MAX_N(315, 2), \
|
||||
HID_REPORT_SIZE(4), HID_REPORT_COUNT(1), \
|
||||
0x65, 0x14, /* Unit */ \
|
||||
HID_USAGE(HID_USAGE_DESKTOP_HAT_SWITCH), \
|
||||
HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE | HID_NO_NULL_POSITION),\
|
||||
0x65, 0x00, /* Unit None */ \
|
||||
HID_REPORT_COUNT(1), \
|
||||
HID_INPUT(HID_CONSTANT | HID_ARRAY | HID_ABSOLUTE), \
|
||||
\
|
||||
HID_LOGICAL_MAX_N(0xff, 2), HID_PHYSICAL_MAX_N(0xff, 2), /* Analog */ \
|
||||
HID_USAGE(HID_USAGE_DESKTOP_X), HID_USAGE(HID_USAGE_DESKTOP_Y), \
|
||||
HID_USAGE(HID_USAGE_DESKTOP_Z), HID_USAGE(HID_USAGE_DESKTOP_RZ), \
|
||||
HID_REPORT_SIZE(8), HID_REPORT_COUNT(4), \
|
||||
HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \
|
||||
\
|
||||
HID_USAGE_PAGE_N(HID_USAGE_PAGE_VENDOR, 2), \
|
||||
HID_USAGE(0x20), \
|
||||
HID_REPORT_COUNT(1), \
|
||||
HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \
|
||||
\
|
||||
HID_USAGE_N(0x2621, 2), \
|
||||
HID_REPORT_COUNT(8), \
|
||||
HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \
|
||||
HID_COLLECTION_END
|
||||
|
||||
//HID_USAGE_PAGE_N(9761, 2), HID_REPORT_COUNT(8), HID_OUTPUT(2),
|
||||
|
||||
#define GEKI_PICO_LED_HEADER \
|
||||
HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), HID_USAGE(0x00), \
|
||||
HID_COLLECTION(HID_COLLECTION_APPLICATION), \
|
||||
HID_REPORT_COUNT(1), HID_REPORT_SIZE(8), \
|
||||
HID_INPUT(HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE)
|
||||
|
||||
#define GEKI_PICO_LED_FOOTER \
|
||||
HID_COLLECTION_END
|
||||
|
||||
#define GEKI_PICO_REPORT_DESC_NKRO \
|
||||
HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), \
|
||||
HID_USAGE(HID_USAGE_DESKTOP_KEYBOARD), \
|
||||
HID_COLLECTION(HID_COLLECTION_APPLICATION), \
|
||||
/* Modifier */ \
|
||||
HID_REPORT_SIZE(1), \
|
||||
HID_REPORT_COUNT(8), \
|
||||
HID_USAGE_PAGE(HID_USAGE_PAGE_KEYBOARD), \
|
||||
HID_USAGE_MIN(224), \
|
||||
HID_USAGE_MAX(231), \
|
||||
HID_LOGICAL_MIN(0), \
|
||||
HID_LOGICAL_MAX(1), \
|
||||
HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \
|
||||
/* LED output that we don't care */ \
|
||||
HID_REPORT_COUNT(5), \
|
||||
HID_REPORT_SIZE(1), \
|
||||
HID_USAGE_PAGE(HID_USAGE_PAGE_LED), \
|
||||
HID_USAGE_MIN(1), \
|
||||
HID_USAGE_MAX(5), \
|
||||
HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \
|
||||
HID_REPORT_COUNT(1), \
|
||||
HID_REPORT_SIZE(3), \
|
||||
HID_OUTPUT(HID_CONSTANT), \
|
||||
/* Full Keyboard Bitmap */ \
|
||||
HID_REPORT_SIZE(1), \
|
||||
HID_REPORT_COUNT(120), \
|
||||
HID_LOGICAL_MIN(0), \
|
||||
HID_LOGICAL_MAX(1), \
|
||||
HID_USAGE_PAGE(HID_USAGE_PAGE_KEYBOARD), \
|
||||
HID_USAGE_MIN(0), \
|
||||
HID_USAGE_MAX(119), \
|
||||
HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \
|
||||
HID_COLLECTION_END
|
||||
|
||||
// HID_REPORT_ID(REPORT_ID_NKRO)
|
||||
|
||||
#endif /* USB_DESCRIPTORS_H_ */
|
775
firmware/src/vl53l0x.c
Normal file
775
firmware/src/vl53l0x.c
Normal file
@ -0,0 +1,775 @@
|
||||
/*
|
||||
* VL53L0X Distance measurement sensor
|
||||
* WHowe <github.com/whowechina>
|
||||
*
|
||||
* Most of this VL53L0X code is from https://github.com/pololu/vl53l0x-arduino
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "hardware/i2c.h"
|
||||
#include "board_defs.h"
|
||||
|
||||
#include "vl53l0x.h"
|
||||
|
||||
#define VL53L0X_DEF_ADDR 0x29
|
||||
|
||||
#define IO_TIMEOUT_US 1000
|
||||
#define TOF_WAIT_US 200000
|
||||
|
||||
// Decode VCSEL (vertical cavity surface emitting laser) pulse period in PCLKs
|
||||
#define decodeVcselPeriod(reg_val) (((reg_val) + 1) << 1)
|
||||
|
||||
// Encode VCSEL pulse period register value from period in PCLKs
|
||||
#define encodeVcselPeriod(period_pclks) (((period_pclks) >> 1) - 1)
|
||||
|
||||
// Calculate macro period in *nanoseconds* from VCSEL period in PCLKs
|
||||
// PLL_period_ps = 1655; macro_period_vclks = 2304
|
||||
#define calcMacroPeriod(vcsel_period_pclks) ((((uint32_t)2304 * (vcsel_period_pclks) * 1655) + 500) / 1000)
|
||||
|
||||
static struct {
|
||||
i2c_inst_t *port;
|
||||
uint8_t addr;
|
||||
uint8_t stop_variable; // read by init and used when starting measurement
|
||||
uint16_t range;
|
||||
uint32_t timing_budget_us;
|
||||
} instances[16] = { { i2c0, VL53L0X_DEF_ADDR } };
|
||||
|
||||
static int current_instance = 0;
|
||||
|
||||
#define INSTANCE_NUM (sizeof(instances) / sizeof(instances[0]))
|
||||
#define I2C_PORT instances[current_instance].port
|
||||
#define I2C_ADDR instances[current_instance].addr
|
||||
|
||||
// Write an 8-bit register
|
||||
void write_reg(uint8_t reg, uint8_t value)
|
||||
{
|
||||
uint8_t data[2] = { reg, value };
|
||||
i2c_write_blocking_until(I2C_PORT, I2C_ADDR, data, 2, false, time_us_64() + IO_TIMEOUT_US);
|
||||
}
|
||||
|
||||
// Write a 16-bit register
|
||||
void write_reg16(uint8_t reg, uint16_t value)
|
||||
{
|
||||
uint8_t data[3] = { reg, value >> 8, value & 0xff };
|
||||
i2c_write_blocking_until(I2C_PORT, I2C_ADDR, data, 3, false, time_us_64() + IO_TIMEOUT_US);
|
||||
}
|
||||
|
||||
static void write_reg_list(const uint16_t *list)
|
||||
{
|
||||
const uint16_t *regs = list + 1;
|
||||
for (int i = 0; i < *list; i++) {
|
||||
write_reg(regs[i] >> 8, regs[i] & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
// Read an 8-bit register
|
||||
uint8_t read_reg(uint8_t reg)
|
||||
{
|
||||
uint8_t value;
|
||||
i2c_write_blocking_until(I2C_PORT, I2C_ADDR, ®, 1, true, time_us_64() + IO_TIMEOUT_US);
|
||||
i2c_read_blocking_until(I2C_PORT, I2C_ADDR, &value, 1, false, time_us_64() + IO_TIMEOUT_US);
|
||||
return value;
|
||||
}
|
||||
|
||||
// Read a 16-bit register
|
||||
uint16_t read_reg16(uint8_t reg)
|
||||
{
|
||||
uint8_t value[2];
|
||||
i2c_write_blocking_until(I2C_PORT, I2C_ADDR, ®, 1, true, time_us_64() + IO_TIMEOUT_US);
|
||||
i2c_read_blocking_until(I2C_PORT, I2C_ADDR, value, 2, false, time_us_64() + IO_TIMEOUT_US);
|
||||
return (value[0] << 8) | value[1];
|
||||
}
|
||||
|
||||
// Write an arbitrary number of bytes from the given array to the sensor,
|
||||
// starting at the given register
|
||||
void write_many(uint8_t reg, const uint8_t *src, uint8_t len)
|
||||
{
|
||||
i2c_write_blocking_until(I2C_PORT, I2C_ADDR, ®, 1, true, time_us_64() + IO_TIMEOUT_US);
|
||||
i2c_write_blocking_until(I2C_PORT, I2C_ADDR, src, len, false, time_us_64() + IO_TIMEOUT_US);
|
||||
}
|
||||
|
||||
// Read an arbitrary number of bytes from the sensor, starting at the given
|
||||
// register, into the given array
|
||||
void read_many(uint8_t reg, uint8_t *dst, uint8_t len)
|
||||
{
|
||||
i2c_write_blocking_until(I2C_PORT, I2C_ADDR, ®, 1, true, time_us_64() + IO_TIMEOUT_US);
|
||||
i2c_read_blocking_until(I2C_PORT, I2C_ADDR, dst, len, false, time_us_64() + IO_TIMEOUT_US * len);
|
||||
}
|
||||
|
||||
|
||||
const uint16_t reg_mode1[] = { 4, 0x8800, 0x8001, 0xff01, 0x0000 };
|
||||
const uint16_t reg_mode2[] = { 3, 0x0001, 0xff00, 0x8000 };
|
||||
const uint16_t reg_spad0[] = { 4, 0x8001, 0xff01, 0x0000, 0xff06 };
|
||||
const uint16_t reg_spad1[] = { 5, 0xff07, 0x8101, 0x8001, 0x946b, 0x8300 };
|
||||
const uint16_t reg_spad2[] = { 4, 0xff01, 0x0001, 0xff00, 0x8000 };
|
||||
const uint16_t reg_spad[] = { 5, 0xff01, 0x4f00, 0x4e2c, 0xff00, 0xb6b4 };
|
||||
const uint16_t reg_tuning[] = { 80,
|
||||
0xff01, 0x0000, 0xff00, 0x0900, 0x1000, 0x1100, 0x2401, 0x25ff,
|
||||
0x7500, 0xff01, 0x4e2c, 0x4800, 0x3020, 0xff00, 0x3009, 0x5400,
|
||||
0x3104, 0x3203, 0x4083, 0x4625, 0x6000, 0x2700, 0x5006, 0x5100,
|
||||
0x5296, 0x5608, 0x5730, 0x6100, 0x6200, 0x6400, 0x6500, 0x66a0,
|
||||
0xff01, 0x2232, 0x4714, 0x49ff, 0x4a00, 0xff00, 0x7a0a, 0x7b00,
|
||||
0x7821, 0xff01, 0x2334, 0x4200, 0x44ff, 0x4526, 0x4605, 0x4040,
|
||||
0x0e06, 0x201a, 0x4340, 0xff00, 0x3403, 0x3544, 0xff01, 0x3104,
|
||||
0x4b09, 0x4c05, 0x4d04, 0xff00, 0x4400, 0x4520, 0x4708, 0x4828,
|
||||
0x6700, 0x7004, 0x7101, 0x72fe, 0x7600, 0x7700, 0xff01, 0x0d01,
|
||||
0xff00, 0x8001, 0x01f8, 0xff01, 0x8e01, 0x0001, 0xff00, 0x8000,
|
||||
};
|
||||
|
||||
void vl53l0x_init(unsigned index, i2c_inst_t *i2c_port, uint8_t i2c_addr)
|
||||
{
|
||||
if (index < INSTANCE_NUM) {
|
||||
instances[index].port = i2c_port;
|
||||
instances[index].addr = i2c_addr ? i2c_addr : VL53L0X_DEF_ADDR;
|
||||
}
|
||||
}
|
||||
|
||||
void vl53l0x_use(unsigned index)
|
||||
{
|
||||
if (index < INSTANCE_NUM) {
|
||||
current_instance = index;
|
||||
}
|
||||
}
|
||||
|
||||
bool vl53l0x_is_present()
|
||||
{
|
||||
return read_reg(IDENTIFICATION_MODEL_ID) == 0xEE;
|
||||
}
|
||||
|
||||
bool vl53l0x_init_tof()
|
||||
{
|
||||
write_reg(VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV,
|
||||
read_reg(VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01);
|
||||
|
||||
write_reg_list(reg_mode1);
|
||||
instances[current_instance].stop_variable = read_reg(0x91);
|
||||
write_reg_list(reg_mode2);
|
||||
|
||||
// disable SIGNAL_RATE_MSRC (bit 1) and SIGNAL_RATE_PRE_RANGE (bit 4) limit checks
|
||||
write_reg(MSRC_CONFIG_CONTROL, read_reg(MSRC_CONFIG_CONTROL) | 0x12);
|
||||
|
||||
// Q9.7 fixed point format (9 integer bits, 7 fractional bits)
|
||||
write_reg16(FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT, 32);
|
||||
|
||||
write_reg(SYSTEM_SEQUENCE_CONFIG, 0xFF);
|
||||
|
||||
uint8_t spad_count;
|
||||
bool is_aperture;
|
||||
if (!getSpadInfo(&spad_count, &is_aperture)) {
|
||||
printf("%d\n", __LINE__);
|
||||
return false;
|
||||
}
|
||||
|
||||
// The SPAD map (RefGoodSpadMap) is read by VL53L0X_get_info_from_device() in
|
||||
// the API, but the same data seems to be more easily readable from
|
||||
// GLOBAL_CONFIG_SPAD_ENABLES_REF_0 through _6, so read it from there
|
||||
uint8_t ref_spad_map[6];
|
||||
read_many(GLOBAL_CONFIG_SPAD_ENABLES_REF_0, ref_spad_map, 6);
|
||||
write_reg_list(reg_spad);
|
||||
|
||||
uint8_t first_spad = is_aperture ? 12 : 0; // 12 is the first aperture spad
|
||||
uint8_t spads_enabled = 0;
|
||||
|
||||
for (int i = 0; i < 48; i++) {
|
||||
if (i < first_spad || spads_enabled == spad_count) {
|
||||
// This bit is lower than the first one that should be enabled, or
|
||||
// (reference_spad_count) bits have already been enabled, so zero this bit
|
||||
ref_spad_map[i / 8] &= ~(1 << (i % 8));
|
||||
} else if (ref_spad_map[i / 8] & (1 << i % 8)) {
|
||||
spads_enabled++;
|
||||
}
|
||||
}
|
||||
|
||||
write_many(GLOBAL_CONFIG_SPAD_ENABLES_REF_0, ref_spad_map, 6);
|
||||
|
||||
write_reg_list(reg_tuning);
|
||||
|
||||
write_reg(SYSTEM_INTERRUPT_CONFIG_GPIO, 0x04);
|
||||
write_reg(GPIO_HV_MUX_ACTIVE_HIGH, read_reg(GPIO_HV_MUX_ACTIVE_HIGH) & ~0x10); // active low
|
||||
write_reg(SYSTEM_INTERRUPT_CLEAR, 0x01);
|
||||
|
||||
instances[current_instance].timing_budget_us = getMeasurementTimingBudget();
|
||||
write_reg(SYSTEM_SEQUENCE_CONFIG, 0xE8);
|
||||
setMeasurementTimingBudget(instances[current_instance].timing_budget_us);
|
||||
|
||||
write_reg(SYSTEM_SEQUENCE_CONFIG, 0x01);
|
||||
if (!performSingleRefCalibration(0x40)) {
|
||||
printf("%d\n", __LINE__);
|
||||
return false;
|
||||
}
|
||||
|
||||
write_reg(SYSTEM_SEQUENCE_CONFIG, 0x02);
|
||||
if (!performSingleRefCalibration(0x00)) {
|
||||
printf("%d\n", __LINE__);
|
||||
return false;
|
||||
}
|
||||
|
||||
write_reg(SYSTEM_SEQUENCE_CONFIG, 0xE8);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the return signal rate limit check value in MCPS
|
||||
float getSignalRateLimit()
|
||||
{
|
||||
return (float)read_reg16(FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT) / (1 << 7);
|
||||
}
|
||||
|
||||
// Decode sequence step timeout in MCLKs from register value
|
||||
// based on VL53L0X_decode_timeout()
|
||||
// Note: the original function returned a uint32_t, but the return value is
|
||||
// always stored in a uint16_t.
|
||||
uint16_t decodeTimeout(uint16_t reg_val)
|
||||
{
|
||||
// format: "(LSByte * 2^MSByte) + 1"
|
||||
return (uint16_t)((reg_val & 0x00FF) <<
|
||||
(uint16_t)((reg_val & 0xFF00) >> 8)) + 1;
|
||||
}
|
||||
|
||||
// Encode sequence step timeout register value from timeout in MCLKs
|
||||
// based on VL53L0X_encode_timeout()
|
||||
static uint16_t encodeTimeout(uint16_t timeout_mclks)
|
||||
{
|
||||
// format: "(LSByte * 2^MSByte) + 1"
|
||||
uint32_t ls_byte = 0;
|
||||
uint16_t ms_byte = 0;
|
||||
|
||||
if (timeout_mclks > 0) {
|
||||
ls_byte = timeout_mclks - 1;
|
||||
|
||||
while ((ls_byte & 0xFFFFFF00) > 0) {
|
||||
ls_byte >>= 1;
|
||||
ms_byte++;
|
||||
}
|
||||
|
||||
return (ms_byte << 8) | (ls_byte & 0xFF);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Convert sequence step timeout from MCLKs to microseconds with given VCSEL period in PCLKs
|
||||
// based on VL53L0X_calc_timeout_us()
|
||||
uint32_t timeoutMclksToMicroseconds(uint16_t timeout_period_mclks, uint8_t vcsel_period_pclks)
|
||||
{
|
||||
uint32_t macro_period_ns = calcMacroPeriod(vcsel_period_pclks);
|
||||
|
||||
return ((timeout_period_mclks * macro_period_ns) + 500) / 1000;
|
||||
}
|
||||
|
||||
// Convert sequence step timeout from microseconds to MCLKs with given VCSEL period in PCLKs
|
||||
// based on VL53L0X_calc_timeout_mclks()
|
||||
uint32_t timeoutMicrosecondsToMclks(uint32_t timeout_period_us, uint8_t vcsel_period_pclks)
|
||||
{
|
||||
uint32_t macro_period_ns = calcMacroPeriod(vcsel_period_pclks);
|
||||
|
||||
return (((timeout_period_us * 1000) + (macro_period_ns / 2)) / macro_period_ns);
|
||||
}
|
||||
|
||||
// Set the measurement timing budget in microseconds, which is the time allowed
|
||||
// for one measurement; the ST API and this library take care of splitting the
|
||||
// timing budget among the sub-steps in the ranging sequence. A longer timing
|
||||
// budget allows for more accurate measurements. Increasing the budget by a
|
||||
// factor of N decreases the range measurement standard deviation by a factor of
|
||||
// sqrt(N). Defaults to about 33 milliseconds; the minimum is 20 ms.
|
||||
// based on VL53L0X_set_measurement_timing_budget_micro_seconds()
|
||||
bool setMeasurementTimingBudget(uint32_t budget_us)
|
||||
{
|
||||
SequenceStepEnables enables;
|
||||
SequenceStepTimeouts timeouts;
|
||||
|
||||
uint16_t const StartOverhead = 1320; // different than the value in get_
|
||||
uint16_t const EndOverhead = 960;
|
||||
uint16_t const MsrcOverhead = 660;
|
||||
uint16_t const TccOverhead = 590;
|
||||
uint16_t const DssOverhead = 690;
|
||||
uint16_t const PreRangeOverhead = 660;
|
||||
uint16_t const FinalRangeOverhead = 550;
|
||||
|
||||
uint32_t const MinTimingBudget = 20000;
|
||||
|
||||
if (budget_us < MinTimingBudget) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t used_budget_us = StartOverhead + EndOverhead;
|
||||
|
||||
getSequenceStepEnables(&enables);
|
||||
getSequenceStepTimeouts(&enables, &timeouts);
|
||||
|
||||
if (enables.tcc) {
|
||||
used_budget_us += (timeouts.msrc_dss_tcc_us + TccOverhead);
|
||||
}
|
||||
|
||||
if (enables.dss) {
|
||||
used_budget_us += 2 * (timeouts.msrc_dss_tcc_us + DssOverhead);
|
||||
} else if (enables.msrc) {
|
||||
used_budget_us += (timeouts.msrc_dss_tcc_us + MsrcOverhead);
|
||||
}
|
||||
|
||||
if (enables.pre_range) {
|
||||
used_budget_us += (timeouts.pre_range_us + PreRangeOverhead);
|
||||
}
|
||||
|
||||
if (enables.final_range) {
|
||||
used_budget_us += FinalRangeOverhead;
|
||||
|
||||
// "Note that the final range timeout is determined by the timing
|
||||
// budget and the sum of all other timeouts within the sequence.
|
||||
// If there is no room for the final range timeout, then an error
|
||||
// will be set. Otherwise the remaining time will be applied to
|
||||
// the final range."
|
||||
|
||||
if (used_budget_us > budget_us)
|
||||
{
|
||||
// "Requested timeout too big."
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t final_range_timeout_us = budget_us - used_budget_us;
|
||||
|
||||
// set_sequence_step_timeout() begin
|
||||
// (SequenceStepId == VL53L0X_SEQUENCESTEP_FINAL_RANGE)
|
||||
|
||||
// "For the final range timeout, the pre-range timeout
|
||||
// must be added. To do this both final and pre-range
|
||||
// timeouts must be expressed in macro periods MClks
|
||||
// because they have different vcsel periods."
|
||||
|
||||
uint32_t final_range_timeout_mclks =
|
||||
timeoutMicrosecondsToMclks(final_range_timeout_us,
|
||||
timeouts.final_range_vcsel_period_pclks);
|
||||
|
||||
if (enables.pre_range) {
|
||||
final_range_timeout_mclks += timeouts.pre_range_mclks;
|
||||
}
|
||||
|
||||
write_reg16(FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI,
|
||||
encodeTimeout(final_range_timeout_mclks));
|
||||
|
||||
// set_sequence_step_timeout() end
|
||||
|
||||
instances[current_instance].timing_budget_us = budget_us; // store for internal reuse
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the measurement timing budget in microseconds
|
||||
// based on VL53L0X_get_measurement_timing_budget_micro_seconds()
|
||||
// in us
|
||||
uint32_t getMeasurementTimingBudget()
|
||||
{
|
||||
SequenceStepEnables enables;
|
||||
SequenceStepTimeouts timeouts;
|
||||
|
||||
uint16_t const StartOverhead = 1910;
|
||||
uint16_t const EndOverhead = 960;
|
||||
uint16_t const MsrcOverhead = 660;
|
||||
uint16_t const TccOverhead = 590;
|
||||
uint16_t const DssOverhead = 690;
|
||||
uint16_t const PreRangeOverhead = 660;
|
||||
uint16_t const FinalRangeOverhead = 550;
|
||||
|
||||
// "Start and end overhead times always present"
|
||||
uint32_t budget_us = StartOverhead + EndOverhead;
|
||||
|
||||
getSequenceStepEnables(&enables);
|
||||
getSequenceStepTimeouts(&enables, &timeouts);
|
||||
|
||||
if (enables.tcc) {
|
||||
budget_us += (timeouts.msrc_dss_tcc_us + TccOverhead);
|
||||
}
|
||||
|
||||
if (enables.dss) {
|
||||
budget_us += 2 * (timeouts.msrc_dss_tcc_us + DssOverhead);
|
||||
} else if (enables.msrc) {
|
||||
budget_us += (timeouts.msrc_dss_tcc_us + MsrcOverhead);
|
||||
}
|
||||
|
||||
if (enables.pre_range) {
|
||||
budget_us += (timeouts.pre_range_us + PreRangeOverhead);
|
||||
}
|
||||
|
||||
if (enables.final_range) {
|
||||
budget_us += (timeouts.final_range_us + FinalRangeOverhead);
|
||||
}
|
||||
|
||||
instances[current_instance].timing_budget_us = budget_us; // cache for reuse
|
||||
return budget_us;
|
||||
}
|
||||
|
||||
// Set the VCSEL (vertical cavity surface emitting laser) pulse period for the
|
||||
// given period type (pre-range or final range) to the given value in PCLKs.
|
||||
// Longer periods seem to increase the potential range of the sensor.
|
||||
// Valid values are (even numbers only):
|
||||
// pre: 12 to 18 (initialized default: 14)
|
||||
// final: 8 to 14 (initialized default: 10)
|
||||
// based on VL53L0X_set_vcsel_pulse_period()
|
||||
bool setVcselPulsePeriod(vcselPeriodType type, uint8_t period_pclks)
|
||||
{
|
||||
uint8_t vcsel_period_reg = encodeVcselPeriod(period_pclks);
|
||||
|
||||
SequenceStepEnables enables;
|
||||
SequenceStepTimeouts timeouts;
|
||||
|
||||
getSequenceStepEnables(&enables);
|
||||
getSequenceStepTimeouts(&enables, &timeouts);
|
||||
|
||||
// "Apply specific settings for the requested clock period"
|
||||
// "Re-calculate and apply timeouts, in macro periods"
|
||||
|
||||
// "When the VCSEL period for the pre or final range is changed,
|
||||
// the corresponding timeout must be read from the device using
|
||||
// the current VCSEL period, then the new VCSEL period can be
|
||||
// applied. The timeout then must be written back to the device
|
||||
// using the new VCSEL period.
|
||||
//
|
||||
// For the MSRC timeout, the same applies - this timeout being
|
||||
// dependant on the pre-range vcsel period."
|
||||
|
||||
|
||||
if (type == VcselPeriodPreRange) {
|
||||
// "Set phase check limits"
|
||||
switch (period_pclks) {
|
||||
case 12:
|
||||
write_reg(PRE_RANGE_CONFIG_VALID_PHASE_HIGH, 0x18);
|
||||
break;
|
||||
|
||||
case 14:
|
||||
write_reg(PRE_RANGE_CONFIG_VALID_PHASE_HIGH, 0x30);
|
||||
break;
|
||||
|
||||
case 16:
|
||||
write_reg(PRE_RANGE_CONFIG_VALID_PHASE_HIGH, 0x40);
|
||||
break;
|
||||
|
||||
case 18:
|
||||
write_reg(PRE_RANGE_CONFIG_VALID_PHASE_HIGH, 0x50);
|
||||
break;
|
||||
|
||||
default:
|
||||
// invalid period
|
||||
return false;
|
||||
}
|
||||
write_reg(PRE_RANGE_CONFIG_VALID_PHASE_LOW, 0x08);
|
||||
|
||||
// apply new VCSEL period
|
||||
write_reg(PRE_RANGE_CONFIG_VCSEL_PERIOD, vcsel_period_reg);
|
||||
|
||||
// update timeouts
|
||||
|
||||
// set_sequence_step_timeout() begin
|
||||
// (SequenceStepId == VL53L0X_SEQUENCESTEP_PRE_RANGE)
|
||||
|
||||
uint16_t new_pre_range_timeout_mclks =
|
||||
timeoutMicrosecondsToMclks(timeouts.pre_range_us, period_pclks);
|
||||
|
||||
write_reg16(PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI,
|
||||
encodeTimeout(new_pre_range_timeout_mclks));
|
||||
|
||||
// set_sequence_step_timeout() end
|
||||
|
||||
// set_sequence_step_timeout() begin
|
||||
// (SequenceStepId == VL53L0X_SEQUENCESTEP_MSRC)
|
||||
|
||||
uint16_t new_msrc_timeout_mclks =
|
||||
timeoutMicrosecondsToMclks(timeouts.msrc_dss_tcc_us, period_pclks);
|
||||
|
||||
write_reg(MSRC_CONFIG_TIMEOUT_MACROP,
|
||||
(new_msrc_timeout_mclks > 256) ? 255 : (new_msrc_timeout_mclks - 1));
|
||||
|
||||
// set_sequence_step_timeout() end
|
||||
} else if (type == VcselPeriodFinalRange) {
|
||||
switch (period_pclks) {
|
||||
case 8:
|
||||
write_reg(FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, 0x10);
|
||||
write_reg(FINAL_RANGE_CONFIG_VALID_PHASE_LOW, 0x08);
|
||||
write_reg(GLOBAL_CONFIG_VCSEL_WIDTH, 0x02);
|
||||
write_reg(ALGO_PHASECAL_CONFIG_TIMEOUT, 0x0C);
|
||||
write_reg(0xFF, 0x01);
|
||||
write_reg(ALGO_PHASECAL_LIM, 0x30);
|
||||
write_reg(0xFF, 0x00);
|
||||
break;
|
||||
|
||||
case 10:
|
||||
write_reg(FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, 0x28);
|
||||
write_reg(FINAL_RANGE_CONFIG_VALID_PHASE_LOW, 0x08);
|
||||
write_reg(GLOBAL_CONFIG_VCSEL_WIDTH, 0x03);
|
||||
write_reg(ALGO_PHASECAL_CONFIG_TIMEOUT, 0x09);
|
||||
write_reg(0xFF, 0x01);
|
||||
write_reg(ALGO_PHASECAL_LIM, 0x20);
|
||||
write_reg(0xFF, 0x00);
|
||||
break;
|
||||
|
||||
case 12:
|
||||
write_reg(FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, 0x38);
|
||||
write_reg(FINAL_RANGE_CONFIG_VALID_PHASE_LOW, 0x08);
|
||||
write_reg(GLOBAL_CONFIG_VCSEL_WIDTH, 0x03);
|
||||
write_reg(ALGO_PHASECAL_CONFIG_TIMEOUT, 0x08);
|
||||
write_reg(0xFF, 0x01);
|
||||
write_reg(ALGO_PHASECAL_LIM, 0x20);
|
||||
write_reg(0xFF, 0x00);
|
||||
break;
|
||||
|
||||
case 14:
|
||||
write_reg(FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, 0x48);
|
||||
write_reg(FINAL_RANGE_CONFIG_VALID_PHASE_LOW, 0x08);
|
||||
write_reg(GLOBAL_CONFIG_VCSEL_WIDTH, 0x03);
|
||||
write_reg(ALGO_PHASECAL_CONFIG_TIMEOUT, 0x07);
|
||||
write_reg(0xFF, 0x01);
|
||||
write_reg(ALGO_PHASECAL_LIM, 0x20);
|
||||
write_reg(0xFF, 0x00);
|
||||
break;
|
||||
|
||||
default:
|
||||
// invalid period
|
||||
return false;
|
||||
}
|
||||
|
||||
// apply new VCSEL period
|
||||
write_reg(FINAL_RANGE_CONFIG_VCSEL_PERIOD, vcsel_period_reg);
|
||||
|
||||
// update timeouts
|
||||
|
||||
// set_sequence_step_timeout() begin
|
||||
// (SequenceStepId == VL53L0X_SEQUENCESTEP_FINAL_RANGE)
|
||||
|
||||
// "For the final range timeout, the pre-range timeout
|
||||
// must be added. To do this both final and pre-range
|
||||
// timeouts must be expressed in macro periods MClks
|
||||
// because they have different vcsel periods."
|
||||
|
||||
uint16_t new_final_range_timeout_mclks =
|
||||
timeoutMicrosecondsToMclks(timeouts.final_range_us, period_pclks);
|
||||
|
||||
if (enables.pre_range) {
|
||||
new_final_range_timeout_mclks += timeouts.pre_range_mclks;
|
||||
}
|
||||
|
||||
write_reg16(FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI,
|
||||
encodeTimeout(new_final_range_timeout_mclks));
|
||||
|
||||
// set_sequence_step_timeout end
|
||||
}
|
||||
else {
|
||||
// invalid type
|
||||
return false;
|
||||
}
|
||||
|
||||
// "Finally, the timing budget must be re-applied"
|
||||
|
||||
setMeasurementTimingBudget(instances[current_instance].timing_budget_us);
|
||||
|
||||
// "Perform the phase calibration. This is needed after changing on vcsel period."
|
||||
// VL53L0X_perform_phase_calibration() begin
|
||||
|
||||
uint8_t sequence_config = read_reg(SYSTEM_SEQUENCE_CONFIG);
|
||||
write_reg(SYSTEM_SEQUENCE_CONFIG, 0x02);
|
||||
performSingleRefCalibration(0x0);
|
||||
write_reg(SYSTEM_SEQUENCE_CONFIG, sequence_config);
|
||||
|
||||
// VL53L0X_perform_phase_calibration() end
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the VCSEL pulse period in PCLKs for the given period type.
|
||||
// based on VL53L0X_get_vcsel_pulse_period()
|
||||
uint8_t getVcselPulsePeriod(vcselPeriodType type)
|
||||
{
|
||||
if (type == VcselPeriodPreRange)
|
||||
{
|
||||
return decodeVcselPeriod(read_reg(PRE_RANGE_CONFIG_VCSEL_PERIOD));
|
||||
}
|
||||
else if (type == VcselPeriodFinalRange)
|
||||
{
|
||||
return decodeVcselPeriod(read_reg(FINAL_RANGE_CONFIG_VCSEL_PERIOD));
|
||||
}
|
||||
else { return 255; }
|
||||
}
|
||||
|
||||
// Start continuous ranging measurements.
|
||||
// based on VL53L0X_StartMeasurement()
|
||||
void vl53l0x_start_continuous()
|
||||
{
|
||||
write_reg(0x80, 0x01);
|
||||
write_reg(0xFF, 0x01);
|
||||
write_reg(0x00, 0x00);
|
||||
write_reg(0x91, instances[current_instance].stop_variable);
|
||||
write_reg(0x00, 0x01);
|
||||
write_reg(0xFF, 0x00);
|
||||
write_reg(0x80, 0x00);
|
||||
|
||||
write_reg(SYSRANGE_START, 0x02); // VL53L0X_REG_SYSRANGE_MODE_BACKTOBACK
|
||||
}
|
||||
|
||||
// Stop continuous measurements
|
||||
// based on VL53L0X_StopMeasurement()
|
||||
void vl53l0x_stop_continuous()
|
||||
{
|
||||
write_reg(SYSRANGE_START, 0x01); // VL53L0X_REG_SYSRANGE_MODE_SINGLESHOT
|
||||
|
||||
write_reg(0xFF, 0x01);
|
||||
write_reg(0x00, 0x00);
|
||||
write_reg(0x91, 0x00);
|
||||
write_reg(0x00, 0x01);
|
||||
write_reg(0xFF, 0x00);
|
||||
}
|
||||
|
||||
// Returns a range reading in millimeters when continuous mode is active
|
||||
// (readRangeSingleMillimeters() also calls this function after starting a
|
||||
// single-shot range measurement)
|
||||
uint16_t readRangeContinuousMillimeters()
|
||||
{
|
||||
if ((read_reg(RESULT_INTERRUPT_STATUS) & 0x07) == 0) {
|
||||
return instances[current_instance].range; // use last result
|
||||
}
|
||||
|
||||
// assumptions: Linearity Corrective Gain is 1000 (default);
|
||||
// fractional ranging is not enabled
|
||||
instances[current_instance].range = read_reg16(RESULT_RANGE_STATUS + 10);
|
||||
|
||||
write_reg(SYSTEM_INTERRUPT_CLEAR, 0x01);
|
||||
|
||||
return instances[current_instance].range;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// Performs a single-shot range measurement and returns the reading in
|
||||
// millimeters
|
||||
// based on VL53L0X_PerformSingleRangingMeasurement()
|
||||
uint16_t readRangeSingleMillimeters()
|
||||
{
|
||||
static uint16_t range = 65535;
|
||||
static bool reading = false;
|
||||
static uint64_t start_time = 0;
|
||||
|
||||
uint64_t now = time_us_64();
|
||||
if (now - start_time > TOF_WAIT_US) {
|
||||
reading = false;
|
||||
}
|
||||
|
||||
if (reading) {
|
||||
if ((read_reg(SYSRANGE_START) & 0x01) == 0) {
|
||||
range = readRangeContinuousMillimeters(index);
|
||||
reading = false;
|
||||
}
|
||||
} else {
|
||||
write_reg(0x80, 0x01);
|
||||
write_reg(0xFF, 0x01);
|
||||
write_reg(0x00, 0x00);
|
||||
write_reg(0x91, instances[index].stop_variable);
|
||||
write_reg(0x00, 0x01);
|
||||
write_reg(0xFF, 0x00);
|
||||
write_reg(0x80, 0x00);
|
||||
|
||||
write_reg(SYSRANGE_START, 0x01);
|
||||
start_time = now;
|
||||
reading = true;
|
||||
}
|
||||
return range;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Private Methods /////////////////////////////////////////////////////////////
|
||||
|
||||
// Get reference SPAD (single photon avalanche diode) count and type
|
||||
// based on VL53L0X_get_info_from_device(),
|
||||
// but only gets reference SPAD count and type
|
||||
bool getSpadInfo(uint8_t *count, bool *type_is_aperture)
|
||||
{
|
||||
write_reg_list(reg_spad0);
|
||||
write_reg(0x83, read_reg(0x83) | 0x04);
|
||||
write_reg_list(reg_spad1);
|
||||
|
||||
uint64_t start = time_us_64();
|
||||
while (read_reg(0x83) == 0x00) {
|
||||
if (time_us_64() - start > TOF_WAIT_US) {
|
||||
return false;
|
||||
}
|
||||
sleep_ms(1);
|
||||
}
|
||||
|
||||
write_reg(0x83, 0x01);
|
||||
|
||||
uint8_t tmp = read_reg(0x92);
|
||||
*count = tmp & 0x7f;
|
||||
*type_is_aperture = (tmp & 0x80);
|
||||
|
||||
write_reg(0x81, 0x00);
|
||||
write_reg(0xFF, 0x06);
|
||||
|
||||
write_reg(0x83, read_reg(0x83) & ~0x04);
|
||||
|
||||
write_reg_list(reg_spad2);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get sequence step enables
|
||||
// based on VL53L0X_GetSequenceStepEnables()
|
||||
void getSequenceStepEnables(SequenceStepEnables * enables)
|
||||
{
|
||||
uint8_t seq_cfg = read_reg(SYSTEM_SEQUENCE_CONFIG);
|
||||
|
||||
enables->tcc = (seq_cfg >> 4) & 0x1;
|
||||
enables->dss = (seq_cfg >> 3) & 0x1;
|
||||
enables->msrc = (seq_cfg >> 2) & 0x1;
|
||||
enables->pre_range = (seq_cfg >> 6) & 0x1;
|
||||
enables->final_range = (seq_cfg >> 7) & 0x1;
|
||||
}
|
||||
|
||||
// Get sequence step timeouts
|
||||
// based on get_sequence_step_timeout(),
|
||||
// but gets all timeouts instead of just the requested one, and also stores
|
||||
// intermediate values
|
||||
void getSequenceStepTimeouts(SequenceStepEnables const * enables, SequenceStepTimeouts * timeouts)
|
||||
{
|
||||
timeouts->pre_range_vcsel_period_pclks = getVcselPulsePeriod(VcselPeriodPreRange);
|
||||
|
||||
timeouts->msrc_dss_tcc_mclks = read_reg(MSRC_CONFIG_TIMEOUT_MACROP) + 1;
|
||||
timeouts->msrc_dss_tcc_us =
|
||||
timeoutMclksToMicroseconds(timeouts->msrc_dss_tcc_mclks,
|
||||
timeouts->pre_range_vcsel_period_pclks);
|
||||
|
||||
timeouts->pre_range_mclks =
|
||||
decodeTimeout(read_reg16(PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI));
|
||||
timeouts->pre_range_us =
|
||||
timeoutMclksToMicroseconds(timeouts->pre_range_mclks,
|
||||
timeouts->pre_range_vcsel_period_pclks);
|
||||
|
||||
timeouts->final_range_vcsel_period_pclks = getVcselPulsePeriod(VcselPeriodFinalRange);
|
||||
|
||||
timeouts->final_range_mclks =
|
||||
decodeTimeout(read_reg16(FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI));
|
||||
|
||||
if (enables->pre_range) {
|
||||
timeouts->final_range_mclks -= timeouts->pre_range_mclks;
|
||||
}
|
||||
|
||||
timeouts->final_range_us =
|
||||
timeoutMclksToMicroseconds(timeouts->final_range_mclks,
|
||||
timeouts->final_range_vcsel_period_pclks);
|
||||
}
|
||||
|
||||
// based on VL53L0X_perform_single_ref_calibration()
|
||||
bool performSingleRefCalibration(uint8_t vhv_init_byte)
|
||||
{
|
||||
write_reg(SYSRANGE_START, 0x01 | vhv_init_byte); // VL53L0X_REG_SYSRANGE_MODE_START_STOP
|
||||
|
||||
uint64_t start = time_us_64();
|
||||
while ((read_reg(RESULT_INTERRUPT_STATUS) & 0x07) == 0) {
|
||||
if (time_us_64() - start > TOF_WAIT_US) {
|
||||
return false;
|
||||
}
|
||||
sleep_ms(1);
|
||||
}
|
||||
|
||||
write_reg(SYSTEM_INTERRUPT_CLEAR, 0x01);
|
||||
write_reg(SYSRANGE_START, 0x00);
|
||||
|
||||
return true;
|
||||
}
|
148
firmware/src/vl53l0x.h
Normal file
148
firmware/src/vl53l0x.h
Normal file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* VL53L0X Distance measurement sensor
|
||||
* WHowe <github.com/whowechina>
|
||||
*
|
||||
* Most of this VL53L0X code is from https://github.com/pololu/vl53l0x-arduino
|
||||
*/
|
||||
|
||||
#ifndef VL53L0X_H
|
||||
#define VL53L0X_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "hardware/i2c.h"
|
||||
|
||||
// register addresses from API vl53l0x_device.h (ordered as listed there)
|
||||
enum regAddr
|
||||
{
|
||||
SYSRANGE_START = 0x00,
|
||||
|
||||
SYSTEM_THRESH_HIGH = 0x0C,
|
||||
SYSTEM_THRESH_LOW = 0x0E,
|
||||
|
||||
SYSTEM_SEQUENCE_CONFIG = 0x01,
|
||||
SYSTEM_RANGE_CONFIG = 0x09,
|
||||
SYSTEM_INTERMEASUREMENT_PERIOD = 0x04,
|
||||
|
||||
SYSTEM_INTERRUPT_CONFIG_GPIO = 0x0A,
|
||||
|
||||
GPIO_HV_MUX_ACTIVE_HIGH = 0x84,
|
||||
|
||||
SYSTEM_INTERRUPT_CLEAR = 0x0B,
|
||||
|
||||
RESULT_INTERRUPT_STATUS = 0x13,
|
||||
RESULT_RANGE_STATUS = 0x14,
|
||||
|
||||
RESULT_CORE_AMBIENT_WINDOW_EVENTS_RTN = 0xBC,
|
||||
RESULT_CORE_RANGING_TOTAL_EVENTS_RTN = 0xC0,
|
||||
RESULT_CORE_AMBIENT_WINDOW_EVENTS_REF = 0xD0,
|
||||
RESULT_CORE_RANGING_TOTAL_EVENTS_REF = 0xD4,
|
||||
RESULT_PEAK_SIGNAL_RATE_REF = 0xB6,
|
||||
|
||||
ALGO_PART_TO_PART_RANGE_OFFSET_MM = 0x28,
|
||||
|
||||
I2C_SLAVE_DEVICE_ADDRESS = 0x8A,
|
||||
|
||||
MSRC_CONFIG_CONTROL = 0x60,
|
||||
|
||||
PRE_RANGE_CONFIG_MIN_SNR = 0x27,
|
||||
PRE_RANGE_CONFIG_VALID_PHASE_LOW = 0x56,
|
||||
PRE_RANGE_CONFIG_VALID_PHASE_HIGH = 0x57,
|
||||
PRE_RANGE_MIN_COUNT_RATE_RTN_LIMIT = 0x64,
|
||||
|
||||
FINAL_RANGE_CONFIG_MIN_SNR = 0x67,
|
||||
FINAL_RANGE_CONFIG_VALID_PHASE_LOW = 0x47,
|
||||
FINAL_RANGE_CONFIG_VALID_PHASE_HIGH = 0x48,
|
||||
FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT = 0x44,
|
||||
|
||||
PRE_RANGE_CONFIG_SIGMA_THRESH_HI = 0x61,
|
||||
PRE_RANGE_CONFIG_SIGMA_THRESH_LO = 0x62,
|
||||
|
||||
PRE_RANGE_CONFIG_VCSEL_PERIOD = 0x50,
|
||||
PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI = 0x51,
|
||||
PRE_RANGE_CONFIG_TIMEOUT_MACROP_LO = 0x52,
|
||||
|
||||
SYSTEM_HISTOGRAM_BIN = 0x81,
|
||||
HISTOGRAM_CONFIG_INITIAL_PHASE_SELECT = 0x33,
|
||||
HISTOGRAM_CONFIG_READOUT_CTRL = 0x55,
|
||||
|
||||
FINAL_RANGE_CONFIG_VCSEL_PERIOD = 0x70,
|
||||
FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI = 0x71,
|
||||
FINAL_RANGE_CONFIG_TIMEOUT_MACROP_LO = 0x72,
|
||||
CROSSTALK_COMPENSATION_PEAK_RATE_MCPS = 0x20,
|
||||
|
||||
MSRC_CONFIG_TIMEOUT_MACROP = 0x46,
|
||||
|
||||
SOFT_RESET_GO2_SOFT_RESET_N = 0xBF,
|
||||
IDENTIFICATION_MODEL_ID = 0xC0,
|
||||
IDENTIFICATION_REVISION_ID = 0xC2,
|
||||
|
||||
OSC_CALIBRATE_VAL = 0xF8,
|
||||
|
||||
GLOBAL_CONFIG_VCSEL_WIDTH = 0x32,
|
||||
GLOBAL_CONFIG_SPAD_ENABLES_REF_0 = 0xB0,
|
||||
GLOBAL_CONFIG_SPAD_ENABLES_REF_1 = 0xB1,
|
||||
GLOBAL_CONFIG_SPAD_ENABLES_REF_2 = 0xB2,
|
||||
GLOBAL_CONFIG_SPAD_ENABLES_REF_3 = 0xB3,
|
||||
GLOBAL_CONFIG_SPAD_ENABLES_REF_4 = 0xB4,
|
||||
GLOBAL_CONFIG_SPAD_ENABLES_REF_5 = 0xB5,
|
||||
|
||||
GLOBAL_CONFIG_REF_EN_START_SELECT = 0xB6,
|
||||
DYNAMIC_SPAD_NUM_REQUESTED_REF_SPAD = 0x4E,
|
||||
DYNAMIC_SPAD_REF_EN_START_OFFSET = 0x4F,
|
||||
POWER_MANAGEMENT_GO1_POWER_FORCE = 0x80,
|
||||
|
||||
VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV = 0x89,
|
||||
|
||||
ALGO_PHASECAL_LIM = 0x30,
|
||||
ALGO_PHASECAL_CONFIG_TIMEOUT = 0x30,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
VcselPeriodPreRange, VcselPeriodFinalRange
|
||||
} vcselPeriodType;
|
||||
|
||||
void vl53l0x_init(unsigned index, i2c_inst_t *i2c_port, uint8_t i2c_addr);
|
||||
void vl53l0x_use(unsigned index);
|
||||
bool vl53l0x_is_present();
|
||||
bool vl53l0x_init_tof();
|
||||
|
||||
bool setSignalRateLimit(float limit_Mcps);
|
||||
float getSignalRateLimit();
|
||||
|
||||
bool setMeasurementTimingBudget(uint32_t budget_us);
|
||||
uint32_t getMeasurementTimingBudget();
|
||||
|
||||
bool setVcselPulsePeriod(vcselPeriodType type, uint8_t period_pclks);
|
||||
uint8_t getVcselPulsePeriod(vcselPeriodType type);
|
||||
|
||||
void vl53l0x_start_continuous();
|
||||
void vl53l0x_stop_continuous();
|
||||
|
||||
uint16_t readRangeContinuousMillimeters();
|
||||
uint16_t readRangeSingleMillimeters();
|
||||
|
||||
// TCC: Target CentreCheck
|
||||
// MSRC: Minimum Signal Rate Check
|
||||
// DSS: Dynamic Spad Selection
|
||||
|
||||
typedef struct {
|
||||
bool tcc, msrc, dss, pre_range, final_range;
|
||||
} SequenceStepEnables;
|
||||
|
||||
typedef struct {
|
||||
uint16_t pre_range_vcsel_period_pclks, final_range_vcsel_period_pclks;
|
||||
|
||||
uint16_t msrc_dss_tcc_mclks, pre_range_mclks, final_range_mclks;
|
||||
uint32_t msrc_dss_tcc_us, pre_range_us, final_range_us;
|
||||
} SequenceStepTimeouts;
|
||||
|
||||
bool getSpadInfo(uint8_t * count, bool * type_is_aperture);
|
||||
|
||||
void getSequenceStepEnables(SequenceStepEnables * enables);
|
||||
void getSequenceStepTimeouts(SequenceStepEnables const * enables, SequenceStepTimeouts * timeouts);
|
||||
|
||||
bool performSingleRefCalibration(uint8_t vhv_init_byte);
|
||||
|
||||
#endif
|
90
firmware/src/wad.c
Normal file
90
firmware/src/wad.c
Normal file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Controller Wads
|
||||
* WHowe <github.com/whowechina>
|
||||
*
|
||||
*/
|
||||
|
||||
#include "wad.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "hardware/gpio.h"
|
||||
#include "hardware/timer.h"
|
||||
#include "hardware/pwm.h"
|
||||
#include "hardware/i2c.h"
|
||||
|
||||
#include "vl53l0x.h"
|
||||
#include "config.h"
|
||||
#include "board_defs.h"
|
||||
|
||||
#define WAD_NUM 2
|
||||
static i2c_inst_t *wad_ports[] = WAD_DEF;
|
||||
static bool sw_val[WAD_NUM]; /* true if triggered */
|
||||
static uint64_t sw_freeze_time[WAD_NUM];
|
||||
|
||||
void wad_init()
|
||||
{
|
||||
uint8_t wad_gpio[] = WAD_GPIO_DEF;
|
||||
for (int i = 0; i < WAD_NUM; i++) {
|
||||
sw_val[i] = false;
|
||||
sw_freeze_time[i] = 0;
|
||||
uint8_t scl = wad_gpio[i * 2];
|
||||
uint8_t sda = wad_gpio[i * 2 + 1];
|
||||
|
||||
gpio_init(scl);
|
||||
gpio_init(sda);
|
||||
|
||||
gpio_set_function(scl, GPIO_FUNC_I2C);
|
||||
gpio_set_function(sda, GPIO_FUNC_I2C);
|
||||
|
||||
gpio_pull_up(scl);
|
||||
gpio_pull_up(sda);
|
||||
|
||||
vl53l0x_init(i, wad_ports[i], 0);
|
||||
vl53l0x_use(i);
|
||||
vl53l0x_init_tof();
|
||||
vl53l0x_start_continuous();
|
||||
}
|
||||
}
|
||||
|
||||
static bool wad_readings[WAD_NUM];
|
||||
|
||||
static bool wad_read(unsigned index)
|
||||
{
|
||||
vl53l0x_use(index);
|
||||
uint16_t dist = readRangeContinuousMillimeters(index);
|
||||
if (wad_readings[index]) {
|
||||
return (dist >= 80) && (dist <= 270);
|
||||
}
|
||||
return (dist >= 100) && (dist <= 250);
|
||||
}
|
||||
|
||||
/* If a switch flips, it freezes for a while */
|
||||
#define DEBOUNCE_FREEZE_TIME_US 30000
|
||||
void wad_update()
|
||||
{
|
||||
uint64_t now = time_us_64();
|
||||
|
||||
for (int i = 0; i < WAD_NUM; i++) {
|
||||
bool triggered = wad_read(i);
|
||||
if (now >= sw_freeze_time[i]) {
|
||||
if (triggered != sw_val[i]) {
|
||||
sw_val[i] = triggered;
|
||||
sw_freeze_time[i] = now + DEBOUNCE_FREEZE_TIME_US;
|
||||
}
|
||||
}
|
||||
wad_readings[i] = sw_val[i];
|
||||
}
|
||||
}
|
||||
|
||||
bool wad_read_left()
|
||||
{
|
||||
return wad_readings[0];
|
||||
}
|
||||
|
||||
bool wad_read_right()
|
||||
{
|
||||
return wad_readings[1];
|
||||
}
|
18
firmware/src/wad.h
Normal file
18
firmware/src/wad.h
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Controller Wads
|
||||
* WHowe <github.com/whowechina>
|
||||
*/
|
||||
|
||||
#ifndef WAD_H
|
||||
#define WAD_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "hardware/flash.h"
|
||||
|
||||
void wad_init();
|
||||
void wad_update();
|
||||
bool wad_read_left();
|
||||
bool wad_read_right();
|
||||
|
||||
#endif
|
85
firmware/src/ws2812.pio
Normal file
85
firmware/src/ws2812.pio
Normal file
@ -0,0 +1,85 @@
|
||||
;
|
||||
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
;
|
||||
; SPDX-License-Identifier: BSD-3-Clause
|
||||
;
|
||||
|
||||
.program ws2812
|
||||
.side_set 1
|
||||
|
||||
.define public T1 2
|
||||
.define public T2 5
|
||||
.define public T3 3
|
||||
|
||||
.lang_opt python sideset_init = pico.PIO.OUT_HIGH
|
||||
.lang_opt python out_init = pico.PIO.OUT_HIGH
|
||||
.lang_opt python out_shiftdir = 1
|
||||
|
||||
.wrap_target
|
||||
bitloop:
|
||||
out x, 1 side 0 [T3 - 1] ; Side-set still takes place when instruction stalls
|
||||
jmp !x do_zero side 1 [T1 - 1] ; Branch on the bit we shifted out. Positive pulse
|
||||
do_one:
|
||||
jmp bitloop side 1 [T2 - 1] ; Continue driving high, for a long pulse
|
||||
do_zero:
|
||||
nop side 0 [T2 - 1] ; Or drive low, for a short pulse
|
||||
.wrap
|
||||
|
||||
% c-sdk {
|
||||
#include "hardware/clocks.h"
|
||||
|
||||
static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, float freq, bool rgbw) {
|
||||
|
||||
pio_gpio_init(pio, pin);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
|
||||
|
||||
pio_sm_config c = ws2812_program_get_default_config(offset);
|
||||
sm_config_set_sideset_pins(&c, pin);
|
||||
sm_config_set_out_shift(&c, false, true, rgbw ? 32 : 24);
|
||||
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
|
||||
|
||||
int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3;
|
||||
float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
|
||||
sm_config_set_clkdiv(&c, div);
|
||||
|
||||
pio_sm_init(pio, sm, offset, &c);
|
||||
pio_sm_set_enabled(pio, sm, true);
|
||||
}
|
||||
%}
|
||||
|
||||
.program ws2812_parallel
|
||||
|
||||
.define public T1 2
|
||||
.define public T2 5
|
||||
.define public T3 3
|
||||
|
||||
.wrap_target
|
||||
out x, 32
|
||||
mov pins, !null [T1-1]
|
||||
mov pins, x [T2-1]
|
||||
mov pins, null [T3-2]
|
||||
.wrap
|
||||
|
||||
% c-sdk {
|
||||
#include "hardware/clocks.h"
|
||||
|
||||
static inline void ws2812_parallel_program_init(PIO pio, uint sm, uint offset, uint pin_base, uint pin_count, float freq) {
|
||||
for(uint i=pin_base; i<pin_base+pin_count; i++) {
|
||||
pio_gpio_init(pio, i);
|
||||
}
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, pin_base, pin_count, true);
|
||||
|
||||
pio_sm_config c = ws2812_parallel_program_get_default_config(offset);
|
||||
sm_config_set_out_shift(&c, true, true, 32);
|
||||
sm_config_set_out_pins(&c, pin_base, pin_count);
|
||||
sm_config_set_set_pins(&c, pin_base, pin_count);
|
||||
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
|
||||
|
||||
int cycles_per_bit = ws2812_parallel_T1 + ws2812_parallel_T2 + ws2812_parallel_T3;
|
||||
float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
|
||||
sm_config_set_clkdiv(&c, div);
|
||||
|
||||
pio_sm_init(pio, sm, offset, &c);
|
||||
pio_sm_set_enabled(pio, sm, true);
|
||||
}
|
||||
%}
|
Loading…
Reference in New Issue
Block a user