Compare commits
No commits in common. 'master' and 'pin-io-improv' have entirely different histories.
master
...
pin-io-imp
@ -1,373 +0,0 @@ |
|||||||
Mozilla Public License Version 2.0 |
|
||||||
================================== |
|
||||||
|
|
||||||
1. Definitions |
|
||||||
-------------- |
|
||||||
|
|
||||||
1.1. "Contributor" |
|
||||||
means each individual or legal entity that creates, contributes to |
|
||||||
the creation of, or owns Covered Software. |
|
||||||
|
|
||||||
1.2. "Contributor Version" |
|
||||||
means the combination of the Contributions of others (if any) used |
|
||||||
by a Contributor and that particular Contributor's Contribution. |
|
||||||
|
|
||||||
1.3. "Contribution" |
|
||||||
means Covered Software of a particular Contributor. |
|
||||||
|
|
||||||
1.4. "Covered Software" |
|
||||||
means Source Code Form to which the initial Contributor has attached |
|
||||||
the notice in Exhibit A, the Executable Form of such Source Code |
|
||||||
Form, and Modifications of such Source Code Form, in each case |
|
||||||
including portions thereof. |
|
||||||
|
|
||||||
1.5. "Incompatible With Secondary Licenses" |
|
||||||
means |
|
||||||
|
|
||||||
(a) that the initial Contributor has attached the notice described |
|
||||||
in Exhibit B to the Covered Software; or |
|
||||||
|
|
||||||
(b) that the Covered Software was made available under the terms of |
|
||||||
version 1.1 or earlier of the License, but not also under the |
|
||||||
terms of a Secondary License. |
|
||||||
|
|
||||||
1.6. "Executable Form" |
|
||||||
means any form of the work other than Source Code Form. |
|
||||||
|
|
||||||
1.7. "Larger Work" |
|
||||||
means a work that combines Covered Software with other material, in |
|
||||||
a separate file or files, that is not Covered Software. |
|
||||||
|
|
||||||
1.8. "License" |
|
||||||
means this document. |
|
||||||
|
|
||||||
1.9. "Licensable" |
|
||||||
means having the right to grant, to the maximum extent possible, |
|
||||||
whether at the time of the initial grant or subsequently, any and |
|
||||||
all of the rights conveyed by this License. |
|
||||||
|
|
||||||
1.10. "Modifications" |
|
||||||
means any of the following: |
|
||||||
|
|
||||||
(a) any file in Source Code Form that results from an addition to, |
|
||||||
deletion from, or modification of the contents of Covered |
|
||||||
Software; or |
|
||||||
|
|
||||||
(b) any new file in Source Code Form that contains any Covered |
|
||||||
Software. |
|
||||||
|
|
||||||
1.11. "Patent Claims" of a Contributor |
|
||||||
means any patent claim(s), including without limitation, method, |
|
||||||
process, and apparatus claims, in any patent Licensable by such |
|
||||||
Contributor that would be infringed, but for the grant of the |
|
||||||
License, by the making, using, selling, offering for sale, having |
|
||||||
made, import, or transfer of either its Contributions or its |
|
||||||
Contributor Version. |
|
||||||
|
|
||||||
1.12. "Secondary License" |
|
||||||
means either the GNU General Public License, Version 2.0, the GNU |
|
||||||
Lesser General Public License, Version 2.1, the GNU Affero General |
|
||||||
Public License, Version 3.0, or any later versions of those |
|
||||||
licenses. |
|
||||||
|
|
||||||
1.13. "Source Code Form" |
|
||||||
means the form of the work preferred for making modifications. |
|
||||||
|
|
||||||
1.14. "You" (or "Your") |
|
||||||
means an individual or a legal entity exercising rights under this |
|
||||||
License. For legal entities, "You" includes any entity that |
|
||||||
controls, is controlled by, or is under common control with You. For |
|
||||||
purposes of this definition, "control" means (a) the power, direct |
|
||||||
or indirect, to cause the direction or management of such entity, |
|
||||||
whether by contract or otherwise, or (b) ownership of more than |
|
||||||
fifty percent (50%) of the outstanding shares or beneficial |
|
||||||
ownership of such entity. |
|
||||||
|
|
||||||
2. License Grants and Conditions |
|
||||||
-------------------------------- |
|
||||||
|
|
||||||
2.1. Grants |
|
||||||
|
|
||||||
Each Contributor hereby grants You a world-wide, royalty-free, |
|
||||||
non-exclusive license: |
|
||||||
|
|
||||||
(a) under intellectual property rights (other than patent or trademark) |
|
||||||
Licensable by such Contributor to use, reproduce, make available, |
|
||||||
modify, display, perform, distribute, and otherwise exploit its |
|
||||||
Contributions, either on an unmodified basis, with Modifications, or |
|
||||||
as part of a Larger Work; and |
|
||||||
|
|
||||||
(b) under Patent Claims of such Contributor to make, use, sell, offer |
|
||||||
for sale, have made, import, and otherwise transfer either its |
|
||||||
Contributions or its Contributor Version. |
|
||||||
|
|
||||||
2.2. Effective Date |
|
||||||
|
|
||||||
The licenses granted in Section 2.1 with respect to any Contribution |
|
||||||
become effective for each Contribution on the date the Contributor first |
|
||||||
distributes such Contribution. |
|
||||||
|
|
||||||
2.3. Limitations on Grant Scope |
|
||||||
|
|
||||||
The licenses granted in this Section 2 are the only rights granted under |
|
||||||
this License. No additional rights or licenses will be implied from the |
|
||||||
distribution or licensing of Covered Software under this License. |
|
||||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a |
|
||||||
Contributor: |
|
||||||
|
|
||||||
(a) for any code that a Contributor has removed from Covered Software; |
|
||||||
or |
|
||||||
|
|
||||||
(b) for infringements caused by: (i) Your and any other third party's |
|
||||||
modifications of Covered Software, or (ii) the combination of its |
|
||||||
Contributions with other software (except as part of its Contributor |
|
||||||
Version); or |
|
||||||
|
|
||||||
(c) under Patent Claims infringed by Covered Software in the absence of |
|
||||||
its Contributions. |
|
||||||
|
|
||||||
This License does not grant any rights in the trademarks, service marks, |
|
||||||
or logos of any Contributor (except as may be necessary to comply with |
|
||||||
the notice requirements in Section 3.4). |
|
||||||
|
|
||||||
2.4. Subsequent Licenses |
|
||||||
|
|
||||||
No Contributor makes additional grants as a result of Your choice to |
|
||||||
distribute the Covered Software under a subsequent version of this |
|
||||||
License (see Section 10.2) or under the terms of a Secondary License (if |
|
||||||
permitted under the terms of Section 3.3). |
|
||||||
|
|
||||||
2.5. Representation |
|
||||||
|
|
||||||
Each Contributor represents that the Contributor believes its |
|
||||||
Contributions are its original creation(s) or it has sufficient rights |
|
||||||
to grant the rights to its Contributions conveyed by this License. |
|
||||||
|
|
||||||
2.6. Fair Use |
|
||||||
|
|
||||||
This License is not intended to limit any rights You have under |
|
||||||
applicable copyright doctrines of fair use, fair dealing, or other |
|
||||||
equivalents. |
|
||||||
|
|
||||||
2.7. Conditions |
|
||||||
|
|
||||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted |
|
||||||
in Section 2.1. |
|
||||||
|
|
||||||
3. Responsibilities |
|
||||||
------------------- |
|
||||||
|
|
||||||
3.1. Distribution of Source Form |
|
||||||
|
|
||||||
All distribution of Covered Software in Source Code Form, including any |
|
||||||
Modifications that You create or to which You contribute, must be under |
|
||||||
the terms of this License. You must inform recipients that the Source |
|
||||||
Code Form of the Covered Software is governed by the terms of this |
|
||||||
License, and how they can obtain a copy of this License. You may not |
|
||||||
attempt to alter or restrict the recipients' rights in the Source Code |
|
||||||
Form. |
|
||||||
|
|
||||||
3.2. Distribution of Executable Form |
|
||||||
|
|
||||||
If You distribute Covered Software in Executable Form then: |
|
||||||
|
|
||||||
(a) such Covered Software must also be made available in Source Code |
|
||||||
Form, as described in Section 3.1, and You must inform recipients of |
|
||||||
the Executable Form how they can obtain a copy of such Source Code |
|
||||||
Form by reasonable means in a timely manner, at a charge no more |
|
||||||
than the cost of distribution to the recipient; and |
|
||||||
|
|
||||||
(b) You may distribute such Executable Form under the terms of this |
|
||||||
License, or sublicense it under different terms, provided that the |
|
||||||
license for the Executable Form does not attempt to limit or alter |
|
||||||
the recipients' rights in the Source Code Form under this License. |
|
||||||
|
|
||||||
3.3. Distribution of a Larger Work |
|
||||||
|
|
||||||
You may create and distribute a Larger Work under terms of Your choice, |
|
||||||
provided that You also comply with the requirements of this License for |
|
||||||
the Covered Software. If the Larger Work is a combination of Covered |
|
||||||
Software with a work governed by one or more Secondary Licenses, and the |
|
||||||
Covered Software is not Incompatible With Secondary Licenses, this |
|
||||||
License permits You to additionally distribute such Covered Software |
|
||||||
under the terms of such Secondary License(s), so that the recipient of |
|
||||||
the Larger Work may, at their option, further distribute the Covered |
|
||||||
Software under the terms of either this License or such Secondary |
|
||||||
License(s). |
|
||||||
|
|
||||||
3.4. Notices |
|
||||||
|
|
||||||
You may not remove or alter the substance of any license notices |
|
||||||
(including copyright notices, patent notices, disclaimers of warranty, |
|
||||||
or limitations of liability) contained within the Source Code Form of |
|
||||||
the Covered Software, except that You may alter any license notices to |
|
||||||
the extent required to remedy known factual inaccuracies. |
|
||||||
|
|
||||||
3.5. Application of Additional Terms |
|
||||||
|
|
||||||
You may choose to offer, and to charge a fee for, warranty, support, |
|
||||||
indemnity or liability obligations to one or more recipients of Covered |
|
||||||
Software. However, You may do so only on Your own behalf, and not on |
|
||||||
behalf of any Contributor. You must make it absolutely clear that any |
|
||||||
such warranty, support, indemnity, or liability obligation is offered by |
|
||||||
You alone, and You hereby agree to indemnify every Contributor for any |
|
||||||
liability incurred by such Contributor as a result of warranty, support, |
|
||||||
indemnity or liability terms You offer. You may include additional |
|
||||||
disclaimers of warranty and limitations of liability specific to any |
|
||||||
jurisdiction. |
|
||||||
|
|
||||||
4. Inability to Comply Due to Statute or Regulation |
|
||||||
--------------------------------------------------- |
|
||||||
|
|
||||||
If it is impossible for You to comply with any of the terms of this |
|
||||||
License with respect to some or all of the Covered Software due to |
|
||||||
statute, judicial order, or regulation then You must: (a) comply with |
|
||||||
the terms of this License to the maximum extent possible; and (b) |
|
||||||
describe the limitations and the code they affect. Such description must |
|
||||||
be placed in a text file included with all distributions of the Covered |
|
||||||
Software under this License. Except to the extent prohibited by statute |
|
||||||
or regulation, such description must be sufficiently detailed for a |
|
||||||
recipient of ordinary skill to be able to understand it. |
|
||||||
|
|
||||||
5. Termination |
|
||||||
-------------- |
|
||||||
|
|
||||||
5.1. The rights granted under this License will terminate automatically |
|
||||||
if You fail to comply with any of its terms. However, if You become |
|
||||||
compliant, then the rights granted under this License from a particular |
|
||||||
Contributor are reinstated (a) provisionally, unless and until such |
|
||||||
Contributor explicitly and finally terminates Your grants, and (b) on an |
|
||||||
ongoing basis, if such Contributor fails to notify You of the |
|
||||||
non-compliance by some reasonable means prior to 60 days after You have |
|
||||||
come back into compliance. Moreover, Your grants from a particular |
|
||||||
Contributor are reinstated on an ongoing basis if such Contributor |
|
||||||
notifies You of the non-compliance by some reasonable means, this is the |
|
||||||
first time You have received notice of non-compliance with this License |
|
||||||
from such Contributor, and You become compliant prior to 30 days after |
|
||||||
Your receipt of the notice. |
|
||||||
|
|
||||||
5.2. If You initiate litigation against any entity by asserting a patent |
|
||||||
infringement claim (excluding declaratory judgment actions, |
|
||||||
counter-claims, and cross-claims) alleging that a Contributor Version |
|
||||||
directly or indirectly infringes any patent, then the rights granted to |
|
||||||
You by any and all Contributors for the Covered Software under Section |
|
||||||
2.1 of this License shall terminate. |
|
||||||
|
|
||||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all |
|
||||||
end user license agreements (excluding distributors and resellers) which |
|
||||||
have been validly granted by You or Your distributors under this License |
|
||||||
prior to termination shall survive termination. |
|
||||||
|
|
||||||
************************************************************************ |
|
||||||
* * |
|
||||||
* 6. Disclaimer of Warranty * |
|
||||||
* ------------------------- * |
|
||||||
* * |
|
||||||
* Covered Software is provided under this License on an "as is" * |
|
||||||
* basis, without warranty of any kind, either expressed, implied, or * |
|
||||||
* statutory, including, without limitation, warranties that the * |
|
||||||
* Covered Software is free of defects, merchantable, fit for a * |
|
||||||
* particular purpose or non-infringing. The entire risk as to the * |
|
||||||
* quality and performance of the Covered Software is with You. * |
|
||||||
* Should any Covered Software prove defective in any respect, You * |
|
||||||
* (not any Contributor) assume the cost of any necessary servicing, * |
|
||||||
* repair, or correction. This disclaimer of warranty constitutes an * |
|
||||||
* essential part of this License. No use of any Covered Software is * |
|
||||||
* authorized under this License except under this disclaimer. * |
|
||||||
* * |
|
||||||
************************************************************************ |
|
||||||
|
|
||||||
************************************************************************ |
|
||||||
* * |
|
||||||
* 7. Limitation of Liability * |
|
||||||
* -------------------------- * |
|
||||||
* * |
|
||||||
* Under no circumstances and under no legal theory, whether tort * |
|
||||||
* (including negligence), contract, or otherwise, shall any * |
|
||||||
* Contributor, or anyone who distributes Covered Software as * |
|
||||||
* permitted above, be liable to You for any direct, indirect, * |
|
||||||
* special, incidental, or consequential damages of any character * |
|
||||||
* including, without limitation, damages for lost profits, loss of * |
|
||||||
* goodwill, work stoppage, computer failure or malfunction, or any * |
|
||||||
* and all other commercial damages or losses, even if such party * |
|
||||||
* shall have been informed of the possibility of such damages. This * |
|
||||||
* limitation of liability shall not apply to liability for death or * |
|
||||||
* personal injury resulting from such party's negligence to the * |
|
||||||
* extent applicable law prohibits such limitation. Some * |
|
||||||
* jurisdictions do not allow the exclusion or limitation of * |
|
||||||
* incidental or consequential damages, so this exclusion and * |
|
||||||
* limitation may not apply to You. * |
|
||||||
* * |
|
||||||
************************************************************************ |
|
||||||
|
|
||||||
8. Litigation |
|
||||||
------------- |
|
||||||
|
|
||||||
Any litigation relating to this License may be brought only in the |
|
||||||
courts of a jurisdiction where the defendant maintains its principal |
|
||||||
place of business and such litigation shall be governed by laws of that |
|
||||||
jurisdiction, without reference to its conflict-of-law provisions. |
|
||||||
Nothing in this Section shall prevent a party's ability to bring |
|
||||||
cross-claims or counter-claims. |
|
||||||
|
|
||||||
9. Miscellaneous |
|
||||||
---------------- |
|
||||||
|
|
||||||
This License represents the complete agreement concerning the subject |
|
||||||
matter hereof. If any provision of this License is held to be |
|
||||||
unenforceable, such provision shall be reformed only to the extent |
|
||||||
necessary to make it enforceable. Any law or regulation which provides |
|
||||||
that the language of a contract shall be construed against the drafter |
|
||||||
shall not be used to construe this License against a Contributor. |
|
||||||
|
|
||||||
10. Versions of the License |
|
||||||
--------------------------- |
|
||||||
|
|
||||||
10.1. New Versions |
|
||||||
|
|
||||||
Mozilla Foundation is the license steward. Except as provided in Section |
|
||||||
10.3, no one other than the license steward has the right to modify or |
|
||||||
publish new versions of this License. Each version will be given a |
|
||||||
distinguishing version number. |
|
||||||
|
|
||||||
10.2. Effect of New Versions |
|
||||||
|
|
||||||
You may distribute the Covered Software under the terms of the version |
|
||||||
of the License under which You originally received the Covered Software, |
|
||||||
or under the terms of any subsequent version published by the license |
|
||||||
steward. |
|
||||||
|
|
||||||
10.3. Modified Versions |
|
||||||
|
|
||||||
If you create software not governed by this License, and you want to |
|
||||||
create a new license for such software, you may create and use a |
|
||||||
modified version of this License if you rename the license and remove |
|
||||||
any references to the name of the license steward (except to note that |
|
||||||
such modified license differs from this License). |
|
||||||
|
|
||||||
10.4. Distributing Source Code Form that is Incompatible With Secondary |
|
||||||
Licenses |
|
||||||
|
|
||||||
If You choose to distribute Source Code Form that is Incompatible With |
|
||||||
Secondary Licenses under the terms of this version of the License, the |
|
||||||
notice described in Exhibit B of this License must be attached. |
|
||||||
|
|
||||||
Exhibit A - Source Code Form License Notice |
|
||||||
------------------------------------------- |
|
||||||
|
|
||||||
This Source Code Form is subject to the terms of the Mozilla Public |
|
||||||
License, v. 2.0. If a copy of the MPL was not distributed with this |
|
||||||
file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
||||||
|
|
||||||
If it is not possible or desirable to put the notice in a particular |
|
||||||
file, then You may include the notice in a location (such as a LICENSE |
|
||||||
file in a relevant directory) where a recipient would be likely to look |
|
||||||
for such a notice. |
|
||||||
|
|
||||||
You may add additional accurate notices of copyright ownership. |
|
||||||
|
|
||||||
Exhibit B - "Incompatible With Secondary Licenses" Notice |
|
||||||
--------------------------------------------------------- |
|
||||||
|
|
||||||
This Source Code Form is "Incompatible With Secondary Licenses", as |
|
||||||
defined by the Mozilla Public License, v. 2.0. |
|
@ -1,13 +0,0 @@ |
|||||||
GEX core |
|
||||||
======== |
|
||||||
|
|
||||||
This is the (mostly) independent core of the GEX platform. To port GEX to a new STM32 microcontroller, |
|
||||||
add this as a submodule and implement platform specific configuration by following the example of another |
|
||||||
existing port. Some examples are in the template folder. |
|
||||||
|
|
||||||
License |
|
||||||
------- |
|
||||||
|
|
||||||
- Original GEX source code is provided under the Mozilla Public License v2.0. |
|
||||||
- The VFS driver is adapted from the DAPLink project and remains licensed under the Apache license v2.0. |
|
||||||
- STM32 device drivers etc. provided by ST Microelectronics are licensed under the 3-clause BSD license. |
|
@ -1,192 +0,0 @@ |
|||||||
//
|
|
||||||
// Created by MightyPork on 2018/04/06.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "platform.h" |
|
||||||
#if SUPPORT_NRF |
|
||||||
|
|
||||||
#include "iface_nordic.h" |
|
||||||
#include "nrf_pins.h" |
|
||||||
#include "resources.h" |
|
||||||
#include "hw_utils.h" |
|
||||||
#include "nrf.h" |
|
||||||
#include "unit_base.h" |
|
||||||
#include "system_settings.h" |
|
||||||
#include "utils/hexdump.h" |
|
||||||
|
|
||||||
|
|
||||||
extern osSemaphoreId semVcomTxReadyHandle; |
|
||||||
|
|
||||||
#define RX_PIPE_NUM 0 |
|
||||||
|
|
||||||
void iface_nordic_claim_resources(void) |
|
||||||
{ |
|
||||||
assert_param(E_SUCCESS == rsc_claim(&UNIT_SYSTEM, NRF_R_SPI)); |
|
||||||
assert_param(E_SUCCESS == rsc_claim(&UNIT_SYSTEM, NRF_R_CE)); |
|
||||||
assert_param(E_SUCCESS == rsc_claim(&UNIT_SYSTEM, NRF_R_NSS)); |
|
||||||
assert_param(E_SUCCESS == rsc_claim(&UNIT_SYSTEM, NRF_R_IRQ)); |
|
||||||
assert_param(E_SUCCESS == rsc_claim(&UNIT_SYSTEM, NRF_R_MISO)); |
|
||||||
assert_param(E_SUCCESS == rsc_claim(&UNIT_SYSTEM, NRF_R_MOSI)); |
|
||||||
assert_param(E_SUCCESS == rsc_claim(&UNIT_SYSTEM, NRF_R_SCK)); |
|
||||||
|
|
||||||
assert_param(E_SUCCESS == rsc_claim(&UNIT_SYSTEM, R_EXTI0+NRF_EXTI_LINENUM)); |
|
||||||
} |
|
||||||
|
|
||||||
void iface_nordic_free_resources(void) |
|
||||||
{ |
|
||||||
rsc_free(&UNIT_SYSTEM, NRF_R_SPI); |
|
||||||
rsc_free(&UNIT_SYSTEM, NRF_R_CE); |
|
||||||
rsc_free(&UNIT_SYSTEM, NRF_R_NSS); |
|
||||||
rsc_free(&UNIT_SYSTEM, NRF_R_IRQ); |
|
||||||
rsc_free(&UNIT_SYSTEM, NRF_R_MISO); |
|
||||||
rsc_free(&UNIT_SYSTEM, NRF_R_MOSI); |
|
||||||
rsc_free(&UNIT_SYSTEM, NRF_R_SCK); |
|
||||||
|
|
||||||
rsc_free(&UNIT_SYSTEM, R_EXTI0+NRF_EXTI_LINENUM); |
|
||||||
} |
|
||||||
|
|
||||||
static uint8_t rx_buffer[32]; |
|
||||||
|
|
||||||
static void NrfIrqHandler(void *arg) |
|
||||||
{ |
|
||||||
LL_EXTI_ClearFlag_0_31(LL_EXTI_LINES[NRF_EXTI_LINENUM]); |
|
||||||
dbg_nrf("[EXTI] ---"); |
|
||||||
|
|
||||||
while (NRF_IsRxPacket()) { |
|
||||||
uint8_t pipenum; |
|
||||||
uint8_t count = NRF_ReceivePacket(rx_buffer, &pipenum); |
|
||||||
if (count > 0) { |
|
||||||
dbg_nrf("NRF RX %d bytes", (int) count); |
|
||||||
rxQuePostMsg(rx_buffer, count); |
|
||||||
} |
|
||||||
else { |
|
||||||
dbg("IRQ but no Rx"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
dbg_nrf("--- end [EXTI]"); |
|
||||||
} |
|
||||||
|
|
||||||
bool iface_nordic_init(void) |
|
||||||
{ |
|
||||||
dbg("Setting up Nordic..."); |
|
||||||
|
|
||||||
hw_periph_clock_enable(NRF_SPI); |
|
||||||
|
|
||||||
// SPI pins
|
|
||||||
assert_param(E_SUCCESS == hw_configure_gpiorsc_af(NRF_R_SCK, NRF_SCK_AF)); |
|
||||||
assert_param(E_SUCCESS == hw_configure_gpiorsc_af(NRF_R_MOSI, NRF_MOSI_AF)); |
|
||||||
assert_param(E_SUCCESS == hw_configure_gpiorsc_af(NRF_R_MISO, NRF_MISO_AF)); |
|
||||||
|
|
||||||
// Manual pins
|
|
||||||
LL_GPIO_SetPinMode(NRF_NSS_GPIO_Port, NRF_NSS_Pin, LL_GPIO_MODE_OUTPUT); |
|
||||||
LL_GPIO_SetPinMode(NRF_CE_GPIO_Port, NRF_CE_Pin, LL_GPIO_MODE_OUTPUT); |
|
||||||
LL_GPIO_SetPinMode(NRF_RST_GPIO_Port, NRF_RST_Pin, LL_GPIO_MODE_OUTPUT); |
|
||||||
LL_GPIO_SetPinMode(NRF_IRQ_GPIO_Port, NRF_IRQ_Pin, LL_GPIO_MODE_INPUT); |
|
||||||
|
|
||||||
// set up SPI
|
|
||||||
LL_SPI_Disable(NRF_SPI); |
|
||||||
{ |
|
||||||
LL_SPI_SetBaudRatePrescaler(NRF_SPI, LL_SPI_BAUDRATEPRESCALER_DIV32); |
|
||||||
//LL_SPI_BAUDRATEPRESCALER_DIV8
|
|
||||||
|
|
||||||
LL_SPI_SetClockPolarity(NRF_SPI, LL_SPI_POLARITY_LOW); |
|
||||||
LL_SPI_SetClockPhase(NRF_SPI, LL_SPI_PHASE_1EDGE); |
|
||||||
LL_SPI_SetTransferDirection(NRF_SPI, LL_SPI_FULL_DUPLEX); |
|
||||||
LL_SPI_SetTransferBitOrder(NRF_SPI, LL_SPI_MSB_FIRST); |
|
||||||
|
|
||||||
LL_SPI_SetNSSMode(NRF_SPI, LL_SPI_NSS_SOFT); |
|
||||||
LL_SPI_SetDataWidth(NRF_SPI, LL_SPI_DATAWIDTH_8BIT); |
|
||||||
LL_SPI_SetRxFIFOThreshold(NRF_SPI, LL_SPI_RX_FIFO_TH_QUARTER); // trigger RXNE on 1 byte
|
|
||||||
|
|
||||||
LL_SPI_SetMode(NRF_SPI, LL_SPI_MODE_MASTER); |
|
||||||
} |
|
||||||
LL_SPI_Enable(NRF_SPI); |
|
||||||
|
|
||||||
// reset the radio / enable its power supply
|
|
||||||
NRF_Reset(1); |
|
||||||
LL_mDelay(5); |
|
||||||
NRF_Reset(0); |
|
||||||
|
|
||||||
dbg("configure nrf module"); |
|
||||||
|
|
||||||
// Now configure the radio
|
|
||||||
if (!NRF_Init(NRF_SPEED_2M)) {// TODO configurable speed - also maybe better to use slower
|
|
||||||
dbg("--- NRF not present!"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
NRF_SetChannel(SystemSettings.nrf_channel); |
|
||||||
NRF_SetBaseAddress(SystemSettings.nrf_network); |
|
||||||
NRF_SetRxAddress(RX_PIPE_NUM, SystemSettings.nrf_address); |
|
||||||
NRF_EnablePipe(RX_PIPE_NUM); |
|
||||||
NRF_ModeRX(); |
|
||||||
|
|
||||||
dbg("enable exti"); |
|
||||||
// EXTI
|
|
||||||
LL_EXTI_EnableIT_0_31(LL_EXTI_LINES[NRF_EXTI_LINENUM]); |
|
||||||
LL_EXTI_EnableFallingTrig_0_31(LL_EXTI_LINES[NRF_EXTI_LINENUM]); |
|
||||||
LL_SYSCFG_SetEXTISource(NRF_SYSCFG_EXTI_PORT, LL_SYSCFG_EXTI_LINES[NRF_EXTI_LINENUM]); |
|
||||||
irqd_attach(EXTIS[NRF_EXTI_LINENUM], NrfIrqHandler, NULL); |
|
||||||
// TODO increase priority in NVIC?
|
|
||||||
|
|
||||||
dbg("nrf setup done"); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
void iface_nordic_deinit(void) |
|
||||||
{ |
|
||||||
LL_EXTI_DisableIT_0_31(LL_EXTI_LINES[NRF_EXTI_LINENUM]); |
|
||||||
LL_EXTI_DisableFallingTrig_0_31(LL_EXTI_LINES[NRF_EXTI_LINENUM]); |
|
||||||
irqd_detach(EXTIS[NRF_EXTI_LINENUM], NrfIrqHandler); |
|
||||||
hw_periph_clock_disable(NRF_SPI); |
|
||||||
|
|
||||||
hw_deinit_pin_rsc(NRF_R_SCK); |
|
||||||
hw_deinit_pin_rsc(NRF_R_MOSI); |
|
||||||
hw_deinit_pin_rsc(NRF_R_MISO); |
|
||||||
hw_deinit_pin_rsc(NRF_R_NSS); |
|
||||||
hw_deinit_pin_rsc(NRF_R_CE); |
|
||||||
hw_deinit_pin_rsc(NRF_R_IRQ); |
|
||||||
hw_deinit_pin_rsc(NRF_R_RST); |
|
||||||
} |
|
||||||
|
|
||||||
// FIXME
|
|
||||||
#define MAX_RETRY 5 |
|
||||||
|
|
||||||
void iface_nordic_transmit(const uint8_t *buff, uint32_t len) |
|
||||||
{ |
|
||||||
bool suc = false; |
|
||||||
while (len > 0) { |
|
||||||
uint8_t chunk = (uint8_t) MIN(32, len); |
|
||||||
|
|
||||||
uint16_t delay = 1; |
|
||||||
//hexDump("Tx chunk", buff, chunk);
|
|
||||||
for (int i = 0; i < MAX_RETRY; i++) { |
|
||||||
suc = NRF_SendPacket(RX_PIPE_NUM, buff, chunk); // use the pipe to retrieve the address
|
|
||||||
if (!suc) { |
|
||||||
vTaskDelay(delay); |
|
||||||
delay *= 2; // longer delay next time
|
|
||||||
} else { |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (!suc) { |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
buff += chunk; |
|
||||||
len -= chunk; |
|
||||||
} |
|
||||||
|
|
||||||
if (suc) { |
|
||||||
dbg_nrf("+ NRF Tx OK!"); |
|
||||||
} else { |
|
||||||
dbg("- NRF sending failed"); |
|
||||||
} |
|
||||||
|
|
||||||
// give when it's done
|
|
||||||
xSemaphoreGive(semVcomTxReadyHandle); // similar to how it's done in USB - this is called in the Tx Done handler
|
|
||||||
} |
|
||||||
|
|
||||||
#endif // SUPPORT_NRF
|
|
@ -1,23 +0,0 @@ |
|||||||
//
|
|
||||||
// Created by MightyPork on 2018/04/06.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef GEX_CORE_IFACE_NORDIC_H |
|
||||||
#define GEX_CORE_IFACE_NORDIC_H |
|
||||||
|
|
||||||
#include "platform.h" |
|
||||||
#if SUPPORT_NRF |
|
||||||
|
|
||||||
void iface_nordic_claim_resources(void); |
|
||||||
|
|
||||||
void iface_nordic_free_resources(void); |
|
||||||
|
|
||||||
void iface_nordic_deinit(void); |
|
||||||
|
|
||||||
bool iface_nordic_init(void); |
|
||||||
|
|
||||||
void iface_nordic_transmit(const uint8_t *buff, uint32_t len); |
|
||||||
|
|
||||||
#endif // SUPPORT_NRF
|
|
||||||
|
|
||||||
#endif //GEX_CORE_IFACE_NORDIC_H
|
|
@ -1,105 +0,0 @@ |
|||||||
//
|
|
||||||
// Created by MightyPork on 2018/04/06.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "platform.h" |
|
||||||
#include "iface_uart.h" |
|
||||||
#include "resources.h" |
|
||||||
#include "hw_utils.h" |
|
||||||
#include "irq_dispatcher.h" |
|
||||||
#include "task_msg.h" |
|
||||||
#include "system_settings.h" |
|
||||||
|
|
||||||
extern osSemaphoreId semVcomTxReadyHandle; |
|
||||||
|
|
||||||
static uint32_t usart_rxi = 0; |
|
||||||
static uint8_t usart_rx_buffer[MSG_QUE_SLOT_SIZE]; |
|
||||||
static uint32_t last_rx_time = 0; |
|
||||||
|
|
||||||
void iface_uart_claim_resources(void) |
|
||||||
{ |
|
||||||
assert_param(E_SUCCESS == rsc_claim(&UNIT_SYSTEM, R_USART2)); |
|
||||||
assert_param(E_SUCCESS == rsc_claim(&UNIT_SYSTEM, R_PA2)); |
|
||||||
assert_param(E_SUCCESS == rsc_claim(&UNIT_SYSTEM, R_PA3)); |
|
||||||
} |
|
||||||
|
|
||||||
void iface_uart_free_resources(void) |
|
||||||
{ |
|
||||||
rsc_free(&UNIT_SYSTEM, R_USART2); |
|
||||||
rsc_free(&UNIT_SYSTEM, R_PA2); |
|
||||||
rsc_free(&UNIT_SYSTEM, R_PA3); |
|
||||||
} |
|
||||||
|
|
||||||
/** Handler for the USART transport */ |
|
||||||
static void com_UsartIrqHandler(void *arg) |
|
||||||
{ |
|
||||||
(void)arg; |
|
||||||
if (LL_USART_IsActiveFlag_RXNE(USART2)) { |
|
||||||
vPortEnterCritical(); |
|
||||||
{ |
|
||||||
usart_rx_buffer[usart_rxi++] = LL_USART_ReceiveData8(USART2); |
|
||||||
if (usart_rxi == MSG_QUE_SLOT_SIZE) { |
|
||||||
rxQuePostMsg(usart_rx_buffer, MSG_QUE_SLOT_SIZE); // avoid it happening in the irq
|
|
||||||
usart_rxi = 0; |
|
||||||
} |
|
||||||
last_rx_time = HAL_GetTick(); |
|
||||||
} |
|
||||||
vPortExitCritical(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** this is called from the hal tick irq */ |
|
||||||
void com_iface_flush_buffer(void) |
|
||||||
{ |
|
||||||
if (usart_rxi > 0 && (HAL_GetTick()-last_rx_time)>=2) { |
|
||||||
vPortEnterCritical(); |
|
||||||
{ |
|
||||||
rxQuePostMsg(usart_rx_buffer, usart_rxi); |
|
||||||
usart_rxi = 0; |
|
||||||
} |
|
||||||
vPortExitCritical(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
bool iface_uart_init(void) |
|
||||||
{ |
|
||||||
dbg("Setting up UART transfer"); |
|
||||||
assert_param(E_SUCCESS == hw_configure_gpiorsc_af(R_PA2, LL_GPIO_AF_1)); |
|
||||||
assert_param(E_SUCCESS == hw_configure_gpiorsc_af(R_PA3, LL_GPIO_AF_1)); |
|
||||||
|
|
||||||
__HAL_RCC_USART2_CLK_ENABLE(); |
|
||||||
__HAL_RCC_USART2_FORCE_RESET(); |
|
||||||
__HAL_RCC_USART2_RELEASE_RESET(); |
|
||||||
|
|
||||||
LL_USART_Disable(USART2); |
|
||||||
|
|
||||||
LL_USART_SetBaudRate(USART2, PLAT_APB1_HZ, LL_USART_OVERSAMPLING_16, SystemSettings.comm_uart_baud); |
|
||||||
dbg("baud = %d", (int)SystemSettings.comm_uart_baud); |
|
||||||
|
|
||||||
irqd_attach(USART2, com_UsartIrqHandler, NULL); |
|
||||||
LL_USART_EnableIT_RXNE(USART2); |
|
||||||
LL_USART_SetTransferDirection(USART2, LL_USART_DIRECTION_TX_RX); |
|
||||||
|
|
||||||
LL_USART_Enable(USART2); |
|
||||||
|
|
||||||
return true; // always OK (TODO check voltage on Rx if it's 3V3 when idle?)
|
|
||||||
} |
|
||||||
|
|
||||||
void iface_uart_deinit(void) |
|
||||||
{ |
|
||||||
// this doesn't normally happen
|
|
||||||
hw_deinit_pin_rsc(R_PA2); |
|
||||||
hw_deinit_pin_rsc(R_PA3); |
|
||||||
__HAL_RCC_USART2_CLK_DISABLE(); |
|
||||||
irqd_detach(USART2, com_UsartIrqHandler); |
|
||||||
} |
|
||||||
|
|
||||||
void iface_uart_transmit(const uint8_t *buff, uint32_t len) |
|
||||||
{ |
|
||||||
// TODO rewrite this to use DMA, then wait for the DMA
|
|
||||||
for(uint32_t i=0;i<len;i++) { |
|
||||||
while(!LL_USART_IsActiveFlag_TXE(USART2)); |
|
||||||
LL_USART_TransmitData8(USART2, buff[i]); |
|
||||||
} |
|
||||||
xSemaphoreGive(semVcomTxReadyHandle); // act as if we just finished it and this is perhaps the DMA irq
|
|
||||||
} |
|
@ -1,20 +0,0 @@ |
|||||||
//
|
|
||||||
// Created by MightyPork on 2018/04/06.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef GEX_CORE_IFACE_UART_H |
|
||||||
#define GEX_CORE_IFACE_UART_H |
|
||||||
|
|
||||||
#include "platform.h" |
|
||||||
|
|
||||||
void com_iface_flush_buffer(void); |
|
||||||
|
|
||||||
void iface_uart_deinit(void); |
|
||||||
bool iface_uart_init(void); |
|
||||||
|
|
||||||
void iface_uart_free_resources(void); |
|
||||||
void iface_uart_claim_resources(void); |
|
||||||
|
|
||||||
void iface_uart_transmit(const uint8_t *buff, uint32_t len); |
|
||||||
|
|
||||||
#endif //GEX_CORE_IFACE_UART_H
|
|
@ -1,73 +0,0 @@ |
|||||||
//
|
|
||||||
// Created by MightyPork on 2018/04/06.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "iface_usb.h" |
|
||||||
#include "TinyFrame.h" |
|
||||||
#include "system_settings.h" |
|
||||||
#include "USB/usb_device.h" |
|
||||||
|
|
||||||
extern osSemaphoreId semVcomTxReadyHandle; |
|
||||||
extern osMutexId mutTinyFrameTxHandle; |
|
||||||
|
|
||||||
bool iface_usb_ready(void) |
|
||||||
{ |
|
||||||
return 0 != (USB->DADDR & USB_DADDR_ADD); |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* USB transmit implementation |
|
||||||
* |
|
||||||
* @param buff - buffer to send (can be longer than the buffers) |
|
||||||
* @param len - buffer size |
|
||||||
*/ |
|
||||||
void iface_usb_transmit(const uint8_t *buff, uint32_t len) |
|
||||||
{ |
|
||||||
#if 1 |
|
||||||
const uint32_t real_size = len; |
|
||||||
|
|
||||||
// Padding to a multiple of 64 bytes - this is supposed to maximize the bulk transfer speed
|
|
||||||
if ((len&0x3F) && !SystemSettings.visible_vcom) { // this corrupts VCOM on Linux for some reason
|
|
||||||
uint32_t pad = (64 - (len&0x3F)); |
|
||||||
memset((void *) (buff + len), 0, pad); |
|
||||||
len += pad; // padding to a multiple of 64 (size of the endpoint)
|
|
||||||
} |
|
||||||
|
|
||||||
// We bypass the USBD driver library's overhead by using the HAL function directly
|
|
||||||
assert_param(HAL_OK == HAL_PCD_EP_Transmit(hUsbDeviceFS.pData, CDC_IN_EP, (uint8_t *) buff, len)); |
|
||||||
|
|
||||||
// The buffer is the TF transmit buffer, we can't leave it to work asynchronously because
|
|
||||||
// the next call could modify it before it's been transmitted (in the case of a chunked / multi-part frame)
|
|
||||||
|
|
||||||
// If this is not the last chunk (assuming all but the last use full 512 bytes of the TF buffer), wait now for completion
|
|
||||||
if (real_size == TF_SENDBUF_LEN) { |
|
||||||
// TODO this seems wrong - investigate
|
|
||||||
if (pdTRUE != xSemaphoreTake(semVcomTxReadyHandle, 100)) { |
|
||||||
TF_Error("Tx stalled in WriteImpl"); |
|
||||||
return; |
|
||||||
} |
|
||||||
} |
|
||||||
#else |
|
||||||
(void) tf; |
|
||||||
#define CHUNK 64 // size of the USB packet
|
|
||||||
int32_t total = (int32_t) len; |
|
||||||
while (total > 0) { |
|
||||||
const int32_t mxStatus = osSemaphoreWait(semVcomTxReadyHandle, 100); |
|
||||||
if (mxStatus != osOK) { |
|
||||||
TF_Error("Tx stalled"); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
const uint16_t chunksize = (uint16_t) MIN(total, CHUNK); |
|
||||||
|
|
||||||
// this is an attempt to speed it up a little by removing a couple levels of indirection
|
|
||||||
assert_param(HAL_OK == HAL_PCD_EP_Transmit(hUsbDeviceFS.pData, CDC_IN_EP, (uint8_t *) buff, chunksize)); |
|
||||||
|
|
||||||
// USBD_LL_Transmit(&hUsbDeviceFS, CDC_IN_EP, (uint8_t *) buff, chunksize);
|
|
||||||
// assert_param(USBD_OK == CDC_Transmit_FS((uint8_t *) buff, chunksize));
|
|
||||||
|
|
||||||
buff += chunksize; |
|
||||||
total -= chunksize; |
|
||||||
} |
|
||||||
#endif |
|
||||||
} |
|
@ -1,20 +0,0 @@ |
|||||||
//
|
|
||||||
// Created by MightyPork on 2018/04/06.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef GEX_CORE_IFACE_USB_H |
|
||||||
#define GEX_CORE_IFACE_USB_H |
|
||||||
|
|
||||||
#include "platform.h" |
|
||||||
|
|
||||||
bool iface_usb_ready(void); |
|
||||||
|
|
||||||
/**
|
|
||||||
* USB transmit implementation |
|
||||||
* |
|
||||||
* @param buff - buffer to send (can be longer than the buffers) |
|
||||||
* @param len - buffer size |
|
||||||
*/ |
|
||||||
void iface_usb_transmit(const uint8_t *buff, uint32_t len); |
|
||||||
|
|
||||||
#endif //GEX_CORE_IFACE_USB_H
|
|
@ -1,156 +0,0 @@ |
|||||||
//
|
|
||||||
// Created by MightyPork on 2018/03/23.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "platform.h" |
|
||||||
#include "usbd_core.h" |
|
||||||
#include "USB/usb_device.h" |
|
||||||
#include "interfaces.h" |
|
||||||
#include "framework/system_settings.h" |
|
||||||
#include "framework/unit.h" |
|
||||||
#include "framework/resources.h" |
|
||||||
#include "platform/hw_utils.h" |
|
||||||
#include "framework/unit_base.h" |
|
||||||
#include "nrf_pins.h" |
|
||||||
#include "iface_uart.h" |
|
||||||
#include "iface_usb.h" |
|
||||||
#include "iface_nordic.h" |
|
||||||
|
|
||||||
// those correspond to the ENUM values
|
|
||||||
const char * COMPORT_NAMES[] = { |
|
||||||
"NONE", |
|
||||||
"USB", |
|
||||||
"UART", |
|
||||||
"NRF", |
|
||||||
"LORA", |
|
||||||
}; |
|
||||||
|
|
||||||
enum ComportSelection gActiveComport = COMPORT_USB; // start with USB so the handlers work correctly initially
|
|
||||||
|
|
||||||
static uint32_t last_switch_time = 0; // started with USB
|
|
||||||
static bool xfer_verify_done = false; |
|
||||||
|
|
||||||
static bool configure_interface(enum ComportSelection iface); |
|
||||||
|
|
||||||
/** Switch com transfer if the current one doesnt seem to work */ |
|
||||||
void com_switch_transfer_if_needed(void) |
|
||||||
{ |
|
||||||
if (xfer_verify_done) return; |
|
||||||
|
|
||||||
const uint32_t now = HAL_GetTick(); |
|
||||||
const uint32_t elapsed = now - last_switch_time; |
|
||||||
|
|
||||||
if (gActiveComport == COMPORT_USB) { |
|
||||||
if (elapsed > 1000) { |
|
||||||
// USB may or may not work, depending on whether the module is plugged in
|
|
||||||
// or running from a battery/external supply remotely.
|
|
||||||
|
|
||||||
// Check if USB is enumerated
|
|
||||||
|
|
||||||
if (!iface_usb_ready()) { |
|
||||||
dbg("Not enumerated, assuming USB is dead"); |
|
||||||
|
|
||||||
// Fallback to radio or bare USART
|
|
||||||
do { |
|
||||||
if (SystemSettings.use_comm_nordic) { |
|
||||||
if (configure_interface(COMPORT_NORDIC)) break; |
|
||||||
} |
|
||||||
|
|
||||||
#if 0 |
|
||||||
if (SystemSettings.use_comm_lora) { |
|
||||||
if (configure_interface(COMPORT_LORA)) break; |
|
||||||
} |
|
||||||
#endif |
|
||||||
|
|
||||||
if (SystemSettings.use_comm_uart) { |
|
||||||
// after nordic/lora
|
|
||||||
if (configure_interface(COMPORT_USART)) break; |
|
||||||
} |
|
||||||
|
|
||||||
dbg("No alternate com interface configured."); |
|
||||||
gActiveComport = COMPORT_NONE; |
|
||||||
} while (0); |
|
||||||
} else { |
|
||||||
dbg("USB got address - OK"); |
|
||||||
} |
|
||||||
|
|
||||||
xfer_verify_done = true; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** Claim resources that may be needed for alternate transfers */ |
|
||||||
void com_claim_resources_for_alt_transfers(void) |
|
||||||
{ |
|
||||||
if (SystemSettings.use_comm_uart) { |
|
||||||
iface_uart_claim_resources(); |
|
||||||
} |
|
||||||
|
|
||||||
#if SUPPORT_NRF |
|
||||||
if (SystemSettings.use_comm_nordic) { |
|
||||||
iface_nordic_claim_resources(); |
|
||||||
} |
|
||||||
#endif // SUPPORT_NRF
|
|
||||||
} |
|
||||||
|
|
||||||
/** Release resources allocated for alternate transfers */ |
|
||||||
void com_release_resources_for_alt_transfers(void) |
|
||||||
{ |
|
||||||
if (SystemSettings.use_comm_uart) { |
|
||||||
iface_uart_free_resources(); |
|
||||||
} |
|
||||||
|
|
||||||
#if SUPPORT_NRF |
|
||||||
if (SystemSettings.use_comm_nordic) { |
|
||||||
iface_nordic_free_resources(); |
|
||||||
} |
|
||||||
#endif // SUPPORT_NRF
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
static bool configure_interface(enum ComportSelection iface) |
|
||||||
{ |
|
||||||
// Teardown
|
|
||||||
if (gActiveComport == COMPORT_USB) { |
|
||||||
// simplest USB disabling (XXX needs porting)
|
|
||||||
HAL_PCD_DeInit(&hpcd_USB_FS); |
|
||||||
__HAL_RCC_USB_CLK_DISABLE(); |
|
||||||
} |
|
||||||
else if (gActiveComport == COMPORT_USART) { |
|
||||||
iface_uart_deinit(); |
|
||||||
} |
|
||||||
#if SUPPORT_NRF |
|
||||||
else if (gActiveComport == COMPORT_NORDIC) { |
|
||||||
iface_nordic_deinit(); |
|
||||||
} |
|
||||||
#endif // SUPPORT_NRF
|
|
||||||
|
|
||||||
|
|
||||||
gActiveComport = iface; |
|
||||||
|
|
||||||
// Init
|
|
||||||
if (iface == COMPORT_USB) { |
|
||||||
trap("illegal"); // this never happens
|
|
||||||
return false; |
|
||||||
} |
|
||||||
else if (iface == COMPORT_USART) { |
|
||||||
return iface_uart_init(); |
|
||||||
} |
|
||||||
#if SUPPORT_NRF |
|
||||||
else if (iface == COMPORT_NORDIC) { |
|
||||||
return iface_nordic_init(); |
|
||||||
} |
|
||||||
#endif // SUPPORT_NRF
|
|
||||||
#if 0 |
|
||||||
else if (iface == COMPORT_LORA) { |
|
||||||
// Try to configure nordic
|
|
||||||
dbg("Setting up LoRa transfer"); |
|
||||||
// TODO set up and check LoRa transport
|
|
||||||
dbg("LoRa not impl!"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
#endif |
|
||||||
else { |
|
||||||
trap("Bad iface %d", iface); |
|
||||||
} |
|
||||||
} |
|
@ -1,37 +0,0 @@ |
|||||||
//
|
|
||||||
// Created by MightyPork on 2018/03/23.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef GEX_CORE_COM_INTERFACES_H |
|
||||||
#define GEX_CORE_COM_INTERFACES_H |
|
||||||
|
|
||||||
#include "platform.h" |
|
||||||
|
|
||||||
enum ComportSelection { |
|
||||||
COMPORT_NONE = 0, |
|
||||||
COMPORT_USB = 1, |
|
||||||
COMPORT_USART = 2, |
|
||||||
COMPORT_NORDIC = 3, |
|
||||||
COMPORT_LORA = 4, |
|
||||||
}; |
|
||||||
|
|
||||||
extern const char * COMPORT_NAMES[]; |
|
||||||
|
|
||||||
/**
|
|
||||||
* The currently active communication port |
|
||||||
*/ |
|
||||||
extern enum ComportSelection gActiveComport; |
|
||||||
|
|
||||||
/** Switch com transfer if the current one doesnt seem to work */ |
|
||||||
void com_switch_transfer_if_needed(void); |
|
||||||
|
|
||||||
/** Claim resources that may be needed for alternate transfers */ |
|
||||||
void com_claim_resources_for_alt_transfers(void); |
|
||||||
|
|
||||||
/** Release resources allocated for alternate transfers */ |
|
||||||
void com_release_resources_for_alt_transfers(void); |
|
||||||
|
|
||||||
/** Flush the rx buffer */ |
|
||||||
void com_iface_flush_buffer(void); |
|
||||||
|
|
||||||
#endif //GEX_CORE_COM_INTERFACES_H
|
|
@ -1,551 +0,0 @@ |
|||||||
//
|
|
||||||
// Created by MightyPork on 2018/04/02.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "platform.h" |
|
||||||
#if SUPPORT_NRF |
|
||||||
|
|
||||||
#include "nrf.h" |
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a register |
|
||||||
* |
|
||||||
* @param reg - register number (max 83) |
|
||||||
* @return register value |
|
||||||
*/ |
|
||||||
static uint8_t NRF_ReadRegister(uint8_t reg); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a register |
|
||||||
* |
|
||||||
* @param reg - register number (max 83) |
|
||||||
* @param value - register value |
|
||||||
* @return status register value |
|
||||||
*/ |
|
||||||
static uint8_t NRF_WriteRegister(uint8_t reg, uint8_t value); |
|
||||||
|
|
||||||
// Internal use
|
|
||||||
static uint8_t NRF_WriteBuffer(uint8_t reg, const uint8_t *pBuf, uint8_t bytes); |
|
||||||
|
|
||||||
static uint8_t NRF_ReadBuffer(uint8_t reg, uint8_t *pBuf, uint8_t bytes); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Set TX address for next frame |
|
||||||
* |
|
||||||
* @param SendTo - addr |
|
||||||
*/ |
|
||||||
static void NRF_SetTxAddress(uint8_t ToAddr); |
|
||||||
|
|
||||||
//----------------------------------------------------------
|
|
||||||
|
|
||||||
/** Tight asm loop */ |
|
||||||
#define __asm_loop(cycles) \ |
|
||||||
do { \
|
|
||||||
register uint32_t _count asm ("r4") = cycles; \
|
|
||||||
asm volatile( \
|
|
||||||
".syntax unified\n" \
|
|
||||||
".thumb\n" \
|
|
||||||
"0:" \
|
|
||||||
"subs %0, #1\n" \
|
|
||||||
"bne 0b\n" \
|
|
||||||
: "+r" (_count)); \
|
|
||||||
} while(0) |
|
||||||
|
|
||||||
// 12 for 72 MHz
|
|
||||||
#define _delay_us(n) __asm_loop((n)*8) |
|
||||||
|
|
||||||
static inline void CE(bool level) { |
|
||||||
__asm_loop(1); |
|
||||||
if (level) LL_GPIO_SetOutputPin(NRF_CE_GPIO_Port, NRF_CE_Pin); |
|
||||||
else LL_GPIO_ResetOutputPin(NRF_CE_GPIO_Port, NRF_CE_Pin); |
|
||||||
__asm_loop(1); |
|
||||||
} |
|
||||||
|
|
||||||
static inline void NSS(bool level) { |
|
||||||
__asm_loop(1); |
|
||||||
if (level) LL_GPIO_SetOutputPin(NRF_NSS_GPIO_Port, NRF_NSS_Pin); |
|
||||||
else LL_GPIO_ResetOutputPin(NRF_NSS_GPIO_Port, NRF_NSS_Pin); |
|
||||||
__asm_loop(1); |
|
||||||
} |
|
||||||
|
|
||||||
static uint8_t spi(uint8_t tx) { |
|
||||||
while (! LL_SPI_IsActiveFlag_TXE(NRF_SPI)); |
|
||||||
LL_SPI_TransmitData8(NRF_SPI, tx); |
|
||||||
while (! LL_SPI_IsActiveFlag_RXNE(NRF_SPI)); |
|
||||||
return LL_SPI_ReceiveData8(NRF_SPI); |
|
||||||
} |
|
||||||
|
|
||||||
void NRF_Reset(bool enable_reset) |
|
||||||
{ |
|
||||||
if (enable_reset) { |
|
||||||
// go HIGH - this closes the PMOS
|
|
||||||
LL_GPIO_SetOutputPin(NRF_RST_GPIO_Port, NRF_RST_Pin); |
|
||||||
} else { |
|
||||||
// LOW - open PMOS, enable power
|
|
||||||
LL_GPIO_ResetOutputPin(NRF_RST_GPIO_Port, NRF_RST_Pin); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
//-------
|
|
||||||
|
|
||||||
/*
|
|
||||||
* from: http://barefootelectronics.com/NRF24L01.aspx
|
|
||||||
*/ |
|
||||||
|
|
||||||
#define CMD_READ_REG 0x00 // Read command to register
|
|
||||||
#define CMD_WRITE_REG 0x20 // Write command to register
|
|
||||||
#define CMD_RD_RX_PLD 0x61 // Read RX payload register address
|
|
||||||
#define CMD_WR_TX_PLD 0xA0 // Write TX payload register address
|
|
||||||
#define CMD_FLUSH_TX 0xE1 // Flush TX register command
|
|
||||||
#define CMD_FLUSH_RX 0xE2 // Flush RX register command
|
|
||||||
#define CMD_REUSE_TX_PL 0xE3 // Reuse TX payload register command
|
|
||||||
#define CMD_RD_RX_PL_WIDTH 0x60 // Read RX Payload Width
|
|
||||||
#define CMD_WR_ACK_PLD 0xA8 // Write ACK payload for Pipe (Add pipe number 0-6)
|
|
||||||
#define CMD_WR_TX_PLD_NK 0xB0 // Write ACK payload for not ack
|
|
||||||
#define CMD_NOP 0xFF // No Operation, might be used to read status register
|
|
||||||
|
|
||||||
// SPI(nRF24L01) registers(addresses)
|
|
||||||
#define RG_CONFIG 0x00 // 'Config' register address
|
|
||||||
#define RG_EN_AA 0x01 // 'Enable Auto Acknowledgment' register address
|
|
||||||
#define RG_EN_RXADDR 0x02 // 'Enabled RX addresses' register address
|
|
||||||
#define RG_SETUP_AW 0x03 // 'Setup address width' register address
|
|
||||||
#define RG_SETUP_RETR 0x04 // 'Setup Auto. Retrans' register address
|
|
||||||
#define RG_RF_CH 0x05 // 'RF channel' register address
|
|
||||||
#define RG_RF_SETUP 0x06 // 'RF setup' register address
|
|
||||||
#define RG_STATUS 0x07 // 'Status' register address
|
|
||||||
#define RG_OBSERVE_TX 0x08 // 'Observe TX' register address
|
|
||||||
#define RG_CD 0x09 // 'Carrier Detect' register address
|
|
||||||
#define RG_RX_ADDR_P0 0x0A // 'RX address pipe0' register address
|
|
||||||
#define RG_RX_ADDR_P1 0x0B // 'RX address pipe1' register address
|
|
||||||
#define RG_RX_ADDR_P2 0x0C // 'RX address pipe2' register address
|
|
||||||
#define RG_RX_ADDR_P3 0x0D // 'RX address pipe3' register address
|
|
||||||
#define RG_RX_ADDR_P4 0x0E // 'RX address pipe4' register address
|
|
||||||
#define RG_RX_ADDR_P5 0x0F // 'RX address pipe5' register address
|
|
||||||
#define RG_TX_ADDR 0x10 // 'TX address' register address
|
|
||||||
#define RG_RX_PW_P0 0x11 // 'RX payload width, pipe0' register address
|
|
||||||
#define RG_RX_PW_P1 0x12 // 'RX payload width, pipe1' register address
|
|
||||||
#define RG_RX_PW_P2 0x13 // 'RX payload width, pipe2' register address
|
|
||||||
#define RG_RX_PW_P3 0x14 // 'RX payload width, pipe3' register address
|
|
||||||
#define RG_RX_PW_P4 0x15 // 'RX payload width, pipe4' register address
|
|
||||||
#define RG_RX_PW_P5 0x16 // 'RX payload width, pipe5' register address
|
|
||||||
#define RG_FIFO_STATUS 0x17 // 'FIFO Status Register' register address
|
|
||||||
#define RG_DYNPD 0x1C // 'Enable dynamic payload length
|
|
||||||
#define RG_FEATURE 0x1D // 'Feature register
|
|
||||||
|
|
||||||
#define RD_STATUS_TX_FULL 0x01 // tx queue full
|
|
||||||
#define RD_STATUS_RX_PNO 0x0E // pipe number of the received payload
|
|
||||||
#define RD_STATUS_MAX_RT 0x10 // max retransmissions
|
|
||||||
#define RD_STATUS_TX_DS 0x20 // data sent
|
|
||||||
#define RD_STATUS_RX_DR 0x40 // data ready
|
|
||||||
|
|
||||||
#define RD_CONFIG_PRIM_RX 0x01 // is primary receiver
|
|
||||||
#define RD_CONFIG_PWR_UP 0x02 // power up
|
|
||||||
#define RD_CONFIG_CRCO 0x04 // 2-byte CRC
|
|
||||||
#define RD_CONFIG_EN_CRC 0x08 // enable CRC
|
|
||||||
#define RD_CONFIG_DISABLE_IRQ_MAX_RT 0x10 |
|
||||||
#define RD_CONFIG_DISABLE_IRQ_TX_DS 0x20 |
|
||||||
#define RD_CONFIG_DISABLE_IRQ_RX_DR 0x40 |
|
||||||
|
|
||||||
#define RD_FIFO_STATUS_RX_EMPTY 0x01 |
|
||||||
#define RD_FIFO_STATUS_RX_FULL 0x02 |
|
||||||
#define RD_FIFO_STATUS_TX_EMPTY 0x10 |
|
||||||
#define RD_FIFO_STATUS_TX_FULL 0x20 |
|
||||||
#define RD_FIFO_STATUS_TX_REUSE 0x40 |
|
||||||
|
|
||||||
// Config register bits (excluding the bottom two that are changed dynamically)
|
|
||||||
// enable only Rx IRQ
|
|
||||||
#define ModeBits (RD_CONFIG_DISABLE_IRQ_MAX_RT | \ |
|
||||||
RD_CONFIG_DISABLE_IRQ_TX_DS | \
|
|
||||||
RD_CONFIG_EN_CRC | \
|
|
||||||
RD_CONFIG_CRCO) |
|
||||||
|
|
||||||
static inline uint8_t CS(uint8_t hl) |
|
||||||
{ |
|
||||||
if (hl == 1) { |
|
||||||
NSS(0); |
|
||||||
} |
|
||||||
else { |
|
||||||
NSS(1); |
|
||||||
} |
|
||||||
return hl; |
|
||||||
} |
|
||||||
|
|
||||||
#define CHIPSELECT for (uint8_t cs = CS(1); cs==1; cs = CS(0)) |
|
||||||
|
|
||||||
// Base NRF address - byte 4 may be modified before writing to a pipe register,
|
|
||||||
// the first 4 bytes are shared in the network. Master is always code 0.
|
|
||||||
static uint8_t nrf_base_address[5]; |
|
||||||
|
|
||||||
static uint8_t nrf_pipe_addr[6]; |
|
||||||
static bool nrf_pipe_enabled[6]; |
|
||||||
|
|
||||||
static uint8_t NRF_WriteRegister(uint8_t reg, uint8_t value) |
|
||||||
{ |
|
||||||
uint8_t status = 0; |
|
||||||
CHIPSELECT { |
|
||||||
status = spi(CMD_WRITE_REG | reg); |
|
||||||
spi(value); |
|
||||||
} |
|
||||||
dbg_nrf("Wr[0x%02x] := 0x%02x", (int)reg, (int) value); |
|
||||||
|
|
||||||
|
|
||||||
uint8_t reg_val = 0; |
|
||||||
CHIPSELECT { |
|
||||||
spi(CMD_READ_REG | reg); |
|
||||||
reg_val = spi(0); |
|
||||||
} |
|
||||||
dbg_nrf(" verify 0x%02x", (int)reg_val); |
|
||||||
if (reg_val != value) |
|
||||||
dbg_nrf(" !!!"); |
|
||||||
else |
|
||||||
dbg_nrf(" OK"); |
|
||||||
|
|
||||||
|
|
||||||
return status; |
|
||||||
} |
|
||||||
|
|
||||||
static uint8_t NRF_ReadRegister(uint8_t reg) |
|
||||||
{ |
|
||||||
uint8_t reg_val = 0; |
|
||||||
CHIPSELECT { |
|
||||||
spi(CMD_READ_REG | reg); |
|
||||||
reg_val = spi(0); |
|
||||||
} |
|
||||||
dbg_nrf("Rd[0x%02x] = 0x%02x", (int)reg, (int) reg_val); |
|
||||||
return reg_val; |
|
||||||
} |
|
||||||
|
|
||||||
static uint8_t NRF_ReadStatus(void) |
|
||||||
{ |
|
||||||
uint8_t reg_val = 0; |
|
||||||
CHIPSELECT { |
|
||||||
reg_val = spi(CMD_NOP); |
|
||||||
} |
|
||||||
return reg_val; |
|
||||||
} |
|
||||||
|
|
||||||
static uint8_t NRF_WriteBuffer(uint8_t reg, const uint8_t *pBuf, uint8_t bytes) |
|
||||||
{ |
|
||||||
uint8_t status = 0, i = 0; |
|
||||||
CHIPSELECT { |
|
||||||
status = spi(CMD_WRITE_REG | reg); |
|
||||||
for (i = 0; i < bytes; i++) { |
|
||||||
spi(*pBuf++); |
|
||||||
} |
|
||||||
} |
|
||||||
return status; |
|
||||||
} |
|
||||||
|
|
||||||
static uint8_t NRF_ReadBuffer(uint8_t reg, uint8_t *pBuf, uint8_t bytes) |
|
||||||
{ |
|
||||||
uint8_t status = 0, i; |
|
||||||
CHIPSELECT { |
|
||||||
status = spi(CMD_READ_REG | reg); |
|
||||||
for (i = 0; i < bytes; i++) { |
|
||||||
pBuf[i] = spi(0); |
|
||||||
} |
|
||||||
} |
|
||||||
return status; |
|
||||||
} |
|
||||||
|
|
||||||
void NRF_SetBaseAddress(const uint8_t *Bytes4) |
|
||||||
{ |
|
||||||
memcpy(nrf_base_address, Bytes4, 4); |
|
||||||
nrf_base_address[4] = 0; |
|
||||||
|
|
||||||
// write it to the two full-width address registers
|
|
||||||
NRF_WriteBuffer(RG_RX_ADDR_P0, nrf_base_address, 5); |
|
||||||
nrf_base_address[4] = 1;// to be different
|
|
||||||
NRF_WriteBuffer(RG_RX_ADDR_P1, nrf_base_address, 5); |
|
||||||
} |
|
||||||
|
|
||||||
void NRF_SetRxAddress(uint8_t pipenum, uint8_t AddrByte) |
|
||||||
{ |
|
||||||
if (pipenum > 5) { |
|
||||||
dbg_nrf("!! bad pipe %d", pipenum); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
nrf_base_address[4] = AddrByte; |
|
||||||
|
|
||||||
nrf_pipe_addr[pipenum] = AddrByte; |
|
||||||
|
|
||||||
dbg_nrf("Set Rx addr (pipe %d) = 0x%02x", (int)pipenum, AddrByte); |
|
||||||
if (pipenum == 0) { |
|
||||||
dbg_nrf("W ADDR_PA0: %02X-%02X-%02X-%02X-%02X", nrf_base_address[0], nrf_base_address[1], nrf_base_address[2], nrf_base_address[3], nrf_base_address[4]); |
|
||||||
NRF_WriteBuffer(RG_RX_ADDR_P0, nrf_base_address, 5); |
|
||||||
} |
|
||||||
else if (pipenum == 1) { |
|
||||||
dbg_nrf("W ADDR_PA1: %02X-%02X-%02X-%02X-%02X", nrf_base_address[0], nrf_base_address[1], nrf_base_address[2], nrf_base_address[3], nrf_base_address[4]); |
|
||||||
NRF_WriteBuffer(RG_RX_ADDR_P1, nrf_base_address, 5); |
|
||||||
} |
|
||||||
else { |
|
||||||
NRF_WriteRegister(RG_RX_ADDR_P2 + (pipenum - 2), AddrByte); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
bool NRF_AddPipe(uint8_t AddrByte, uint8_t *PipeNum) |
|
||||||
{ |
|
||||||
for (uint8_t i = 0; i < 6; i++) { |
|
||||||
if(!nrf_pipe_enabled[i]) { |
|
||||||
NRF_SetRxAddress(i, AddrByte); |
|
||||||
*PipeNum = i; |
|
||||||
NRF_EnablePipe(i); |
|
||||||
return true; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
uint8_t NRF_PipeNum2Addr(uint8_t pipe_num) |
|
||||||
{ |
|
||||||
if (pipe_num > 5) return 0; // fail
|
|
||||||
return nrf_pipe_addr[pipe_num]; |
|
||||||
} |
|
||||||
|
|
||||||
uint8_t NRF_Addr2PipeNum(uint8_t addr) |
|
||||||
{ |
|
||||||
for (int i = 0; i < 6; i++) { |
|
||||||
if (nrf_pipe_addr[i] == addr) return (uint8_t) i; |
|
||||||
} |
|
||||||
return 0xFF; |
|
||||||
} |
|
||||||
|
|
||||||
void NRF_EnablePipe(uint8_t pipenum) |
|
||||||
{ |
|
||||||
dbg_nrf("Enable pipe num %d", (int)pipenum); |
|
||||||
uint8_t enabled = NRF_ReadRegister(RG_EN_RXADDR); |
|
||||||
enabled |= 1 << pipenum; |
|
||||||
NRF_WriteRegister(RG_EN_RXADDR, enabled); |
|
||||||
|
|
||||||
nrf_pipe_enabled[pipenum] = 1; |
|
||||||
} |
|
||||||
|
|
||||||
void NRF_DisablePipe(uint8_t pipenum) |
|
||||||
{ |
|
||||||
uint8_t enabled = NRF_ReadRegister(RG_EN_RXADDR); |
|
||||||
enabled &= ~(1 << pipenum); |
|
||||||
NRF_WriteRegister(RG_EN_RXADDR, enabled); |
|
||||||
|
|
||||||
nrf_pipe_enabled[pipenum] = 0; |
|
||||||
} |
|
||||||
|
|
||||||
static void NRF_SetTxAddress(uint8_t SendTo) |
|
||||||
{ |
|
||||||
nrf_base_address[4] = SendTo; |
|
||||||
|
|
||||||
dbg_nrf("W Tx_ADDR + Rx0: %02X-%02X-%02X-%02X-%02X", |
|
||||||
nrf_base_address[0], nrf_base_address[1], nrf_base_address[2], nrf_base_address[3], nrf_base_address[4]); |
|
||||||
|
|
||||||
NRF_WriteBuffer(RG_TX_ADDR, nrf_base_address, 5); |
|
||||||
NRF_WriteBuffer(RG_RX_ADDR_P0, nrf_base_address, 5); // the ACK will come to pipe 0
|
|
||||||
} |
|
||||||
|
|
||||||
void NRF_PowerDown(void) |
|
||||||
{ |
|
||||||
dbg_nrf("PDn"); |
|
||||||
CE(0); |
|
||||||
NRF_WriteRegister(RG_CONFIG, ModeBits); |
|
||||||
} |
|
||||||
|
|
||||||
void NRF_ModeTX(void) |
|
||||||
{ |
|
||||||
dbg_nrf("Tx Mode"); |
|
||||||
|
|
||||||
CE(0); |
|
||||||
uint8_t m = NRF_ReadRegister(RG_CONFIG); |
|
||||||
NRF_WriteRegister(RG_CONFIG, ModeBits | RD_CONFIG_PWR_UP); |
|
||||||
if ((m & RD_CONFIG_PWR_UP) == 0) { |
|
||||||
// switching on
|
|
||||||
LL_mDelay(5); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void NRF_ModeRX(void) |
|
||||||
{ |
|
||||||
dbg_nrf("Rx Mode"); |
|
||||||
NRF_WriteRegister(RG_CONFIG, ModeBits | RD_CONFIG_PWR_UP | RD_CONFIG_PRIM_RX); |
|
||||||
NRF_SetRxAddress(0, nrf_pipe_addr[0]); // set the P0 address - it was changed during Rx for ACK reception
|
|
||||||
CE(1); |
|
||||||
|
|
||||||
//if ((m&2)==0) LL_mDelay()(5); You don't need to wait. Just nothing will come for 5ms or more
|
|
||||||
} |
|
||||||
|
|
||||||
uint8_t NRF_IsModePowerDown(void) |
|
||||||
{ |
|
||||||
uint8_t ret = 0; |
|
||||||
if ((NRF_ReadRegister(RG_CONFIG) & RD_CONFIG_PWR_UP) == 0) ret = 1; |
|
||||||
return ret; |
|
||||||
} |
|
||||||
|
|
||||||
uint8_t NRF_IsModeTX(void) |
|
||||||
{ |
|
||||||
uint8_t m = NRF_ReadRegister(RG_CONFIG); |
|
||||||
if ((m & RD_CONFIG_PWR_UP) == 0) return 0; // OFF
|
|
||||||
if ((m & RD_CONFIG_PRIM_RX) == 0) return 1; |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
uint8_t NRF_IsModeRx(void) |
|
||||||
{ |
|
||||||
uint8_t m = NRF_ReadRegister(RG_CONFIG); |
|
||||||
if ((m & RD_CONFIG_PWR_UP) == 0) return 0; // OFF
|
|
||||||
if ((m & RD_CONFIG_PRIM_RX) == 0) return 0; |
|
||||||
return 1; |
|
||||||
} |
|
||||||
|
|
||||||
void NRF_SetChannel(uint8_t Ch) |
|
||||||
{ |
|
||||||
NRF_WriteRegister(RG_RF_CH, Ch); |
|
||||||
} |
|
||||||
|
|
||||||
uint8_t NRF_ReceivePacket(uint8_t *Packet, uint8_t *PipeNum) |
|
||||||
{ |
|
||||||
uint8_t pw = 0, status; |
|
||||||
// if (!NRF_IsRxPacket()) {
|
|
||||||
// dbg("rx queue empty");
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const uint8_t orig_conf = NRF_ReadRegister(RG_CONFIG);
|
|
||||||
// CE(0); // quit Rx mode - go idle
|
|
||||||
|
|
||||||
CHIPSELECT { |
|
||||||
status = spi(CMD_RD_RX_PL_WIDTH); |
|
||||||
pw = spi(0); |
|
||||||
} |
|
||||||
|
|
||||||
if (pw == 0) { |
|
||||||
dbg("empty pld"); |
|
||||||
} |
|
||||||
|
|
||||||
if (pw > 32) { |
|
||||||
CHIPSELECT { |
|
||||||
spi(CMD_FLUSH_RX); |
|
||||||
} |
|
||||||
pw = 0; |
|
||||||
dbg("over 32"); |
|
||||||
} else { |
|
||||||
// Read the reception pipe number
|
|
||||||
*PipeNum = ((status & RD_STATUS_RX_PNO) >> 1); |
|
||||||
CHIPSELECT { |
|
||||||
spi(CMD_RD_RX_PLD); |
|
||||||
for (uint8_t i = 0; i < pw; i++) Packet[i] = spi(0); |
|
||||||
} |
|
||||||
} |
|
||||||
NRF_WriteRegister(RG_STATUS, RD_STATUS_RX_DR); // Clear the RX_DR interrupt
|
|
||||||
|
|
||||||
// if ((orig_conf & RD_CONFIG_PWR_UP) == 0) {
|
|
||||||
// dbg_nrf("going back PwrDn");
|
|
||||||
// NRF_PowerDown();
|
|
||||||
// }
|
|
||||||
// else if ((orig_conf & RD_CONFIG_PRIM_RX) == RD_CONFIG_PRIM_RX) {
|
|
||||||
// dbg_nrf("going back PwrUp+Rx");
|
|
||||||
// NRF_ModeRX();
|
|
||||||
// }
|
|
||||||
// CE(1); // back to rx
|
|
||||||
return pw; |
|
||||||
} |
|
||||||
|
|
||||||
bool NRF_IsRxPacket(void) |
|
||||||
{ |
|
||||||
return 0 == (NRF_ReadRegister(RG_FIFO_STATUS) & RD_FIFO_STATUS_RX_EMPTY); |
|
||||||
// uint8_t ret = NRF_ReadRegister(RG_STATUS) & RD_STATUS_RX_DR;
|
|
||||||
// return 0 != ret;
|
|
||||||
} |
|
||||||
|
|
||||||
bool NRF_SendPacket(uint8_t PipeNum, const uint8_t *Packet, uint8_t Length) |
|
||||||
{ |
|
||||||
if (!nrf_pipe_enabled[PipeNum]) { |
|
||||||
dbg_nrf("!! Pipe %d not enabled", PipeNum); |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
dbg_nrf("Will tx to addr %02x", nrf_pipe_addr[PipeNum]); |
|
||||||
|
|
||||||
const uint8_t orig_conf = NRF_ReadRegister(RG_CONFIG); |
|
||||||
CE(0); |
|
||||||
NRF_ModeTX(); // Make sure in TX mode
|
|
||||||
NRF_SetTxAddress(nrf_pipe_addr[PipeNum]); // this sets the Tx addr and also pipe 0 addr for ACK
|
|
||||||
|
|
||||||
CHIPSELECT { |
|
||||||
spi(CMD_FLUSH_TX); |
|
||||||
}; |
|
||||||
|
|
||||||
CHIPSELECT { |
|
||||||
spi(CMD_WR_TX_PLD); |
|
||||||
for (uint8_t i = 0; i < Length; i++) spi(Packet[i]); |
|
||||||
}; |
|
||||||
|
|
||||||
// CE pulse
|
|
||||||
CE(1); |
|
||||||
_delay_us(20); // At least 10 us
|
|
||||||
CE(0); |
|
||||||
|
|
||||||
uint8_t st = 0; |
|
||||||
while ((st & (RD_STATUS_MAX_RT|RD_STATUS_TX_DS)) == 0) { |
|
||||||
st = NRF_ReadStatus(); // Packet acked or timed out
|
|
||||||
} |
|
||||||
|
|
||||||
dbg_nrf("Send status: 0x%02x - MAX_RT %d, SENT %d", (int)st, |
|
||||||
(st&RD_STATUS_MAX_RT) != 0, (st&RD_STATUS_TX_DS) != 0); |
|
||||||
|
|
||||||
NRF_WriteRegister(RG_STATUS, st & (RD_STATUS_MAX_RT|RD_STATUS_TX_DS)); // Clear the bit
|
|
||||||
|
|
||||||
if ((orig_conf & RD_CONFIG_PWR_UP) == 0) { |
|
||||||
dbg_nrf("going back PwrDn"); |
|
||||||
NRF_PowerDown(); |
|
||||||
} |
|
||||||
else if ((orig_conf & RD_CONFIG_PRIM_RX) == RD_CONFIG_PRIM_RX) { |
|
||||||
dbg_nrf("going back PwrUp+Rx"); |
|
||||||
NRF_ModeRX(); |
|
||||||
} |
|
||||||
|
|
||||||
return 0 != (st & RD_STATUS_TX_DS); // success
|
|
||||||
} |
|
||||||
|
|
||||||
bool NRF_Init(uint8_t pSpeed) |
|
||||||
{ |
|
||||||
// Set the required output pins
|
|
||||||
NSS(1); |
|
||||||
CE(0); |
|
||||||
|
|
||||||
LL_mDelay(200); |
|
||||||
|
|
||||||
for (int i = 0; i < 6; i++) { |
|
||||||
nrf_pipe_addr[i] = 0; |
|
||||||
nrf_pipe_enabled[i] = 0; |
|
||||||
} |
|
||||||
|
|
||||||
// this is a test if there's anything present
|
|
||||||
uint8_t awreg = NRF_ReadRegister(RG_SETUP_AW); |
|
||||||
if (awreg == 0 || awreg > 3) return false; |
|
||||||
|
|
||||||
// clear flags etc
|
|
||||||
NRF_PowerDown(); |
|
||||||
CHIPSELECT { spi(CMD_FLUSH_RX); } |
|
||||||
CHIPSELECT { spi(CMD_FLUSH_TX); } |
|
||||||
NRF_WriteRegister(RG_STATUS, 0x70); |
|
||||||
|
|
||||||
NRF_WriteRegister(RG_CONFIG, ModeBits); |
|
||||||
NRF_WriteRegister(RG_SETUP_AW, 0b11); // 5 byte addresses
|
|
||||||
|
|
||||||
NRF_WriteRegister(RG_EN_RXADDR, 0x01); // disable all, enable pipe 0 - this is required for shockburst, despite not being specified in the DS
|
|
||||||
|
|
||||||
NRF_WriteRegister(RG_SETUP_RETR, 0x18); // 8 retries
|
|
||||||
NRF_WriteRegister(RG_RF_CH, 2); // channel 2 NO HIGHER THAN 83 in USA!
|
|
||||||
|
|
||||||
NRF_WriteRegister(RG_RF_SETUP, pSpeed); |
|
||||||
|
|
||||||
NRF_WriteRegister(RG_DYNPD, 0b111111); // Dynamic packet length
|
|
||||||
NRF_WriteRegister(RG_FEATURE, 0b100); // Enable dynamic payload, and no payload in the ack.
|
|
||||||
|
|
||||||
// for (int i = 0; i < 6; i++) {
|
|
||||||
// NRF_WriteRegister(RG_RX_PW_P0+i, 32); // Receive 32 byte packets - XXX this is probably not needed with dynamic length
|
|
||||||
// }
|
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
#endif |
|
@ -1,163 +0,0 @@ |
|||||||
//
|
|
||||||
// Created by MightyPork on 2018/04/02.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef GEX_NRF_NRF_H |
|
||||||
#define GEX_NRF_NRF_H |
|
||||||
|
|
||||||
/*
|
|
||||||
* nordic.h |
|
||||||
* |
|
||||||
* Created:12/16/2013 3:36:04 PM |
|
||||||
* Author: Tom |
|
||||||
* |
|
||||||
* NRF24L01+ Library II |
|
||||||
* |
|
||||||
*/ |
|
||||||
|
|
||||||
#include "platform.h" |
|
||||||
#if SUPPORT_NRF |
|
||||||
|
|
||||||
#include "resources.h" |
|
||||||
#include "nrf_pins.h" |
|
||||||
|
|
||||||
#define dbg_nrf(...) do{}while(0) |
|
||||||
//#define dbg_nrf dbg
|
|
||||||
|
|
||||||
// Initialize SPI and the Nordic
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the NRF module |
|
||||||
* |
|
||||||
* @param pSpeed |
|
||||||
* @return success (0 if device not detected) |
|
||||||
*/ |
|
||||||
bool NRF_Init(uint8_t pSpeed); |
|
||||||
|
|
||||||
#define NRF_SPEED_500k 0b00100110 |
|
||||||
#define NRF_SPEED_2M 0b00001110 |
|
||||||
#define NRF_SPEED_1M 0b00000110 |
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the reset pin (this is a PMOS controlling its power) |
|
||||||
* |
|
||||||
* @param enable_reset 1 to go into reset, 0 to enter operational state (will need some time to become ready) |
|
||||||
*/ |
|
||||||
void NRF_Reset(bool enable_reset); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Set reception address |
|
||||||
* @param pipenum - pipe to set |
|
||||||
* @param AddrByte - byte 0 |
|
||||||
*/ |
|
||||||
void NRF_SetRxAddress(uint8_t pipenum, uint8_t AddrByte); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Set communication channel |
|
||||||
*/ |
|
||||||
void NRF_SetChannel(uint8_t Ch) ; // 0 through 83 only!
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Power down the transceiver |
|
||||||
*/ |
|
||||||
void NRF_PowerDown(void); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Selecr RX mode |
|
||||||
*/ |
|
||||||
void NRF_ModeRX(void); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Select TX mode |
|
||||||
*/ |
|
||||||
void NRF_ModeTX(void); |
|
||||||
|
|
||||||
/**
|
|
||||||
* @return NRF is power down |
|
||||||
*/ |
|
||||||
uint8_t NRF_IsModePowerDown(void); |
|
||||||
|
|
||||||
/**
|
|
||||||
* @return NRF in TX mode |
|
||||||
*/ |
|
||||||
uint8_t NRF_IsModeTX(void); |
|
||||||
|
|
||||||
/**
|
|
||||||
* @return NRF in RX mode |
|
||||||
*/ |
|
||||||
uint8_t NRF_IsModeRx(void); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a pipe to the next free slot |
|
||||||
* |
|
||||||
* @param AddrByte - address byte of the peer |
|
||||||
* @param[out] PipeNum - pipe number is written here |
|
||||||
* @return success |
|
||||||
*/ |
|
||||||
bool NRF_AddPipe(uint8_t AddrByte, uint8_t *PipeNum); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert pipe number to address byte |
|
||||||
* |
|
||||||
* @param pipe_num - pipe number |
|
||||||
* @return address byte |
|
||||||
*/ |
|
||||||
uint8_t NRF_PipeNum2Addr(uint8_t pipe_num); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert address to a pipe number |
|
||||||
* |
|
||||||
* @param addr |
|
||||||
* @return |
|
||||||
*/ |
|
||||||
uint8_t NRF_Addr2PipeNum(uint8_t addr); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a packet (takes care of mode switching etc) |
|
||||||
* |
|
||||||
* @param PipeNum - pipe number |
|
||||||
* @param Packet - packet bytes |
|
||||||
* @param Length - packet length |
|
||||||
* @return success (ACK'd) |
|
||||||
*/ |
|
||||||
bool NRF_SendPacket(uint8_t PipeNum, const uint8_t *Packet, uint8_t Length); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Receive a packet |
|
||||||
* |
|
||||||
* @param[out] Packet - the receiuved packet - buffer that is written |
|
||||||
* @param[out] PipeNum - pipe number that was received from |
|
||||||
* @return packet size, 0 on failure |
|
||||||
*/ |
|
||||||
uint8_t NRF_ReceivePacket(uint8_t *Packet, uint8_t *PipeNum); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Set base address |
|
||||||
* @param Bytes4 |
|
||||||
*/ |
|
||||||
void NRF_SetBaseAddress(const uint8_t *Bytes4); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if there's a packet to be read |
|
||||||
* |
|
||||||
* @return 1 if any to read |
|
||||||
*/ |
|
||||||
bool NRF_IsRxPacket(void); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Enable a pipe |
|
||||||
* |
|
||||||
* @param pipenum - pipe number 0-5 |
|
||||||
*/ |
|
||||||
void NRF_EnablePipe(uint8_t pipenum); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable a pipe |
|
||||||
* |
|
||||||
* @param pipenum - pipe number 0-5 |
|
||||||
*/ |
|
||||||
void NRF_DisablePipe(uint8_t pipenum); |
|
||||||
|
|
||||||
#endif // SUPPORT_NRF
|
|
||||||
|
|
||||||
#endif //GEX_NRF_NRF_H
|
|
@ -1,64 +0,0 @@ |
|||||||
//
|
|
||||||
// Created by MightyPork on 2018/07/07.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef GEX_CORE_PLAT_CONFIG_H |
|
||||||
#define GEX_CORE_PLAT_CONFIG_H |
|
||||||
|
|
||||||
#define VFS_DRIVE_NAME "GEX" |
|
||||||
|
|
||||||
// -------- Priorities -------------
|
|
||||||
#define TSK_MAIN_PRIO osPriorityNormal |
|
||||||
#define TSK_JOBS_PRIO osPriorityHigh |
|
||||||
#define TSK_TIMERS_PRIO 4 // this must be in the 0-7 range
|
|
||||||
|
|
||||||
// -------- Static buffers ---------
|
|
||||||
// USB / VFS task stack size
|
|
||||||
#if DISABLE_MSC |
|
||||||
#define TSK_STACK_MAIN 100 // without MSC the stack usage is significantly lower
|
|
||||||
#else |
|
||||||
#define TSK_STACK_MAIN 160 |
|
||||||
#endif |
|
||||||
|
|
||||||
// 180 is normally enough if not doing extensive debug logging
|
|
||||||
#define TSK_STACK_MSG 220 // TF message handler task stack size (all unit commands run on this thread)
|
|
||||||
#define TSK_STACK_IDLE 64 //configMINIMAL_STACK_SIZE
|
|
||||||
#define TSK_STACK_TIMERS 64 //configTIMER_TASK_STACK_DEPTH
|
|
||||||
|
|
||||||
#define PLAT_HEAP_SIZE 4096 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#define BULK_READ_BUF_LEN 256 // Buffer for TF bulk reads
|
|
||||||
#define UNIT_TMP_LEN 256 // Buffer for internal unit operations
|
|
||||||
|
|
||||||
#define FLASH_SAVE_BUF_LEN 128 // Malloc'd buffer for saving to flash
|
|
||||||
|
|
||||||
#define MSG_QUE_SLOT_SIZE 64 // FIXME this should be possible to lower, but there's some bug with bulk transfer / INI parser
|
|
||||||
#define RX_QUE_CAPACITY 16 // TinyFrame rx queue size (64 bytes each)
|
|
||||||
|
|
||||||
#define TF_MAX_PAYLOAD_RX 512 // TF max Rx payload
|
|
||||||
#define TF_SENDBUF_LEN 512 // TF transmit buffer (can be less than a full frame)
|
|
||||||
|
|
||||||
#define TF_MAX_ID_LST 4 // Frame ID listener count
|
|
||||||
#define TF_MAX_TYPE_LST 6 // Frame Type listener count
|
|
||||||
#define TF_MAX_GEN_LST 1 // Generic listener count
|
|
||||||
|
|
||||||
#define USBD_MAX_STR_DESC_SIZ 64 // Descriptor conversion buffer (used for converting ASCII to UTF-16, must be 2x the size of the longest descriptor)
|
|
||||||
#define MSC_MEDIA_PACKET 512 // Mass storage sector size (packet)
|
|
||||||
|
|
||||||
#define INI_KEY_MAX 20 // Ini parser key buffer
|
|
||||||
#define INI_VALUE_MAX 30 // Ini parser value buffer
|
|
||||||
|
|
||||||
// -------- Stack buffers ----------
|
|
||||||
#define DBG_BUF_LEN 100 // Size of the snprintf buffer for debug messages
|
|
||||||
#define ERR_MSG_STR_LEN 64 // Error message buffer size
|
|
||||||
#define IWBUFFER_LEN 80 // Ini writer buffer for sprintf
|
|
||||||
|
|
||||||
// -------- Timeouts ------------
|
|
||||||
#define TF_PARSER_TIMEOUT_TICKS 100 // Timeout for receiving & parsing a frame
|
|
||||||
#define BULK_LST_TIMEOUT_MS 2000 // timeout for the bulk transaction to expire
|
|
||||||
#define MSG_QUE_POST_TIMEOUT 200 // Time to post to the messages / jobs queue
|
|
||||||
|
|
||||||
|
|
||||||
#endif //GEX_CORE_PLAT_CONFIG_H
|
|
@ -1,57 +0,0 @@ |
|||||||
//
|
|
||||||
// Created by MightyPork on 2018/02/27.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "platform.h" |
|
||||||
#include "watchdog.h" |
|
||||||
|
|
||||||
static volatile uint16_t suspend_depth = 0; |
|
||||||
static volatile bool restart_pending = false; |
|
||||||
|
|
||||||
void wd_init(void) |
|
||||||
{ |
|
||||||
dbg("IWDG init, time 2s"); |
|
||||||
|
|
||||||
LL_IWDG_Enable(IWDG); |
|
||||||
LL_IWDG_EnableWriteAccess(IWDG); |
|
||||||
LL_IWDG_SetPrescaler(IWDG, LL_IWDG_PRESCALER_32); // 0.8 ms
|
|
||||||
LL_IWDG_SetReloadCounter(IWDG, 2500); // 2s. max 4095
|
|
||||||
while (!LL_IWDG_IsReady(IWDG)); |
|
||||||
|
|
||||||
// reload
|
|
||||||
LL_IWDG_ReloadCounter(IWDG); |
|
||||||
} |
|
||||||
|
|
||||||
void wd_suspend(void) |
|
||||||
{ |
|
||||||
vPortEnterCritical(); |
|
||||||
if (suspend_depth < 0xFFFF) { |
|
||||||
suspend_depth++; |
|
||||||
} |
|
||||||
vPortExitCritical(); |
|
||||||
} |
|
||||||
|
|
||||||
void wd_resume(void) |
|
||||||
{ |
|
||||||
vPortEnterCritical(); |
|
||||||
if (suspend_depth > 0) { |
|
||||||
suspend_depth--; |
|
||||||
|
|
||||||
if (suspend_depth == 0 && restart_pending) { |
|
||||||
restart_pending = false; |
|
||||||
LL_IWDG_ReloadCounter(IWDG); |
|
||||||
} |
|
||||||
} |
|
||||||
vPortExitCritical(); |
|
||||||
} |
|
||||||
|
|
||||||
void wd_restart(void) |
|
||||||
{ |
|
||||||
vPortEnterCritical(); |
|
||||||
if (suspend_depth == 0) { |
|
||||||
LL_IWDG_ReloadCounter(IWDG); |
|
||||||
} else { |
|
||||||
restart_pending = true; |
|
||||||
} |
|
||||||
vPortExitCritical(); |
|
||||||
} |
|
@ -1,32 +0,0 @@ |
|||||||
//
|
|
||||||
// Created by MightyPork on 2018/02/27.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef GEX_CORE_WATCHDOG_H |
|
||||||
#define GEX_CORE_WATCHDOG_H |
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the application watchdog |
|
||||||
*/ |
|
||||||
void wd_init(void); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Suspend watchdog restarts until resumed |
|
||||||
* (used in other tasks to prevent the main task clearing the wd if the other task is locked up) |
|
||||||
* |
|
||||||
* The suspend/resume calls can be stacked. |
|
||||||
*/ |
|
||||||
void wd_suspend(void); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Resume restarts |
|
||||||
*/ |
|
||||||
void wd_resume(void); |
|
||||||
|
|
||||||
/**
|
|
||||||
* Restart the wd. If restarts are suspended, postpone the restart until resumed |
|
||||||
* and then restart immediately. |
|
||||||
*/ |
|
||||||
void wd_restart(void); |
|
||||||
|
|
||||||
#endif //GEX_CORE_WATCHDOG_H
|
|
@ -1,41 +0,0 @@ |
|||||||
// |
|
||||||
// Definition of nRF24L01+ pin mappings for the platform |
|
||||||
// This file may be omitted if SUPPORT_NRF is not set in plat_compat.h |
|
||||||
// See rsc_enum.h for the R_ resource constants |
|
||||||
// |
|
||||||
|
|
||||||
#ifndef GEX_NRF_PINS_H |
|
||||||
#define GEX_NRF_PINS_H |
|
||||||
|
|
||||||
#include "platform.h" |
|
||||||
#include "rsc_enum.h" |
|
||||||
|
|
||||||
#define NRF_SPI SPI2 |
|
||||||
#define NRF_R_SPI R_SPI2 |
|
||||||
|
|
||||||
#define NRF_IRQ_Pin LL_GPIO_PIN_15 |
|
||||||
#define NRF_IRQ_GPIO_Port GPIOC |
|
||||||
#define NRF_R_IRQ R_PC15 |
|
||||||
#define NRF_EXTI_LINENUM 15 |
|
||||||
#define NRF_SYSCFG_EXTI_PORT LL_SYSCFG_EXTI_PORTC |
|
||||||
|
|
||||||
#define NRF_NSS_Pin LL_GPIO_PIN_13 |
|
||||||
#define NRF_NSS_GPIO_Port GPIOC |
|
||||||
#define NRF_R_NSS R_PC13 |
|
||||||
|
|
||||||
#define NRF_CE_Pin LL_GPIO_PIN_14 |
|
||||||
#define NRF_CE_GPIO_Port GPIOC |
|
||||||
#define NRF_R_CE R_PC14 |
|
||||||
|
|
||||||
#define NRF_RST_Pin LL_GPIO_PIN_12 |
|
||||||
#define NRF_RST_GPIO_Port GPIOC |
|
||||||
#define NRF_R_RST R_PC12 |
|
||||||
|
|
||||||
#define NRF_R_SCK R_PB13 |
|
||||||
#define NRF_SCK_AF LL_GPIO_AF_0 |
|
||||||
#define NRF_R_MISO R_PC2 |
|
||||||
#define NRF_MISO_AF LL_GPIO_AF_1 |
|
||||||
#define NRF_R_MOSI R_PC3 |
|
||||||
#define NRF_MOSI_AF LL_GPIO_AF_1 |
|
||||||
|
|
||||||
#endif //GEX_NRF_PINS_H |
|
@ -1,28 +0,0 @@ |
|||||||
#if defined(GEX_PLAT_F103_BLUEPILL) || defined(GEX_PLAT_F303_DISCOVERY) \ |
|
||||||
|| defined(GEX_PLAT_F407_DISCOVERY) |
|
||||||
// This is for F103+ |
|
||||||
|
|
||||||
/* The lowest interrupt priority that can be used in a call to a "set priority" |
|
||||||
function. */ |
|
||||||
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 |
|
||||||
|
|
||||||
#define configPRIO_BITS 4 |
|
||||||
|
|
||||||
/* The highest interrupt priority that can be used by any interrupt service |
|
||||||
routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL |
|
||||||
INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER |
|
||||||
PRIORITY THAN THIS! (higher priorities are lower numeric values. */ |
|
||||||
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 |
|
||||||
|
|
||||||
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1 |
|
||||||
#elif defined(STM32F072xB) |
|
||||||
// This is for F072 |
|
||||||
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 3 |
|
||||||
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 1 |
|
||||||
|
|
||||||
#define configPRIO_BITS 2 |
|
||||||
|
|
||||||
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0 |
|
||||||
#else |
|
||||||
#error "BAD PLATFORM!!" |
|
||||||
#endif |
|
@ -1,41 +0,0 @@ |
|||||||
|
|
||||||
#define DEBUG_USART_BAUD 115200 |
|
||||||
|
|
||||||
#if GEX_PLAT_F072 |
|
||||||
|
|
||||||
#define DEBUG_USART USART1 |
|
||||||
#define DEBUG_USART_RSC R_USART1 |
|
||||||
#define DEBUG_USART_PORT 'A' |
|
||||||
#define DEBUG_USART_PIN 9 |
|
||||||
#define DEBUG_USART_AF 1 |
|
||||||
#define DEBUG_USART_PCLK PLAT_APB1_HZ |
|
||||||
|
|
||||||
#elif GEX_PLAT_F103 |
|
||||||
|
|
||||||
#define DEBUG_USART USART2 |
|
||||||
#define DEBUG_USART_RSC R_USART2 |
|
||||||
#define DEBUG_USART_PORT 'A' |
|
||||||
#define DEBUG_USART_PIN 2 |
|
||||||
#define DEBUG_USART_PCLK PLAT_APB1_HZ |
|
||||||
|
|
||||||
#elif GEX_PLAT_F303 |
|
||||||
|
|
||||||
#define DEBUG_USART USART3 |
|
||||||
#define DEBUG_USART_RSC R_USART3 |
|
||||||
#define DEBUG_USART_PORT 'D' |
|
||||||
#define DEBUG_USART_PIN 8 |
|
||||||
#define DEBUG_USART_AF 7 |
|
||||||
#define DEBUG_USART_PCLK PLAT_APB1_HZ |
|
||||||
|
|
||||||
#elif GEX_PLAT_F407 |
|
||||||
|
|
||||||
#define DEBUG_USART USART2 |
|
||||||
#define DEBUG_USART_RSC R_USART2 |
|
||||||
#define DEBUG_USART_PORT 'A' |
|
||||||
#define DEBUG_USART_PIN 2 |
|
||||||
#define DEBUG_USART_AF 7 |
|
||||||
#define DEBUG_USART_PCLK PLAT_APB1_HZ |
|
||||||
|
|
||||||
#else |
|
||||||
#error "BAD PLATFORM!" |
|
||||||
#endif |
|
@ -1,194 +0,0 @@ |
|||||||
|
|
||||||
#if defined(GEX_PLAT_F103_BLUEPILL) |
|
||||||
// Platform STM32F103C8T6 Bluepill ($4 board from eBay) |
|
||||||
|
|
||||||
// Units supported by the platform (known to work correctly) |
|
||||||
|
|
||||||
// free all present resources |
|
||||||
{ |
|
||||||
rsc_free_range(NULL, R_ADC1, R_ADC2); |
|
||||||
rsc_free_range(NULL, R_I2C1, R_I2C2); |
|
||||||
rsc_free_range(NULL, R_SPI1, R_SPI2); |
|
||||||
rsc_free_range(NULL, R_TIM1, R_TIM4); |
|
||||||
rsc_free_range(NULL, R_USART1, R_USART3); |
|
||||||
rsc_free_range(NULL, R_PA0, R_PA15); |
|
||||||
rsc_free_range(NULL, R_PB0, R_PB15); |
|
||||||
rsc_free_range(NULL, R_PC13, R_PC15); |
|
||||||
rsc_free_range(NULL, R_PD0, R_PD1); |
|
||||||
} |
|
||||||
|
|
||||||
// Claim resources not available due to board layout or internal usage |
|
||||||
{ |
|
||||||
// HAL timebase |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_TIM1); |
|
||||||
// HSE crystal |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_PD0); |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_PD1); |
|
||||||
// SWD |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_PA13); |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_PA14); |
|
||||||
// USB |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_PA11); |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_PA12); |
|
||||||
// BOOT pin(s) |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_PB2); // BOOT1 |
|
||||||
} |
|
||||||
#elif defined(STM32F072xB) |
|
||||||
// Platform STM32F073RBT |
|
||||||
|
|
||||||
|
|
||||||
// Free all present resources |
|
||||||
{ |
|
||||||
rsc_free(NULL, R_ADC1); |
|
||||||
// rsc_free(NULL, R_CAN1); |
|
||||||
// rsc_free_range(NULL, R_COMP1, R_COMP2); |
|
||||||
rsc_free(NULL, R_DAC1); |
|
||||||
// rsc_free(NULL, R_HDMI_CEC); |
|
||||||
rsc_free(NULL, R_TSC); |
|
||||||
rsc_free_range(NULL, R_I2C1, R_I2C2); |
|
||||||
// rsc_free_range(NULL, R_I2S1, R_I2S2); |
|
||||||
rsc_free_range(NULL, R_SPI1, R_SPI2); |
|
||||||
rsc_free_range(NULL, R_TIM1, R_TIM3); |
|
||||||
rsc_free_range(NULL, R_TIM6, R_TIM7); |
|
||||||
rsc_free_range(NULL, R_TIM14, R_TIM17); |
|
||||||
rsc_free_range(NULL, R_USART1, R_USART4); |
|
||||||
rsc_free_range(NULL, R_DMA1_1, R_DMA1_7); |
|
||||||
|
|
||||||
rsc_free_range(NULL, R_PA0, R_PA15); |
|
||||||
rsc_free_range(NULL, R_PB0, R_PB15); |
|
||||||
rsc_free_range(NULL, R_PC0, R_PC15); |
|
||||||
rsc_free(NULL, R_PD2); |
|
||||||
rsc_free_range(NULL, R_PF0, R_PF1); |
|
||||||
} |
|
||||||
|
|
||||||
// Claim resources not available due to board layout or internal usage |
|
||||||
{ |
|
||||||
// HAL timebase |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_TIM17); |
|
||||||
// HSE crystal |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_PF0); |
|
||||||
|
|
||||||
#if PLAT_FULL_XTAL |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_PF1); // - not used in BYPASS mode |
|
||||||
#endif |
|
||||||
|
|
||||||
// SWD |
|
||||||
// rv |= rsc_claim(&UNIT_SYSTEM, R_PA13); |
|
||||||
// rv |= rsc_claim(&UNIT_SYSTEM, R_PA14); |
|
||||||
// USB |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_PA11); |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_PA12); |
|
||||||
|
|
||||||
#if defined(GEX_PLAT_F072_ZERO) |
|
||||||
// unconnected pins |
|
||||||
rv |= rsc_claim_range(&UNIT_PLATFORM, R_PC0, R_PC1); |
|
||||||
rv |= rsc_claim_range(&UNIT_PLATFORM, R_PC4, R_PC9); |
|
||||||
#endif |
|
||||||
} |
|
||||||
#elif defined(GEX_PLAT_F303_DISCOVERY) |
|
||||||
// Platform STM32F303VCT |
|
||||||
|
|
||||||
// Additional GPIO ports |
|
||||||
__HAL_RCC_GPIOF_CLK_ENABLE(); |
|
||||||
|
|
||||||
// Units supported by the platform (known to work correctly) |
|
||||||
// ureg_add_type(&UNIT_XYZ); |
|
||||||
|
|
||||||
// Free all present resources |
|
||||||
{ |
|
||||||
rsc_free_range(NULL, R_ADC1, R_ADC4); |
|
||||||
// rsc_free(NULL, R_CAN1); |
|
||||||
// rsc_free_range(NULL, R_COMP1, R_COMP7); |
|
||||||
// rsc_free(NULL, R_HDMI_CEC); |
|
||||||
rsc_free(NULL, R_DAC1); |
|
||||||
rsc_free_range(NULL, R_I2C1, R_I2C2); |
|
||||||
rsc_free_range(NULL, R_I2S2, R_I2S3); |
|
||||||
// rsc_free_range(NULL, R_OPAMP1, R_OPAMP4); |
|
||||||
rsc_free_range(NULL, R_SPI1, R_SPI3); |
|
||||||
rsc_free_range(NULL, R_TIM1, R_TIM4); |
|
||||||
rsc_free_range(NULL, R_TIM6, R_TIM8); |
|
||||||
rsc_free_range(NULL, R_TIM15, R_TIM17); |
|
||||||
rsc_free(NULL, R_TSC); |
|
||||||
rsc_free_range(NULL, R_USART1, R_USART5); |
|
||||||
|
|
||||||
rsc_free_range(NULL, R_PA0, R_PA15); |
|
||||||
rsc_free_range(NULL, R_PB0, R_PB15); |
|
||||||
rsc_free_range(NULL, R_PC0, R_PC15); |
|
||||||
rsc_free_range(NULL, R_PD0, R_PD15); |
|
||||||
rsc_free_range(NULL, R_PE0, R_PE15); |
|
||||||
|
|
||||||
rsc_free_range(NULL, R_PF0, R_PF2); |
|
||||||
rsc_free(NULL, R_PF4); |
|
||||||
rsc_free_range(NULL, R_PF9, R_PF10); |
|
||||||
} |
|
||||||
|
|
||||||
// Claim resources not available due to board layout or internal usage |
|
||||||
{ |
|
||||||
// HAL timebase |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_TIM1); |
|
||||||
// HSE crystal |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_PF0); |
|
||||||
//rv |= rsc_claim(&UNIT_SYSTEM, R_PF1); // - not used in BYPASS mode |
|
||||||
// SWD |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_PA13); |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_PA14); |
|
||||||
// USB |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_PA11); |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_PA12); |
|
||||||
// BOOT pin(s) |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_PB2); // BOOT1 |
|
||||||
} |
|
||||||
#elif defined(GEX_PLAT_F407_DISCOVERY) |
|
||||||
// Platform STM32F407VGT |
|
||||||
|
|
||||||
// Additional GPIO ports |
|
||||||
__HAL_RCC_GPIOF_CLK_ENABLE(); |
|
||||||
|
|
||||||
// Units supported by the platform (known to work correctly) |
|
||||||
// ureg_add_type(&UNIT_XYZ); |
|
||||||
|
|
||||||
// Free all present resources |
|
||||||
{ |
|
||||||
rsc_free_range(NULL, R_ADC1, R_ADC3); |
|
||||||
// rsc_free_range(NULL, R_CAN1, R_CAN2); |
|
||||||
// rsc_free_range(NULL, R_COMP1, R_COMP7); |
|
||||||
rsc_free(NULL, R_DAC1); |
|
||||||
// rsc_free(NULL, R_DCMI); |
|
||||||
// rsc_free(NULL, R_ETH); |
|
||||||
// rsc_free(NULL, R_FSMC); |
|
||||||
rsc_free_range(NULL, R_I2C1, R_I2C3); |
|
||||||
rsc_free_range(NULL, R_I2S2, R_I2S3); |
|
||||||
// rsc_free(NULL, R_SDIO); |
|
||||||
rsc_free_range(NULL, R_SPI1, R_SPI3); |
|
||||||
rsc_free_range(NULL, R_TIM1, R_TIM14); |
|
||||||
rsc_free_range(NULL, R_USART1, R_USART3); |
|
||||||
rsc_free(NULL, R_USART6); |
|
||||||
|
|
||||||
rsc_free_range(NULL, R_PA0, R_PA15); |
|
||||||
rsc_free_range(NULL, R_PB0, R_PB15); |
|
||||||
rsc_free_range(NULL, R_PC0, R_PC15); |
|
||||||
rsc_free_range(NULL, R_PD0, R_PD15); |
|
||||||
rsc_free_range(NULL, R_PE0, R_PE15); |
|
||||||
// also has 2 PH pins |
|
||||||
|
|
||||||
// F407 appears to have fewer GPIOs than F303? |
|
||||||
} |
|
||||||
|
|
||||||
// Claim resources not available due to board layout or internal usage |
|
||||||
{ |
|
||||||
// HAL timebase |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_TIM1); |
|
||||||
// HSE crystal |
|
||||||
// H0 and H1 |
|
||||||
// SWD |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_PA13); |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_PA14); |
|
||||||
// USB |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_PA11); |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_PA12); |
|
||||||
// BOOT pin(s) |
|
||||||
rv |= rsc_claim(&UNIT_SYSTEM, R_PB2); // BOOT1 |
|
||||||
} |
|
||||||
#else |
|
||||||
#error "BAD PLATFORM!" |
|
||||||
#endif |
|
@ -1,46 +0,0 @@ |
|||||||
#ifndef GEX_PLAT_COMPAT_H |
|
||||||
#define GEX_PLAT_COMPAT_H |
|
||||||
|
|
||||||
// string identifying the GEX board |
|
||||||
#define GEX_PLATFORM "Discovery-XYZ" |
|
||||||
#define GEX_PLAT_XYZ 1 // only with the MCU name |
|
||||||
|
|
||||||
// GEX_PLAT_XYZ_BOARD is defined in build.mk to identify particular board layout |
|
||||||
|
|
||||||
#define PLAT_AHB_MHZ 72 |
|
||||||
#define PLAT_APB1_MHZ 36 |
|
||||||
|
|
||||||
#include ... // all useful from the peripheral library (HAL / LL) |
|
||||||
|
|
||||||
// in bytes |
|
||||||
#define FLASH_SIZE (128*1024) |
|
||||||
// in bytes |
|
||||||
#define SETTINGS_BLOCK_SIZE (2*1024) |
|
||||||
// address where the settings block starts |
|
||||||
#define SETTINGS_FLASH_ADDR (0x08000000 + FLASH_SIZE - SETTINGS_BLOCK_SIZE) |
|
||||||
|
|
||||||
#define PORTS_COUNT 5 // number of available GPIO ports A,B,C,D,E,F... |
|
||||||
#define LOCK_JUMPER_PORT 'A' |
|
||||||
#define LOCK_JUMPER_PIN 2 |
|
||||||
|
|
||||||
#define STATUS_LED_PORT 'A' |
|
||||||
#define STATUS_LED_PIN 15 |
|
||||||
|
|
||||||
// Feature flags: |
|
||||||
#define PLAT_FLASHBANKS 0 // has the Banks field on the Flash config struct |
|
||||||
#define PLAT_NO_FLOATING_INPUTS 0 // can't have digital inputs with no pull resistor |
|
||||||
#define PLAT_USB_PHYCLOCK 0 // requires special config of phy clock for USB |
|
||||||
#define PLAT_USB_OTGFS 0 // uses the USB OTG IP, needs different config code |
|
||||||
#define PLAT_LOCK_BTN 1 // use a lock button instead of a lock jumper (push to toggle) |
|
||||||
#define PLAT_LOCK_1CLOSED 1 // lock jumper is active (closed / button pressed) in logical 1 |
|
||||||
#define PLAT_NO_AFNUM 0 // legacy platform without numbered AF alternatives |
|
||||||
#define PLAT_FULL_XTAL 1 // use two-wire xtal attachment |
|
||||||
#define PLAT_USB_PU_CTL 1 // platform has USB pullup control |
|
||||||
|
|
||||||
// FreeRTOS config |
|
||||||
#define PLAT_FREERTOS_LOWEST_INTERRUPT_PRIORITY 3 |
|
||||||
#define PLAT_FREERTOS_MAX_SYSCALL_INTERRUPT_PRIORITY 1 |
|
||||||
#define PLAT_FREERTOS_PRIO_BITS 2 |
|
||||||
#define PLAT_FREERTOS_USE_PORT_OPTIMISED_TASK_SELECTION 0 |
|
||||||
|
|
||||||
#endif //GEX_PLAT_COMPAT_H |
|
Binary file not shown.
@ -0,0 +1,129 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/03.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "platform.h" |
||||||
|
#include "unit_1wire.h" |
||||||
|
|
||||||
|
// 1WIRE master
|
||||||
|
#define OW_INTERNAL |
||||||
|
#include "_ow_internal.h" |
||||||
|
#include "_ow_commands.h" |
||||||
|
#include "_ow_low_level.h" |
||||||
|
|
||||||
|
/* Check presence of any devices on the bus */ |
||||||
|
error_t UU_1WIRE_CheckPresence(Unit *unit, bool *presence) |
||||||
|
{ |
||||||
|
CHECK_TYPE(unit, &UNIT_1WIRE); |
||||||
|
// reset
|
||||||
|
*presence = ow_reset(unit); |
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
/* Read address of a lone device on the bus */ |
||||||
|
error_t UU_1WIRE_ReadAddress(Unit *unit, uint64_t *address) |
||||||
|
{ |
||||||
|
CHECK_TYPE(unit, &UNIT_1WIRE); |
||||||
|
*address = 0; |
||||||
|
if (!ow_reset(unit)) return E_HW_TIMEOUT; |
||||||
|
|
||||||
|
// command
|
||||||
|
ow_write_u8(unit, OW_ROM_READ); |
||||||
|
|
||||||
|
// read the ROM code
|
||||||
|
*address = ow_read_u64(unit); |
||||||
|
|
||||||
|
const uint8_t *addr_as_bytes = (void*)address; |
||||||
|
if (0 != ow_checksum(addr_as_bytes, 8)) { |
||||||
|
*address = 0; |
||||||
|
return E_CHECKSUM_MISMATCH; // checksum mismatch
|
||||||
|
} |
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
/* Write bytes to a device */ |
||||||
|
error_t UU_1WIRE_Write(Unit *unit, uint64_t address, const uint8_t *buff, uint32_t len) |
||||||
|
{ |
||||||
|
CHECK_TYPE(unit, &UNIT_1WIRE); |
||||||
|
if (!ow_reset(unit)) return E_HW_TIMEOUT; |
||||||
|
|
||||||
|
// MATCH_ROM+addr, or SKIP_ROM
|
||||||
|
if (address != 0) { |
||||||
|
ow_write_u8(unit, OW_ROM_MATCH); |
||||||
|
ow_write_u64(unit, address); |
||||||
|
} else { |
||||||
|
ow_write_u8(unit, OW_ROM_SKIP); |
||||||
|
} |
||||||
|
|
||||||
|
// write the payload;
|
||||||
|
for (uint32_t i = 0; i < len; i++) { |
||||||
|
ow_write_u8(unit, *buff++); |
||||||
|
} |
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
/* Write a request to a device and read a response */ |
||||||
|
error_t UU_1WIRE_Read(Unit *unit, uint64_t address, |
||||||
|
const uint8_t *request_buff, uint32_t request_len, |
||||||
|
uint8_t *response_buff, uint32_t response_len, bool check_crc) |
||||||
|
{ |
||||||
|
CHECK_TYPE(unit, &UNIT_1WIRE); |
||||||
|
if (!ow_reset(unit)) return E_HW_TIMEOUT; |
||||||
|
|
||||||
|
uint8_t *rb = response_buff; |
||||||
|
|
||||||
|
// MATCH_ROM+addr, or SKIP_ROM
|
||||||
|
if (address != 0) { |
||||||
|
ow_write_u8(unit, OW_ROM_MATCH); |
||||||
|
ow_write_u64(unit, address); |
||||||
|
} else { |
||||||
|
ow_write_u8(unit, OW_ROM_SKIP); |
||||||
|
} |
||||||
|
|
||||||
|
// write the payload;
|
||||||
|
for (uint32_t i = 0; i < request_len; i++) { |
||||||
|
ow_write_u8(unit, *request_buff++); |
||||||
|
} |
||||||
|
|
||||||
|
// read the requested number of bytes
|
||||||
|
for (uint32_t i = 0; i < response_len; i++) { |
||||||
|
*rb++ = ow_read_u8(unit); |
||||||
|
} |
||||||
|
|
||||||
|
if (check_crc) { |
||||||
|
if (0 != ow_checksum(response_buff, response_len)) { |
||||||
|
return E_CHECKSUM_MISMATCH; |
||||||
|
} |
||||||
|
} |
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
/* Perform the search algorithm (start or continue) */ |
||||||
|
error_t UU_1WIRE_Search(Unit *unit, bool with_alarm, bool restart, |
||||||
|
uint64_t *buffer, uint32_t capacity, uint32_t *real_count, |
||||||
|
bool *have_more) |
||||||
|
{ |
||||||
|
CHECK_TYPE(unit, &UNIT_1WIRE); |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
if (restart) { |
||||||
|
uint8_t search_cmd = (uint8_t) (with_alarm ? OW_ROM_ALM_SEARCH : OW_ROM_SEARCH); |
||||||
|
ow_search_init(unit, search_cmd, true); |
||||||
|
} |
||||||
|
|
||||||
|
*real_count = ow_search_run(unit, (ow_romcode_t *) buffer, capacity); |
||||||
|
|
||||||
|
// resolve the code
|
||||||
|
switch (priv->searchState.status) { |
||||||
|
case OW_SEARCH_MORE: |
||||||
|
*have_more = priv->searchState.status == OW_SEARCH_MORE; |
||||||
|
|
||||||
|
case OW_SEARCH_DONE: |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
case OW_SEARCH_FAILED: |
||||||
|
return priv->searchState.error; |
||||||
|
} |
||||||
|
|
||||||
|
return E_INTERNAL_ERROR; |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/01/29.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef GEX_F072_OW_COMMANDS_H |
||||||
|
#define GEX_F072_OW_COMMANDS_H |
||||||
|
|
||||||
|
#ifndef OW_INTERNAL |
||||||
|
#error bad include! |
||||||
|
#endif |
||||||
|
|
||||||
|
#define OW_ROM_SEARCH 0xF0 |
||||||
|
#define OW_ROM_READ 0x33 |
||||||
|
#define OW_ROM_MATCH 0x55 |
||||||
|
#define OW_ROM_SKIP 0xCC |
||||||
|
#define OW_ROM_ALM_SEARCH 0xEC |
||||||
|
|
||||||
|
#endif //GEX_F072_OW_COMMANDS_H
|
@ -0,0 +1,71 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/03.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "platform.h" |
||||||
|
#include "unit_base.h" |
||||||
|
|
||||||
|
#define OW_INTERNAL |
||||||
|
#include "_ow_internal.h" |
||||||
|
|
||||||
|
/** Allocate data structure and set defaults */ |
||||||
|
error_t OW_preInit(Unit *unit) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv)); |
||||||
|
if (priv == NULL) return E_OUT_OF_MEM; |
||||||
|
|
||||||
|
// the timer is not started until needed
|
||||||
|
priv->busyWaitTimer = xTimerCreate("1w_tim", // name
|
||||||
|
750, // interval (will be changed when starting it)
|
||||||
|
true, // periodic (we use this only for the polling variant, the one-shot will stop the timer in the CB)
|
||||||
|
unit, // user data
|
||||||
|
OW_TimerCb); // callback
|
||||||
|
|
||||||
|
if (priv->busyWaitTimer == NULL) return E_OUT_OF_MEM; |
||||||
|
|
||||||
|
// some defaults
|
||||||
|
priv->pin_number = 0; |
||||||
|
priv->port_name = 'A'; |
||||||
|
priv->parasitic = false; |
||||||
|
|
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
/** Finalize unit set-up */ |
||||||
|
error_t OW_init(Unit *unit) |
||||||
|
{ |
||||||
|
bool suc = true; |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
// --- Parse config ---
|
||||||
|
priv->ll_pin = hw_pin2ll(priv->pin_number, &suc); |
||||||
|
priv->port = hw_port2periph(priv->port_name, &suc); |
||||||
|
Resource rsc = rsc_portpin2rsc(priv->port_name, priv->pin_number, &suc); |
||||||
|
if (!suc) return E_BAD_CONFIG; |
||||||
|
|
||||||
|
// --- Claim resources ---
|
||||||
|
TRY(rsc_claim(unit, rsc)); |
||||||
|
|
||||||
|
// --- Init hardware ---
|
||||||
|
LL_GPIO_SetPinMode(priv->port, priv->ll_pin, LL_GPIO_MODE_OUTPUT); |
||||||
|
LL_GPIO_SetPinOutputType(priv->port, priv->ll_pin, LL_GPIO_OUTPUT_PUSHPULL); |
||||||
|
LL_GPIO_SetPinSpeed(priv->port, priv->ll_pin, LL_GPIO_SPEED_FREQ_HIGH); |
||||||
|
LL_GPIO_SetPinPull(priv->port, priv->ll_pin, LL_GPIO_PULL_UP); // pull-up for OD state
|
||||||
|
|
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
/** Tear down the unit */ |
||||||
|
void OW_deInit(Unit *unit) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
// Release all resources
|
||||||
|
rsc_teardown(unit); |
||||||
|
|
||||||
|
// Delete the software timer
|
||||||
|
assert_param(pdPASS == xTimerDelete(priv->busyWaitTimer, 1000)); |
||||||
|
|
||||||
|
// Free memory
|
||||||
|
free_ck(unit->data); |
||||||
|
} |
@ -0,0 +1,60 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/01/29.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef GEX_F072_OW_INTERNAL_H |
||||||
|
#define GEX_F072_OW_INTERNAL_H |
||||||
|
|
||||||
|
#ifndef OW_INTERNAL |
||||||
|
#error bad include! |
||||||
|
#endif |
||||||
|
|
||||||
|
#include "_ow_search.h" |
||||||
|
|
||||||
|
/** Private data structure */ |
||||||
|
struct priv { |
||||||
|
char port_name; |
||||||
|
uint8_t pin_number; |
||||||
|
bool parasitic; |
||||||
|
|
||||||
|
GPIO_TypeDef *port; |
||||||
|
uint32_t ll_pin; |
||||||
|
|
||||||
|
TimerHandle_t busyWaitTimer; // timer used to wait for ds1820 measurement completion
|
||||||
|
bool busy; // flag used when the timer is running
|
||||||
|
uint32_t busyStart; |
||||||
|
TF_ID busyRequestId; |
||||||
|
struct ow_search_state searchState; |
||||||
|
}; |
||||||
|
|
||||||
|
/** Load from a binary buffer stored in Flash */ |
||||||
|
void OW_loadBinary(Unit *unit, PayloadParser *pp); |
||||||
|
|
||||||
|
/** Write to a binary buffer for storing in Flash */ |
||||||
|
void OW_writeBinary(Unit *unit, PayloadBuilder *pb); |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Parse a key-value pair from the INI file */ |
||||||
|
error_t OW_loadIni(Unit *unit, const char *key, const char *value); |
||||||
|
|
||||||
|
/** Generate INI file section for the unit */ |
||||||
|
void OW_writeIni(Unit *unit, IniWriter *iw); |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Allocate data structure and set defaults */ |
||||||
|
error_t OW_preInit(Unit *unit); |
||||||
|
|
||||||
|
/** Finalize unit set-up */ |
||||||
|
error_t OW_init(Unit *unit); |
||||||
|
|
||||||
|
/** Tear down the unit */ |
||||||
|
void OW_deInit(Unit *unit); |
||||||
|
|
||||||
|
/** Callback for the FreeRTOS timer used to wait for device ready */ |
||||||
|
void OW_TimerCb(TimerHandle_t xTimer); |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#endif //GEX_F072_OW_INTERNAL_H
|
@ -0,0 +1,223 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/01/29.
|
||||||
|
//
|
||||||
|
// 1-Wire unit low level functions
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "platform.h" |
||||||
|
|
||||||
|
#define OW_INTERNAL |
||||||
|
#include "_ow_internal.h" |
||||||
|
#include "_ow_low_level.h" |
||||||
|
|
||||||
|
static inline uint8_t crc8_bits(uint8_t data) |
||||||
|
{ |
||||||
|
uint8_t crc = 0; |
||||||
|
if(data & 1) crc ^= 0x5e; |
||||||
|
if(data & 2) crc ^= 0xbc; |
||||||
|
if(data & 4) crc ^= 0x61; |
||||||
|
if(data & 8) crc ^= 0xc2; |
||||||
|
if(data & 0x10) crc ^= 0x9d; |
||||||
|
if(data & 0x20) crc ^= 0x23; |
||||||
|
if(data & 0x40) crc ^= 0x46; |
||||||
|
if(data & 0x80) crc ^= 0x8c; |
||||||
|
return crc; |
||||||
|
} |
||||||
|
|
||||||
|
static uint8_t crc8_add(uint8_t cksum, uint8_t byte) |
||||||
|
{ |
||||||
|
return crc8_bits(byte ^ cksum); |
||||||
|
} |
||||||
|
|
||||||
|
uint8_t ow_checksum(const uint8_t *buff, uint32_t len) |
||||||
|
{ |
||||||
|
uint8_t cksum = 0; |
||||||
|
for(uint32_t i = 0; i < len; i++) { |
||||||
|
cksum = crc8_add(cksum, buff[i]); |
||||||
|
} |
||||||
|
return cksum; |
||||||
|
} |
||||||
|
|
||||||
|
// ----------------------------------------------
|
||||||
|
|
||||||
|
static inline void ow_pull_high(Unit *unit) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
LL_GPIO_SetOutputPin(priv->port, priv->ll_pin); |
||||||
|
LL_GPIO_SetPinMode(priv->port, priv->ll_pin, LL_GPIO_MODE_OUTPUT); |
||||||
|
} |
||||||
|
|
||||||
|
static inline void ow_pull_low(Unit *unit) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
LL_GPIO_ResetOutputPin(priv->port, priv->ll_pin); |
||||||
|
LL_GPIO_SetPinMode(priv->port, priv->ll_pin, LL_GPIO_MODE_OUTPUT); |
||||||
|
} |
||||||
|
|
||||||
|
static inline void ow_release_line(Unit *unit) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
LL_GPIO_SetPinMode(priv->port, priv->ll_pin, LL_GPIO_MODE_INPUT); |
||||||
|
} |
||||||
|
|
||||||
|
static inline bool ow_sample_line(Unit *unit) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
return (bool) LL_GPIO_IsInputPinSet(priv->port, priv->ll_pin); |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the 1-wire bus |
||||||
|
*/ |
||||||
|
bool ow_reset(Unit *unit) |
||||||
|
{ |
||||||
|
ow_pull_low(unit); |
||||||
|
PTIM_MicroDelay(500); |
||||||
|
|
||||||
|
bool presence; |
||||||
|
vPortEnterCritical(); |
||||||
|
{ |
||||||
|
// Strong pull-up (for parasitive power)
|
||||||
|
ow_pull_high(unit); |
||||||
|
PTIM_MicroDelay(2); |
||||||
|
|
||||||
|
// switch to open-drain
|
||||||
|
ow_release_line(unit); |
||||||
|
PTIM_MicroDelay(118); |
||||||
|
|
||||||
|
presence = !ow_sample_line(unit); |
||||||
|
} |
||||||
|
vPortExitCritical(); |
||||||
|
|
||||||
|
PTIM_MicroDelay(130); |
||||||
|
return presence; |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a bit to the 1-wire bus |
||||||
|
*/ |
||||||
|
void ow_write_bit(Unit *unit, bool bit) |
||||||
|
{ |
||||||
|
vPortEnterCritical(); |
||||||
|
{ |
||||||
|
// start mark
|
||||||
|
ow_pull_low(unit); |
||||||
|
PTIM_MicroDelay(2); |
||||||
|
|
||||||
|
if (bit) ow_pull_high(unit); |
||||||
|
PTIM_MicroDelay(70); |
||||||
|
|
||||||
|
// Strong pull-up (for parasitive power)
|
||||||
|
ow_pull_high(unit); |
||||||
|
} |
||||||
|
vPortExitCritical(); |
||||||
|
|
||||||
|
PTIM_MicroDelay(2); |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a bit from the 1-wire bus |
||||||
|
*/ |
||||||
|
bool ow_read_bit(Unit *unit) |
||||||
|
{ |
||||||
|
bool bit; |
||||||
|
|
||||||
|
vPortEnterCritical(); |
||||||
|
{ |
||||||
|
// start mark
|
||||||
|
ow_pull_low(unit); |
||||||
|
PTIM_MicroDelay(2); |
||||||
|
|
||||||
|
ow_release_line(unit); |
||||||
|
PTIM_MicroDelay(20); |
||||||
|
|
||||||
|
bit = ow_sample_line(unit); |
||||||
|
} |
||||||
|
vPortExitCritical(); |
||||||
|
|
||||||
|
PTIM_MicroDelay(40); |
||||||
|
|
||||||
|
return bit; |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a byte to the 1-wire bus |
||||||
|
*/ |
||||||
|
void ow_write_u8(Unit *unit, uint8_t byte) |
||||||
|
{ |
||||||
|
for (int i = 0; i < 8; i++) { |
||||||
|
ow_write_bit(unit, 0 != (byte & (1 << i))); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a halfword to the 1-wire bus |
||||||
|
*/ |
||||||
|
void ow_write_u16(Unit *unit, uint16_t halfword) |
||||||
|
{ |
||||||
|
ow_write_u8(unit, (uint8_t) (halfword & 0xFF)); |
||||||
|
ow_write_u8(unit, (uint8_t) ((halfword >> 8) & 0xFF)); |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a word to the 1-wire bus |
||||||
|
*/ |
||||||
|
void ow_write_u32(Unit *unit, uint32_t word) |
||||||
|
{ |
||||||
|
ow_write_u16(unit, (uint16_t) (word)); |
||||||
|
ow_write_u16(unit, (uint16_t) (word >> 16)); |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a doubleword to the 1-wire bus |
||||||
|
*/ |
||||||
|
void ow_write_u64(Unit *unit, uint64_t dword) |
||||||
|
{ |
||||||
|
ow_write_u32(unit, (uint32_t) (dword)); |
||||||
|
ow_write_u32(unit, (uint32_t) (dword >> 32)); |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a byte form the 1-wire bus |
||||||
|
*/ |
||||||
|
uint8_t ow_read_u8(Unit *unit) |
||||||
|
{ |
||||||
|
uint8_t buf = 0; |
||||||
|
for (int i = 0; i < 8; i++) { |
||||||
|
buf |= (1 & ow_read_bit(unit)) << i; |
||||||
|
} |
||||||
|
return buf; |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a halfword form the 1-wire bus |
||||||
|
*/ |
||||||
|
uint16_t ow_read_u16(Unit *unit) |
||||||
|
{ |
||||||
|
uint16_t acu = 0; |
||||||
|
acu |= ow_read_u8(unit); |
||||||
|
acu |= ow_read_u8(unit) << 8; |
||||||
|
return acu; |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a word form the 1-wire bus |
||||||
|
*/ |
||||||
|
uint32_t ow_read_u32(Unit *unit) |
||||||
|
{ |
||||||
|
uint32_t acu = 0; |
||||||
|
acu |= ow_read_u16(unit); |
||||||
|
acu |= (uint32_t)ow_read_u16(unit) << 16; |
||||||
|
return acu; |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a doubleword form the 1-wire bus |
||||||
|
*/ |
||||||
|
uint64_t ow_read_u64(Unit *unit) |
||||||
|
{ |
||||||
|
uint64_t acu = 0; |
||||||
|
acu |= ow_read_u32(unit); |
||||||
|
acu |= (uint64_t)ow_read_u32(unit) << 32; |
||||||
|
return acu; |
||||||
|
} |
@ -0,0 +1,84 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/01.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef GEX_F072_OW_LOW_LEVEL_H |
||||||
|
#define GEX_F072_OW_LOW_LEVEL_H |
||||||
|
|
||||||
|
#ifndef OW_INTERNAL |
||||||
|
#error bad include! |
||||||
|
#endif |
||||||
|
|
||||||
|
#include "platform.h" |
||||||
|
#include "unit_base.h" |
||||||
|
#include "_ow_low_level.h" |
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute a 1-wire type checksum. |
||||||
|
* If the buffer includes the checksum, the result should be 0. |
||||||
|
* |
||||||
|
* (this function may be used externally, or you can delete the implementation |
||||||
|
* from the c file if another implementation is already available) |
||||||
|
* |
||||||
|
* @param[in] buf - buffer of bytes to verify |
||||||
|
* @param[in] len - buffer length |
||||||
|
* @return checksum |
||||||
|
*/ |
||||||
|
uint8_t ow_checksum(const uint8_t *buf, uint32_t len); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the 1-wire bus |
||||||
|
*/ |
||||||
|
bool ow_reset(Unit *unit); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a bit to the 1-wire bus |
||||||
|
*/ |
||||||
|
void ow_write_bit(Unit *unit, bool bit); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a bit from the 1-wire bus |
||||||
|
*/ |
||||||
|
bool ow_read_bit(Unit *unit); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a byte to the 1-wire bus |
||||||
|
*/ |
||||||
|
void ow_write_u8(Unit *unit, uint8_t byte); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a halfword to the 1-wire bus |
||||||
|
*/ |
||||||
|
void ow_write_u16(Unit *unit, uint16_t halfword); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a word to the 1-wire bus |
||||||
|
*/ |
||||||
|
void ow_write_u32(Unit *unit, uint32_t word); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a doubleword to the 1-wire bus |
||||||
|
*/ |
||||||
|
void ow_write_u64(Unit *unit, uint64_t dword); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a byte form the 1-wire bus |
||||||
|
*/ |
||||||
|
uint8_t ow_read_u8(Unit *unit); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a halfword form the 1-wire bus |
||||||
|
*/ |
||||||
|
uint16_t ow_read_u16(Unit *unit); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a word form the 1-wire bus |
||||||
|
*/ |
||||||
|
uint32_t ow_read_u32(Unit *unit); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a doubleword form the 1-wire bus |
||||||
|
*/ |
||||||
|
uint64_t ow_read_u64(Unit *unit); |
||||||
|
|
||||||
|
#endif //GEX_F072_OW_LOW_LEVEL_H
|
@ -0,0 +1,129 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/01.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "platform.h" |
||||||
|
#include "unit_1wire.h" |
||||||
|
|
||||||
|
#define OW_INTERNAL |
||||||
|
#include "_ow_search.h" |
||||||
|
#include "_ow_internal.h" |
||||||
|
#include "_ow_low_level.h" |
||||||
|
#include "_ow_commands.h" |
||||||
|
|
||||||
|
void ow_search_init(Unit *unit, uint8_t command, bool test_checksums) |
||||||
|
{ |
||||||
|
if (unit->driver != &UNIT_1WIRE) |
||||||
|
trap("Wrong unit type - %s", unit->driver->name); |
||||||
|
|
||||||
|
assert_param(command == OW_ROM_SEARCH || command == OW_ROM_ALM_SEARCH); |
||||||
|
|
||||||
|
struct priv *priv = unit->data; |
||||||
|
struct ow_search_state *state = &priv->searchState; |
||||||
|
|
||||||
|
state->prev_last_fork = 64; |
||||||
|
memset(state->prev_code, 0, 8); |
||||||
|
state->status = OW_SEARCH_MORE; |
||||||
|
state->error = E_SUCCESS; |
||||||
|
state->command = command; |
||||||
|
state->first = true; |
||||||
|
state->test_checksums = test_checksums; |
||||||
|
} |
||||||
|
|
||||||
|
uint32_t ow_search_run(Unit *unit, ow_romcode_t *codes, uint32_t capacity) |
||||||
|
{ |
||||||
|
if (unit->driver != &UNIT_1WIRE) |
||||||
|
trap("Wrong unit type - %s", unit->driver->name); |
||||||
|
|
||||||
|
assert_param(codes); |
||||||
|
|
||||||
|
struct priv *priv = unit->data; |
||||||
|
struct ow_search_state *state = &priv->searchState; |
||||||
|
|
||||||
|
if (state->status != OW_SEARCH_MORE) return 0; |
||||||
|
|
||||||
|
uint32_t found_devices = 0; |
||||||
|
|
||||||
|
while (found_devices < capacity) { |
||||||
|
uint8_t index = 0; |
||||||
|
ow_romcode_t code = {}; |
||||||
|
int8_t last_fork = -1; |
||||||
|
|
||||||
|
// Start a new transaction. Devices respond to reset
|
||||||
|
if (!ow_reset(unit)) { |
||||||
|
state->status = OW_SEARCH_FAILED; |
||||||
|
state->error = E_HW_TIMEOUT; |
||||||
|
goto done; |
||||||
|
} |
||||||
|
// Send the search command (SEARCH_ROM, SEARCH_ALARM)
|
||||||
|
ow_write_u8(unit, state->command); |
||||||
|
|
||||||
|
uint8_t *code_byte = &code[0]; |
||||||
|
|
||||||
|
bool p, n; |
||||||
|
while (index != 64) { |
||||||
|
// Read a bit and its complement
|
||||||
|
p = ow_read_bit(unit); |
||||||
|
n = ow_read_bit(unit); |
||||||
|
|
||||||
|
if (!p && !n) { |
||||||
|
// A fork: there are devices on the bus with different bit value
|
||||||
|
// (the bus is open-drain, in both cases one device pulls it low)
|
||||||
|
if ((found_devices > 0 || !state->first) && index < state->prev_last_fork) { |
||||||
|
// earlier than the last fork, take the same turn as before
|
||||||
|
p = ow_code_getbit(state->prev_code, index); |
||||||
|
if (!p) last_fork = index; // remember for future runs, 1 not explored yet
|
||||||
|
} |
||||||
|
else if (index == state->prev_last_fork) { |
||||||
|
p = 1; // both forks are now exhausted
|
||||||
|
} |
||||||
|
else { // a new fork
|
||||||
|
last_fork = index; |
||||||
|
} |
||||||
|
} |
||||||
|
else if (p && n) { |
||||||
|
// No devices left connected - this doesn't normally happen
|
||||||
|
state->status = OW_SEARCH_FAILED; |
||||||
|
state->error = E_BUS_FAULT; |
||||||
|
goto done; |
||||||
|
} |
||||||
|
|
||||||
|
// All devices have a matching bit here, or it was resolved in a fork
|
||||||
|
if (p) *code_byte |= (1 << (index & 7)); |
||||||
|
ow_write_bit(unit, p); |
||||||
|
|
||||||
|
index++; |
||||||
|
if((index & 7) == 0) { |
||||||
|
code_byte++; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
memcpy(state->prev_code, code, 8); |
||||||
|
|
||||||
|
if (state->test_checksums) { |
||||||
|
if (0 != ow_checksum(code, 8)) { |
||||||
|
state->status = OW_SEARCH_FAILED; |
||||||
|
state->error = E_CHECKSUM_MISMATCH; |
||||||
|
goto done; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Record a found address
|
||||||
|
memcpy(codes[found_devices], code, 8); |
||||||
|
found_devices++; |
||||||
|
|
||||||
|
// Stop condition
|
||||||
|
if (last_fork == -1) { |
||||||
|
state->status = OW_SEARCH_DONE; |
||||||
|
goto done; |
||||||
|
} |
||||||
|
|
||||||
|
state->prev_last_fork = last_fork; |
||||||
|
} |
||||||
|
|
||||||
|
done: |
||||||
|
state->first = false; |
||||||
|
return found_devices; |
||||||
|
} |
||||||
|
|
||||||
|
|
@ -0,0 +1,83 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/01.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef GEX_F072_OW_SEARCH_H |
||||||
|
#define GEX_F072_OW_SEARCH_H |
||||||
|
|
||||||
|
#ifndef OW_INTERNAL |
||||||
|
#error bad include! |
||||||
|
#endif |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
#include <stdbool.h> |
||||||
|
#include "unit_base.h" |
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data type holding a romcode |
||||||
|
*/ |
||||||
|
typedef uint8_t ow_romcode_t[8]; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single bit from a romcode |
||||||
|
*/ |
||||||
|
#define ow_code_getbit(code, index) (bool)((code)[(index) >> 3] & (1 << ((index) & 7))) |
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert to unsigned 64-bit integer |
||||||
|
* (works only on little-endian systems - eg. OK on x86/x86_64, not on PowerPC) |
||||||
|
*/ |
||||||
|
#define ow_romcode_to_u64(code) (*((uint64_t *) (void *)(code))) |
||||||
|
|
||||||
|
/**
|
||||||
|
* States of the search algorithm |
||||||
|
*/ |
||||||
|
enum ow_search_result { |
||||||
|
OW_SEARCH_DONE = 0, |
||||||
|
OW_SEARCH_MORE = 1, |
||||||
|
OW_SEARCH_FAILED = 2, |
||||||
|
}; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal state of the search algorithm. |
||||||
|
* Check status to see if more remain to be read or an error occurred. |
||||||
|
*/ |
||||||
|
struct ow_search_state { |
||||||
|
int8_t prev_last_fork; |
||||||
|
ow_romcode_t prev_code; |
||||||
|
uint8_t command; |
||||||
|
enum ow_search_result status; |
||||||
|
bool first; |
||||||
|
bool test_checksums; |
||||||
|
error_t error; |
||||||
|
}; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Init the search algorithm state structure |
||||||
|
* |
||||||
|
* @param[out] state - inited struct |
||||||
|
* @param[in] command - command to send for requesting the search (e.g. SEARCH_ROM) |
||||||
|
* @param[in] test_checksums - verify checksums of all read romcodes |
||||||
|
*/ |
||||||
|
void ow_search_init(Unit *unit, uint8_t command, bool test_checksums); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a search of the 1-wire bus, with a state struct pre-inited |
||||||
|
* using ow_search_init(). |
||||||
|
* |
||||||
|
* Romcodes are stored in the provided array in a numerically ascending order. |
||||||
|
* |
||||||
|
* This function may be called repeatedly to retrieve more addresses than could fit |
||||||
|
* in the address buffer. |
||||||
|
* |
||||||
|
* @param[in,out] state - search state, used for multiple calls with limited buffer size |
||||||
|
* @param[out] codes - buffer for found romcodes |
||||||
|
* @param[in] capacity - buffer capacity |
||||||
|
* @return number of romcodes found. Search status is stored in `state->status`, |
||||||
|
* possible error code in `status->error` |
||||||
|
*/ |
||||||
|
uint32_t ow_search_run(Unit *unit, ow_romcode_t *codes, uint32_t capacity); |
||||||
|
|
||||||
|
#endif //GEX_F072_OW_SEARCH_H
|
@ -0,0 +1,68 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/03.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "platform.h" |
||||||
|
#include "unit_base.h" |
||||||
|
|
||||||
|
#define OW_INTERNAL |
||||||
|
#include "_ow_internal.h" |
||||||
|
|
||||||
|
/** Load from a binary buffer stored in Flash */ |
||||||
|
void OW_loadBinary(Unit *unit, PayloadParser *pp) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
uint8_t version = pp_u8(pp); |
||||||
|
(void)version; |
||||||
|
|
||||||
|
priv->port_name = pp_char(pp); |
||||||
|
priv->pin_number = pp_u8(pp); |
||||||
|
priv->parasitic = pp_bool(pp); |
||||||
|
} |
||||||
|
|
||||||
|
/** Write to a binary buffer for storing in Flash */ |
||||||
|
void OW_writeBinary(Unit *unit, PayloadBuilder *pb) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
pb_u8(pb, 0); // version
|
||||||
|
|
||||||
|
pb_char(pb, priv->port_name); |
||||||
|
pb_u8(pb, priv->pin_number); |
||||||
|
pb_bool(pb, priv->parasitic); |
||||||
|
} |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Parse a key-value pair from the INI file */ |
||||||
|
error_t OW_loadIni(Unit *unit, const char *key, const char *value) |
||||||
|
{ |
||||||
|
bool suc = true; |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
if (streq(key, "pin")) { |
||||||
|
suc = cfg_portpin_parse(value, &priv->port_name, &priv->pin_number); |
||||||
|
} |
||||||
|
else if (streq(key, "parasitic")) { |
||||||
|
priv->parasitic = cfg_bool_parse(value, &suc); |
||||||
|
} |
||||||
|
else { |
||||||
|
return E_BAD_KEY; |
||||||
|
} |
||||||
|
|
||||||
|
if (!suc) return E_BAD_VALUE; |
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
/** Generate INI file section for the unit */ |
||||||
|
void OW_writeIni(Unit *unit, IniWriter *iw) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
iw_comment(iw, "Data pin"); |
||||||
|
iw_entry(iw, "pin", "%c%d", priv->port_name, priv->pin_number); |
||||||
|
|
||||||
|
iw_comment(iw, "Parasitic (bus-powered) mode"); |
||||||
|
iw_entry(iw, "parasitic", str_yn(priv->parasitic)); |
||||||
|
} |
@ -0,0 +1,245 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/01/29.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "comm/messages.h" |
||||||
|
#include "unit_base.h" |
||||||
|
#include "unit_1wire.h" |
||||||
|
|
||||||
|
// 1WIRE master
|
||||||
|
#define OW_INTERNAL |
||||||
|
#include "_ow_internal.h" |
||||||
|
#include "_ow_low_level.h" |
||||||
|
|
||||||
|
/** Callback for sending a poll_ready success / failure report */ |
||||||
|
static void OW_TimerRespCb(Job *job) |
||||||
|
{ |
||||||
|
Unit *unit = job->unit; |
||||||
|
assert_param(unit); |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
bool success = (bool) job->data1; |
||||||
|
|
||||||
|
if (success) { |
||||||
|
com_respond_ok(priv->busyRequestId); |
||||||
|
} else { |
||||||
|
com_respond_error(priv->busyRequestId, E_HW_TIMEOUT); |
||||||
|
} |
||||||
|
priv->busy = false; |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* 1-Wire timer callback, used for the 'wait_ready' function. |
||||||
|
* |
||||||
|
* - In parasitic mode, this is a simple 750ms wait, after which a SUCCESS response is sent. |
||||||
|
* - In 3-wire mode, the callback is fired periodically and performs a Read operation on the bus. |
||||||
|
* The unit responds with 0 while the operation is ongoing. On receiving 1 a SUCCESS response is sent. |
||||||
|
* The polling is abandoned after a timeout, sending a TIMEOUT response. |
||||||
|
* |
||||||
|
* @param xTimer |
||||||
|
*/ |
||||||
|
void OW_TimerCb(TimerHandle_t xTimer) |
||||||
|
{ |
||||||
|
Unit *unit = pvTimerGetTimerID(xTimer); |
||||||
|
assert_param(unit); |
||||||
|
struct priv *priv = unit->data; |
||||||
|
assert_param(priv->busy); |
||||||
|
|
||||||
|
if (priv->parasitic) { |
||||||
|
// this is the end of the 750ms measurement time
|
||||||
|
goto halt_ok; |
||||||
|
} else { |
||||||
|
bool ready = ow_read_bit(unit); |
||||||
|
if (ready) { |
||||||
|
goto halt_ok; |
||||||
|
} |
||||||
|
|
||||||
|
uint32_t time = PTIM_GetTime(); |
||||||
|
if (time - priv->busyStart > 1000) { |
||||||
|
xTimerStop(xTimer, 100); |
||||||
|
|
||||||
|
Job j = { |
||||||
|
.unit = unit, |
||||||
|
.data1 = 0, // failure
|
||||||
|
.cb = OW_TimerRespCb, |
||||||
|
}; |
||||||
|
scheduleJob(&j); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return; |
||||||
|
halt_ok: |
||||||
|
xTimerStop(xTimer, 100); |
||||||
|
|
||||||
|
Job j = { |
||||||
|
.unit = unit, |
||||||
|
.data1 = 1, // success
|
||||||
|
.cb = OW_TimerRespCb, |
||||||
|
}; |
||||||
|
scheduleJob(&j); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
enum PinCmd_ { |
||||||
|
CMD_CHECK_PRESENCE = 0, // simply tests that any devices are attached
|
||||||
|
CMD_SEARCH_ADDR = 1, // perform a scan of the bus, retrieving all found device ROMs
|
||||||
|
CMD_SEARCH_ALARM = 2, // like normal scan, but retrieve only devices with alarm
|
||||||
|
CMD_SEARCH_CONTINUE = 3, // continue the previously started scan, retrieving more devices
|
||||||
|
CMD_READ_ADDR = 4, // read the ROM code from a single device (for single-device bus)
|
||||||
|
|
||||||
|
CMD_WRITE = 10, // write multiple bytes using the SKIP_ROM command
|
||||||
|
CMD_READ = 11, // write multiple bytes using a ROM address
|
||||||
|
|
||||||
|
CMD_POLL_FOR_1 = 20, |
||||||
|
|
||||||
|
CMD_TEST = 100, |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
/** Handle a request message */ |
||||||
|
static error_t OW_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
bool presence; |
||||||
|
uint64_t addr; |
||||||
|
uint32_t remain; |
||||||
|
const uint8_t *tail; |
||||||
|
|
||||||
|
if (priv->busy) return E_BUSY; |
||||||
|
|
||||||
|
bool with_alarm = false; |
||||||
|
bool search_reset = false; |
||||||
|
|
||||||
|
switch (command) { |
||||||
|
/**
|
||||||
|
* This is the delay function for DS1820 measurements. |
||||||
|
* |
||||||
|
* Parasitic: Returns success after the required 750ms |
||||||
|
* Non-parasitic: Returns SUCCESS after device responds '1', HW_TIMEOUT after 1s |
||||||
|
*/ |
||||||
|
case CMD_POLL_FOR_1: |
||||||
|
// This can't be exposed via the UU API, due to being async
|
||||||
|
if (priv->parasitic) { |
||||||
|
assert_param(pdPASS == xTimerChangePeriod(priv->busyWaitTimer, 750, 100)); |
||||||
|
} else { |
||||||
|
// every 10 ticks
|
||||||
|
assert_param(pdPASS == xTimerChangePeriod(priv->busyWaitTimer, 10, 100)); |
||||||
|
} |
||||||
|
assert_param(pdPASS == xTimerStart(priv->busyWaitTimer, 100)); |
||||||
|
priv->busy = true; |
||||||
|
priv->busyStart = PTIM_GetTime(); |
||||||
|
priv->busyRequestId = frame_id; |
||||||
|
return E_SUCCESS; // We will respond when the timer expires
|
||||||
|
|
||||||
|
/** Search devices with alarm. No payload, restarts the search. */ |
||||||
|
case CMD_SEARCH_ALARM: |
||||||
|
with_alarm = true; |
||||||
|
// fall-through
|
||||||
|
/** Search all devices. No payload, restarts the search. */ |
||||||
|
case CMD_SEARCH_ADDR: |
||||||
|
search_reset = true; |
||||||
|
// fall-through
|
||||||
|
/** Continue a previously begun search. */ |
||||||
|
case CMD_SEARCH_CONTINUE:; |
||||||
|
uint32_t found_count = 0; |
||||||
|
bool have_more = false; |
||||||
|
if (!search_reset && priv->searchState.status != OW_SEARCH_MORE) { |
||||||
|
dbg("Search not ongoing!"); |
||||||
|
return E_PROTOCOL_BREACH; |
||||||
|
} |
||||||
|
|
||||||
|
TRY(UU_1WIRE_Search(unit, with_alarm, search_reset, |
||||||
|
(void *) unit_tmp512, UNIT_TMP_LEN/8, &found_count, |
||||||
|
&have_more)); |
||||||
|
|
||||||
|
// use multipart to avoid allocating extra buffer
|
||||||
|
uint8_t status_code = (uint8_t) have_more; |
||||||
|
TF_Msg msg = { |
||||||
|
.frame_id = frame_id, |
||||||
|
.type = MSG_SUCCESS, |
||||||
|
.len = (TF_LEN) (found_count * 8 + 1), |
||||||
|
}; |
||||||
|
TF_Respond_Multipart(comm, &msg); |
||||||
|
TF_Multipart_Payload(comm, &status_code, 1); |
||||||
|
// the codes are back-to-back stored inside the buffer, we send it directly
|
||||||
|
// (it's already little-endian, as if built by PayloadBuilder)
|
||||||
|
TF_Multipart_Payload(comm, (uint8_t *) unit_tmp512, found_count * 8); |
||||||
|
TF_Multipart_Close(comm); |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
/** Simply check presence of any devices on the bus. Responds with SUCCESS or HW_TIMEOUT */ |
||||||
|
case CMD_CHECK_PRESENCE: |
||||||
|
TRY(UU_1WIRE_CheckPresence(unit, &presence)); |
||||||
|
|
||||||
|
com_respond_u8(frame_id, (uint8_t) presence); |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
/** Read address of the single device on the bus - returns u64 */ |
||||||
|
case CMD_READ_ADDR: |
||||||
|
TRY(UU_1WIRE_ReadAddress(unit, &addr)); |
||||||
|
|
||||||
|
// build response
|
||||||
|
PayloadBuilder pb = pb_start(unit_tmp512, UNIT_TMP_LEN, NULL); |
||||||
|
pb_u64(&pb, addr); |
||||||
|
com_respond_pb(frame_id, MSG_SUCCESS, &pb); |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Write payload to the bus, no confirmation (unless requested). |
||||||
|
* |
||||||
|
* Payload: |
||||||
|
* addr:u64, rest:write_data |
||||||
|
* if addr is 0, use SKIP_ROM |
||||||
|
*/ |
||||||
|
case CMD_WRITE: |
||||||
|
addr = pp_u64(pp); |
||||||
|
tail = pp_tail(pp, &remain); |
||||||
|
TRY(UU_1WIRE_Write(unit, addr, tail, remain)); |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Write and read. |
||||||
|
* |
||||||
|
* Payload: |
||||||
|
* addr:u64, read_len:u16, rest:write_data |
||||||
|
* if addr is 0, use SKIP_ROM |
||||||
|
*/ |
||||||
|
case CMD_READ: |
||||||
|
addr = pp_u64(pp); |
||||||
|
uint16_t rcount = pp_u16(pp); |
||||||
|
bool test_crc = pp_bool(pp); |
||||||
|
tail = pp_tail(pp, &remain); |
||||||
|
|
||||||
|
TRY(UU_1WIRE_Read(unit, addr, |
||||||
|
tail, remain, |
||||||
|
(uint8_t *) unit_tmp512, rcount, |
||||||
|
test_crc)); |
||||||
|
|
||||||
|
// build response
|
||||||
|
com_respond_buf(frame_id, MSG_SUCCESS, (uint8_t *) unit_tmp512, rcount); |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
default: |
||||||
|
return E_UNKNOWN_COMMAND; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Unit template */ |
||||||
|
const UnitDriver UNIT_1WIRE = { |
||||||
|
.name = "1WIRE", |
||||||
|
.description = "1-Wire master", |
||||||
|
// Settings
|
||||||
|
.preInit = OW_preInit, |
||||||
|
.cfgLoadBinary = OW_loadBinary, |
||||||
|
.cfgWriteBinary = OW_writeBinary, |
||||||
|
.cfgLoadIni = OW_loadIni, |
||||||
|
.cfgWriteIni = OW_writeIni, |
||||||
|
// Init
|
||||||
|
.init = OW_init, |
||||||
|
.deInit = OW_deInit, |
||||||
|
// Function
|
||||||
|
.handleRequest = OW_handleRequest, |
||||||
|
}; |
@ -0,0 +1,79 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/01/02.
|
||||||
|
//
|
||||||
|
// Dallas 1-Wire master unit
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef GEX_F072_UNIT_1WIRE_H |
||||||
|
#define GEX_F072_UNIT_1WIRE_H |
||||||
|
|
||||||
|
#include "unit.h" |
||||||
|
|
||||||
|
extern const UnitDriver UNIT_1WIRE; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there are any units present on the bus |
||||||
|
* |
||||||
|
* @param[in,out] unit |
||||||
|
* @param[out] presence - any devices present |
||||||
|
* @return success |
||||||
|
*/ |
||||||
|
error_t UU_1WIRE_CheckPresence(Unit *unit, bool *presence); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a device's address (use only with a single device attached) |
||||||
|
* |
||||||
|
* @param[in,out] unit |
||||||
|
* @param[out] address - the device's address, 0 on error or CRC mismatch |
||||||
|
* @return success |
||||||
|
*/ |
||||||
|
error_t UU_1WIRE_ReadAddress(Unit *unit, uint64_t *address); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Write bytes to a device / devices |
||||||
|
* |
||||||
|
* @param[in,out] unit |
||||||
|
* @param[in] address - device address, 0 to skip match (single device or broadcast) |
||||||
|
* @param[in] buff - bytes to write |
||||||
|
* @param[in] len - buffer length |
||||||
|
* @return success |
||||||
|
*/ |
||||||
|
error_t UU_1WIRE_Write(Unit *unit, uint64_t address, const uint8_t *buff, uint32_t len); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Read bytes from a device / devices, first writing a query |
||||||
|
* |
||||||
|
* @param[in,out] unit |
||||||
|
* @param[in] address - device address, 0 to skip match (single device ONLY!) |
||||||
|
* @param[in] request_buff - bytes to write before reading a response |
||||||
|
* @param[in] request_len - number of bytes to write |
||||||
|
* @param[out] response_buff - buffer for storing the read response |
||||||
|
* @param[in] response_len - number of bytes to read |
||||||
|
* @param[in] check_crc - verify CRC |
||||||
|
* @return success |
||||||
|
*/ |
||||||
|
error_t UU_1WIRE_Read(Unit *unit, uint64_t address, |
||||||
|
const uint8_t *request_buff, uint32_t request_len, |
||||||
|
uint8_t *response_buff, uint32_t response_len, bool check_crc); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a ROM search operation. |
||||||
|
* The algorithm is on a depth-first search without backtracking, |
||||||
|
* taking advantage of the open-drain topology. |
||||||
|
* |
||||||
|
* This function either starts the search, or continues it. |
||||||
|
* |
||||||
|
* @param[in,out] unit |
||||||
|
* @param[in] with_alarm - true to match only devices in alarm state |
||||||
|
* @param[in] restart - true to restart the search (search from the lowest address) |
||||||
|
* @param[out] buffer - buffer for storing found addresses |
||||||
|
* @param[in] capacity - buffer capacity in address entries (8 bytes) |
||||||
|
* @param[out] real_count - real number of found addresses (for which the CRC matched) |
||||||
|
* @param[out] have_more - flag indicating there are more devices to be found |
||||||
|
* @return success |
||||||
|
*/ |
||||||
|
error_t UU_1WIRE_Search(Unit *unit, bool with_alarm, bool restart, |
||||||
|
uint64_t *buffer, uint32_t capacity, uint32_t *real_count, |
||||||
|
bool *have_more); |
||||||
|
|
||||||
|
#endif //GEX_F072_UNIT_1WIRE_H
|
@ -0,0 +1,653 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/04.
|
||||||
|
//
|
||||||
|
// The core functionality of the ADC unit is defined here.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "platform.h" |
||||||
|
#include "unit_base.h" |
||||||
|
#include "unit_adc.h" |
||||||
|
|
||||||
|
#define ADC_INTERNAL |
||||||
|
#include "_adc_internal.h" |
||||||
|
|
||||||
|
#define DMA_POS(priv) ((priv)->buf_itemcount - (priv)->DMA_CHx->CNDTR) |
||||||
|
|
||||||
|
/**
|
||||||
|
* Async job to send a chunk of the DMA buffer to PC. |
||||||
|
* This can't be done directly because the interrupt couldn't wait for the TinyFrame mutex. |
||||||
|
* |
||||||
|
* unit - unit |
||||||
|
* data1 - start index |
||||||
|
* data2 - number of samples to send |
||||||
|
* data3 - bit flags: 0x80 if this is the last sample and we should close |
||||||
|
* 0x01 if this was the TC interrupt (otherwise it's HT) |
||||||
|
*/ |
||||||
|
static void UADC_JobSendBlockChunk(Job *job) |
||||||
|
{ |
||||||
|
Unit *unit = job->unit; |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
const uint32_t start = job->data1; |
||||||
|
const uint32_t count = job->data2; |
||||||
|
const bool close = (bool) (job->data3 & 0x80); |
||||||
|
const bool tc = (bool) (job->data3 & 0x01); |
||||||
|
|
||||||
|
const TF_TYPE type = close ? EVT_CAPT_DONE : EVT_CAPT_MORE; |
||||||
|
|
||||||
|
TF_Msg msg = { |
||||||
|
.frame_id = priv->stream_frame_id, |
||||||
|
.len = (TF_LEN) (1 /*seq*/ + count * sizeof(uint16_t)), |
||||||
|
.type = type, |
||||||
|
}; |
||||||
|
TF_Respond_Multipart(comm, &msg); |
||||||
|
TF_Multipart_Payload(comm, &priv->stream_serial, 1); |
||||||
|
TF_Multipart_Payload(comm, (uint8_t *) (priv->dma_buffer + start), count * sizeof(uint16_t)); |
||||||
|
TF_Multipart_Close(comm); |
||||||
|
|
||||||
|
// Clear the "busy" flags - those are checked in the DMA ISR to detect overrun
|
||||||
|
if (tc) priv->tc_pending = false; |
||||||
|
else priv->ht_pending = false; |
||||||
|
|
||||||
|
priv->stream_serial++; |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Async job to send the trigger header. |
||||||
|
* The header includes info about the trigger + the pre-trigger buffer. |
||||||
|
* |
||||||
|
* data1 - index in the DMA buffer at which the captured data willl start |
||||||
|
* data2 - edge type - 1 rise, 2 fall, 3 forced |
||||||
|
* timestamp - event stamp |
||||||
|
* unit - unit |
||||||
|
*/ |
||||||
|
static void UADC_JobSendTriggerCaptureHeader(Job *job) |
||||||
|
{ |
||||||
|
Unit *unit = job->unit; |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
EventReport er = { |
||||||
|
.unit = unit, |
||||||
|
.type = EVT_CAPT_START, |
||||||
|
.timestamp = job->timestamp, |
||||||
|
.length = (priv->pretrig_len + 1) * // see below why +1
|
||||||
|
priv->nb_channels * |
||||||
|
sizeof(uint16_t) + |
||||||
|
4 /*pretrig len*/ + |
||||||
|
1 /*edge*/ + |
||||||
|
1 /* seq */ |
||||||
|
}; |
||||||
|
|
||||||
|
uint32_t index_trigd = job->data1; |
||||||
|
uint8_t edge = (uint8_t) job->data2; |
||||||
|
|
||||||
|
EventReport_Start(&er); |
||||||
|
priv->stream_frame_id = er.sent_msg_id; |
||||||
|
{ |
||||||
|
// preamble
|
||||||
|
uint8_t buf[4]; |
||||||
|
PayloadBuilder pb = pb_start(buf, 4, NULL); |
||||||
|
pb_u32(&pb, priv->pretrig_len); |
||||||
|
pb_u8(&pb, edge); |
||||||
|
pb_u8(&pb, priv->stream_serial++); // This is the serial counter for the first chunk
|
||||||
|
// (containing the pre-trigger, or empty if no pretrig configured)
|
||||||
|
EventReport_PB(&pb); |
||||||
|
|
||||||
|
if (priv->pretrig_len > 0) { |
||||||
|
// pretrig
|
||||||
|
|
||||||
|
// +1 because we want pretrig 0 to exactly start with the triggering sample
|
||||||
|
uint32_t pretrig_remain = (priv->pretrig_len + 1) * priv->nb_channels; |
||||||
|
|
||||||
|
assert_param(index_trigd <= priv->buf_itemcount); |
||||||
|
|
||||||
|
// this is one past the last entry of the triggering capture group
|
||||||
|
if (pretrig_remain > index_trigd) { |
||||||
|
// used items in the wrap-around part of the buffer
|
||||||
|
uint32_t items_from_end = pretrig_remain - index_trigd; |
||||||
|
assert_param(priv->buf_itemcount - items_from_end >= index_trigd); |
||||||
|
|
||||||
|
EventReport_Data((uint8_t *) &priv->dma_buffer[priv->buf_itemcount - items_from_end], |
||||||
|
items_from_end * sizeof(uint16_t)); |
||||||
|
|
||||||
|
assert_param(items_from_end <= pretrig_remain); |
||||||
|
pretrig_remain -= items_from_end; |
||||||
|
} |
||||||
|
|
||||||
|
assert_param(pretrig_remain <= index_trigd); |
||||||
|
EventReport_Data((uint8_t *) &priv->dma_buffer[index_trigd - pretrig_remain], |
||||||
|
pretrig_remain * sizeof(uint16_t)); |
||||||
|
} |
||||||
|
} |
||||||
|
EventReport_End(); |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Async job to notify about end of stream |
||||||
|
*/ |
||||||
|
static void UADC_JobSendEndOfStreamMsg(Job *job) |
||||||
|
{ |
||||||
|
TF_Msg msg = { |
||||||
|
.type = EVT_CAPT_DONE, |
||||||
|
.frame_id = (TF_ID) job->data1 |
||||||
|
}; |
||||||
|
TF_Respond(comm, &msg); |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule sending a event report to the PC that the current stream has ended. |
||||||
|
* The client library should handle this appropriately. |
||||||
|
*/ |
||||||
|
void UADC_ReportEndOfStream(Unit *unit) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
Job j = { |
||||||
|
.unit = unit, |
||||||
|
.data1 = priv->stream_frame_id, // copy the ID, it may be invalid by the time the cb gets executed
|
||||||
|
.cb = UADC_JobSendEndOfStreamMsg |
||||||
|
}; |
||||||
|
scheduleJob(&j); |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a helper function for the ADC DMA interrupt for handing the different interrupt types (half / full transfer). |
||||||
|
* It sends the part of the buffer that was just captured via an async job, or aborts on overrun. |
||||||
|
* |
||||||
|
* It's split off here to allow calling it for the different flags without repeating code. |
||||||
|
* |
||||||
|
* @param unit |
||||||
|
* @param tc - true if this is the TC interrupt, else HT |
||||||
|
*/ |
||||||
|
static void handle_httc(Unit *unit, bool tc) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
uint32_t start = priv->stream_startpos; |
||||||
|
uint32_t end; |
||||||
|
const bool ht = !tc; |
||||||
|
|
||||||
|
const bool m_trigd = priv->opmode == ADC_OPMODE_TRIGD; |
||||||
|
const bool m_stream = priv->opmode == ADC_OPMODE_STREAM; |
||||||
|
const bool m_fixcpt = priv->opmode == ADC_OPMODE_BLCAP; |
||||||
|
|
||||||
|
if (ht) { |
||||||
|
end = (priv->buf_itemcount / 2); |
||||||
|
} |
||||||
|
else { |
||||||
|
end = priv->buf_itemcount; |
||||||
|
} |
||||||
|
|
||||||
|
if (start != end) { // this sometimes happened after a trigger, may be unnecessary now
|
||||||
|
if (end < start) { |
||||||
|
// this was a trap for a bug with missed TC irq, it's hopefully fixed now
|
||||||
|
trap("end < start! %d < %d, tc %d", (int)end, (int)start, (int)tc); |
||||||
|
} |
||||||
|
|
||||||
|
uint32_t sgcount = (end - start) / priv->nb_channels; |
||||||
|
|
||||||
|
if (m_trigd || m_fixcpt) { |
||||||
|
sgcount = MIN(priv->trig_stream_remain, sgcount); |
||||||
|
priv->trig_stream_remain -= sgcount; |
||||||
|
} |
||||||
|
|
||||||
|
// Check for the closing condition
|
||||||
|
const bool close = !m_stream && priv->trig_stream_remain == 0; |
||||||
|
|
||||||
|
if ((tc && priv->tc_pending) || (ht && priv->ht_pending)) { |
||||||
|
dbg("(!) ADC DMA not handled in time, abort capture"); |
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_EMERGENCY_SHUTDOWN); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Here we set the tc/ht pending flags for detecting overrun
|
||||||
|
|
||||||
|
Job j = { |
||||||
|
.unit = unit, |
||||||
|
.data1 = start, |
||||||
|
.data2 = sgcount * priv->nb_channels, |
||||||
|
.data3 = (uint32_t) (close*0x80) | (tc*1), // bitfields to indicate what's happening
|
||||||
|
.cb = UADC_JobSendBlockChunk |
||||||
|
}; |
||||||
|
|
||||||
|
if (tc) |
||||||
|
priv->tc_pending = true; |
||||||
|
else |
||||||
|
priv->ht_pending = true; |
||||||
|
|
||||||
|
if (!scheduleJob(&j)) { |
||||||
|
// Abort if we can't queue - the stream would tear and we'd hog the system with error messages
|
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_EMERGENCY_SHUTDOWN); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (close) { |
||||||
|
// If auto-arm is enabled, we need to re-arm again.
|
||||||
|
// However, EOS irq is disabled during the capture so the trigger edge detection would
|
||||||
|
// work on stale data from before this trigger. We have to wait for the next full
|
||||||
|
// conversion (EOS) before arming.
|
||||||
|
UADC_SwitchMode(unit, (priv->auto_rearm && m_trigd) ? ADC_OPMODE_REARM_PENDING : ADC_OPMODE_IDLE); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Advance the starting position
|
||||||
|
if (tc) { |
||||||
|
priv->stream_startpos = 0; |
||||||
|
} |
||||||
|
else { |
||||||
|
priv->stream_startpos = priv->buf_itemcount / 2; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* IRQ handler for the DMA flags. |
||||||
|
* |
||||||
|
* We handle flags: |
||||||
|
* TC - transfer complete |
||||||
|
* HT - half transfer |
||||||
|
* TE - transfer error (this should never happen unless there's a bug) |
||||||
|
* |
||||||
|
* The buffer works in a circular mode, so we always handle the previous half |
||||||
|
* or what of it should be sent (if capture started somewhere inside). |
||||||
|
* |
||||||
|
* @param arg - the unit, passed via the irq dispatcher |
||||||
|
*/ |
||||||
|
void UADC_DMA_Handler(void *arg) |
||||||
|
{ |
||||||
|
Unit *unit = arg; |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
// First thing, grab the flags. They may change during the function.
|
||||||
|
// Working on the live register might cause race conditions.
|
||||||
|
const uint32_t isrsnapshot = priv->DMAx->ISR; |
||||||
|
|
||||||
|
if (priv->opmode == ADC_OPMODE_UNINIT) { |
||||||
|
// the IRQ occured while switching mode, clear flags and do nothing else
|
||||||
|
LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum); |
||||||
|
LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum); |
||||||
|
LL_DMA_ClearFlag_TE(priv->DMAx, priv->dma_chnum); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (LL_DMA_IsActiveFlag_G(isrsnapshot, priv->dma_chnum)) { |
||||||
|
// we have some flags set - check which
|
||||||
|
const bool tc = LL_DMA_IsActiveFlag_TC(isrsnapshot, priv->dma_chnum); |
||||||
|
const bool ht = LL_DMA_IsActiveFlag_HT(isrsnapshot, priv->dma_chnum); |
||||||
|
const bool te = LL_DMA_IsActiveFlag_TE(isrsnapshot, priv->dma_chnum); |
||||||
|
|
||||||
|
if (ht) LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum); |
||||||
|
if (tc) LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum); |
||||||
|
|
||||||
|
if (te) { |
||||||
|
// this shouldn't happen - error
|
||||||
|
adc_dbg("ADC DMA TE!"); |
||||||
|
LL_DMA_ClearFlag_TE(priv->DMAx, priv->dma_chnum); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// check what mode we're in
|
||||||
|
const bool m_trigd = priv->opmode == ADC_OPMODE_TRIGD; |
||||||
|
const bool m_stream = priv->opmode == ADC_OPMODE_STREAM; |
||||||
|
const bool m_fixcpt = priv->opmode == ADC_OPMODE_BLCAP; |
||||||
|
if (m_trigd || m_stream || m_fixcpt) { |
||||||
|
const uint32_t half = (uint32_t) (priv->buf_itemcount / 2); |
||||||
|
if (ht && tc) { |
||||||
|
// dual event interrupt - may happen if we missed both and they were pending after
|
||||||
|
// interrupts became enabled again (this can happen due to the EOS or other higher prio irq's)
|
||||||
|
|
||||||
|
if (priv->stream_startpos > half) { |
||||||
|
handle_httc(unit, true); // TC
|
||||||
|
handle_httc(unit, false); // HT
|
||||||
|
} else { |
||||||
|
handle_httc(unit, false); // HT
|
||||||
|
handle_httc(unit, true); // TC
|
||||||
|
} |
||||||
|
} else { |
||||||
|
if (ht && priv->stream_startpos > half) { |
||||||
|
// We missed the TC interrupt while e.g. setting up the stream / interrupt. catch up!
|
||||||
|
// This fixes a bug with "negative size" for report.
|
||||||
|
handle_httc(unit, true); // TC
|
||||||
|
} |
||||||
|
|
||||||
|
handle_httc(unit, tc); |
||||||
|
} |
||||||
|
} else { |
||||||
|
// This shouldn't happen, the interrupt should be disabled in this opmode
|
||||||
|
dbg("(!) not streaming, ADC DMA IT should be disabled"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* End of measurement group interrupt handler. |
||||||
|
* This interrupt records the measured values and checks for trigger. |
||||||
|
* |
||||||
|
* @param arg - unit, passed b y irq dispatcher |
||||||
|
*/ |
||||||
|
void UADC_ADC_EOS_Handler(void *arg) |
||||||
|
{ |
||||||
|
Unit *unit = arg; |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
// Normally
|
||||||
|
uint64_t timestamp = 0; |
||||||
|
if (priv->opmode == ADC_OPMODE_ARMED) timestamp = PTIM_GetMicrotime(); |
||||||
|
|
||||||
|
LL_ADC_ClearFlag_EOS(priv->ADCx); |
||||||
|
if (priv->opmode == ADC_OPMODE_UNINIT) return; |
||||||
|
|
||||||
|
// Wait for the DMA to complete copying the last sample
|
||||||
|
uint32_t dmapos; |
||||||
|
hw_wait_while((dmapos = DMA_POS(priv)) % priv->nb_channels != 0, 100); // XXX this could be changed to reading it from the DR instead
|
||||||
|
|
||||||
|
uint32_t sample_pos; |
||||||
|
if (dmapos == 0) { |
||||||
|
sample_pos = (uint32_t) (priv->buf_itemcount); |
||||||
|
} else { |
||||||
|
sample_pos = dmapos; |
||||||
|
} |
||||||
|
sample_pos -= priv->nb_channels; |
||||||
|
|
||||||
|
int cnt = 0; // index of the sample within the group
|
||||||
|
|
||||||
|
const bool can_average = priv->real_frequency_int < UADC_MAX_FREQ_FOR_AVERAGING; |
||||||
|
const uint32_t channels_mask = priv->channels_mask; |
||||||
|
|
||||||
|
for (uint8_t i = 0; i < 18; i++) { |
||||||
|
if (channels_mask & (1 << i)) { |
||||||
|
const uint16_t val = priv->dma_buffer[sample_pos+cnt]; |
||||||
|
cnt++; |
||||||
|
|
||||||
|
if (can_average) { |
||||||
|
priv->averaging_bins[i] = |
||||||
|
priv->averaging_bins[i] * (1.0f - priv->avg_factor_as_float) + |
||||||
|
((float) val) * priv->avg_factor_as_float; |
||||||
|
} |
||||||
|
|
||||||
|
priv->last_samples[i] = val; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (priv->opmode == ADC_OPMODE_ARMED) { |
||||||
|
const uint16_t val = priv->last_samples[priv->trigger_source]; |
||||||
|
|
||||||
|
if ((priv->trig_prev_level < priv->trig_level) && val >= priv->trig_level && (bool) (priv->trig_edge & 0b01)) { |
||||||
|
// Rising edge
|
||||||
|
UADC_HandleTrigger(unit, 0b01, timestamp); |
||||||
|
} |
||||||
|
else if ((priv->trig_prev_level > priv->trig_level) && val <= priv->trig_level && (bool) (priv->trig_edge & 0b10)) { |
||||||
|
// Falling edge
|
||||||
|
UADC_HandleTrigger(unit, 0b10, timestamp); |
||||||
|
} |
||||||
|
priv->trig_prev_level = val; |
||||||
|
} |
||||||
|
|
||||||
|
// auto-rearm was waiting for the next sample
|
||||||
|
if (priv->opmode == ADC_OPMODE_REARM_PENDING) { |
||||||
|
if (!priv->auto_rearm) { |
||||||
|
// It looks like the flag was cleared by DISARM before we got a new sample.
|
||||||
|
// Let's just switch to IDLE
|
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_IDLE); |
||||||
|
} else { |
||||||
|
// Re-arming for a new trigger
|
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_ARMED); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a detected trigger - start capture if we're not in hold-off |
||||||
|
* |
||||||
|
* @param unit |
||||||
|
* @param edge_type - edge type, is included in the report |
||||||
|
* @param timestamp - event time |
||||||
|
*/ |
||||||
|
void UADC_HandleTrigger(Unit *unit, uint8_t edge_type, uint64_t timestamp) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
if (priv->opmode == ADC_OPMODE_UNINIT) return; |
||||||
|
|
||||||
|
if (priv->trig_holdoff != 0 && priv->trig_holdoff_remain > 0) { |
||||||
|
// Trig discarded due to holdoff
|
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (priv->trig_holdoff > 0) { |
||||||
|
priv->trig_holdoff_remain = priv->trig_holdoff; |
||||||
|
// Start the tick
|
||||||
|
unit->tick_interval = 1; |
||||||
|
unit->_tick_cnt = 1; |
||||||
|
} |
||||||
|
|
||||||
|
priv->stream_startpos = DMA_POS(priv); |
||||||
|
priv->trig_stream_remain = priv->trig_len; |
||||||
|
priv->stream_serial = 0; |
||||||
|
|
||||||
|
// This func may be called from the EOS interrupt, so it's safer to send the header message asynchronously
|
||||||
|
Job j = { |
||||||
|
.unit = unit, |
||||||
|
.timestamp = timestamp, |
||||||
|
.data1 = priv->stream_startpos, |
||||||
|
.data2 = edge_type, |
||||||
|
.cb = UADC_JobSendTriggerCaptureHeader |
||||||
|
}; |
||||||
|
scheduleJob(&j); |
||||||
|
|
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_TRIGD); |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Abort ongoing capture. |
||||||
|
*/ |
||||||
|
void UADC_AbortCapture(Unit *unit) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
const enum uadc_opmode old_opmode = priv->opmode; |
||||||
|
|
||||||
|
priv->auto_rearm = false; |
||||||
|
|
||||||
|
if (old_opmode == ADC_OPMODE_BLCAP || |
||||||
|
old_opmode == ADC_OPMODE_STREAM || |
||||||
|
old_opmode == ADC_OPMODE_TRIGD) { |
||||||
|
UADC_ReportEndOfStream(unit); |
||||||
|
} |
||||||
|
|
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_IDLE); |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a manual block capture. |
||||||
|
* |
||||||
|
* @param unit |
||||||
|
* @param len - number of samples (groups) |
||||||
|
* @param frame_id - TF session to re-use for the report (client has a listener set up) |
||||||
|
*/ |
||||||
|
void UADC_StartBlockCapture(Unit *unit, uint32_t len, TF_ID frame_id) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
if (priv->opmode == ADC_OPMODE_UNINIT) return; |
||||||
|
|
||||||
|
priv->stream_frame_id = frame_id; |
||||||
|
priv->stream_startpos = DMA_POS(priv); |
||||||
|
priv->trig_stream_remain = len; |
||||||
|
priv->stream_serial = 0; |
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_BLCAP); |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a stream |
||||||
|
* |
||||||
|
* @param frame_id - TF session to re-use for the frames (client has a listener set up) |
||||||
|
*/ |
||||||
|
void UADC_StartStream(Unit *unit, TF_ID frame_id) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
if (priv->opmode == ADC_OPMODE_UNINIT) return; |
||||||
|
|
||||||
|
priv->stream_frame_id = frame_id; |
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_STREAM); |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* End a stream by user request. |
||||||
|
*/ |
||||||
|
void UADC_StopStream(Unit *unit) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
if (priv->opmode == ADC_OPMODE_UNINIT) return; |
||||||
|
|
||||||
|
UADC_ReportEndOfStream(unit); |
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_IDLE); |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle unit update tick - expire the trigger hold-off. |
||||||
|
* We also check for the emergency shutdown condition and clear it. |
||||||
|
*/ |
||||||
|
void UADC_updateTick(Unit *unit) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
// Recover from shutdown after a delay
|
||||||
|
if (priv->opmode == ADC_OPMODE_EMERGENCY_SHUTDOWN) { |
||||||
|
adc_dbg("ADC recovering from emergency shutdown"); |
||||||
|
|
||||||
|
UADC_ReportEndOfStream(unit); |
||||||
|
LL_TIM_EnableCounter(priv->TIMx); |
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_IDLE); |
||||||
|
unit->tick_interval = 0; |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (priv->trig_holdoff_remain > 0) { |
||||||
|
priv->trig_holdoff_remain--; |
||||||
|
|
||||||
|
if (priv->trig_holdoff_remain == 0) { |
||||||
|
unit->tick_interval = 0; |
||||||
|
unit->_tick_cnt = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch the ADC operational mode. |
||||||
|
* |
||||||
|
* @param unit |
||||||
|
* @param new_mode - mode to set |
||||||
|
*/ |
||||||
|
void UADC_SwitchMode(Unit *unit, enum uadc_opmode new_mode) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
const enum uadc_opmode old_mode = priv->opmode; |
||||||
|
if (new_mode == old_mode) return; // nothing to do
|
||||||
|
|
||||||
|
// if un-itied, can go only to IDLE
|
||||||
|
assert_param((old_mode != ADC_OPMODE_UNINIT) || (new_mode == ADC_OPMODE_IDLE)); |
||||||
|
|
||||||
|
priv->opmode = ADC_OPMODE_UNINIT; |
||||||
|
|
||||||
|
if (new_mode == ADC_OPMODE_UNINIT) { |
||||||
|
adc_dbg("ADC switch -> UNINIT"); |
||||||
|
// Stop the DMA, timer and disable ADC - this is called before tearing down the unit
|
||||||
|
LL_TIM_DisableCounter(priv->TIMx); |
||||||
|
LL_ADC_ClearFlag_EOS(priv->ADCx); |
||||||
|
LL_ADC_DisableIT_EOS(priv->ADCx); |
||||||
|
|
||||||
|
// Switch off the ADC
|
||||||
|
if (LL_ADC_IsEnabled(priv->ADCx)) { |
||||||
|
// Cancel ongoing conversion
|
||||||
|
if (LL_ADC_REG_IsConversionOngoing(priv->ADCx)) { |
||||||
|
LL_ADC_REG_StopConversion(priv->ADCx); |
||||||
|
hw_wait_while(LL_ADC_REG_IsStopConversionOngoing(priv->ADCx), 100); |
||||||
|
} |
||||||
|
|
||||||
|
LL_ADC_Disable(priv->ADCx); |
||||||
|
hw_wait_while(LL_ADC_IsDisableOngoing(priv->ADCx), 100); |
||||||
|
} |
||||||
|
|
||||||
|
LL_DMA_DisableChannel(priv->DMAx, priv->dma_chnum); |
||||||
|
LL_DMA_DisableIT_HT(priv->DMAx, priv->dma_chnum); |
||||||
|
LL_DMA_DisableIT_TC(priv->DMAx, priv->dma_chnum); |
||||||
|
LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum); |
||||||
|
LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum); |
||||||
|
} |
||||||
|
else if (new_mode == ADC_OPMODE_IDLE || new_mode == ADC_OPMODE_REARM_PENDING) { |
||||||
|
// IDLE and ARMED are identical with the exception that the trigger condition is not checked
|
||||||
|
// ARMED can be only entered from IDLE, thus we do the init only here.
|
||||||
|
|
||||||
|
priv->tc_pending = false; |
||||||
|
priv->ht_pending = false; |
||||||
|
|
||||||
|
// In IDLE, we don't need the DMA interrupts
|
||||||
|
LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum); |
||||||
|
LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum); |
||||||
|
LL_DMA_DisableIT_HT(priv->DMAx, priv->dma_chnum); |
||||||
|
LL_DMA_DisableIT_TC(priv->DMAx, priv->dma_chnum); |
||||||
|
|
||||||
|
// Use End Of Sequence to recover results for averaging from the DMA buffer and DR
|
||||||
|
LL_ADC_ClearFlag_EOS(priv->ADCx); |
||||||
|
LL_ADC_EnableIT_EOS(priv->ADCx); |
||||||
|
|
||||||
|
if (old_mode == ADC_OPMODE_UNINIT) { |
||||||
|
// Nothing is started yet - this is the only way to leave UNINIT
|
||||||
|
LL_ADC_Enable(priv->ADCx); |
||||||
|
LL_DMA_EnableChannel(priv->DMAx, priv->dma_chnum); |
||||||
|
LL_TIM_EnableCounter(priv->TIMx); |
||||||
|
|
||||||
|
LL_ADC_REG_StartConversion(priv->ADCx); |
||||||
|
} |
||||||
|
} |
||||||
|
else if (new_mode == ADC_OPMODE_EMERGENCY_SHUTDOWN) { |
||||||
|
adc_dbg("ADC switch -> EMERGENCY_STOP"); |
||||||
|
// Emergency shutdown is used when the job queue overflows and the stream is torn
|
||||||
|
// This however doesn't help in the case when user sets such a high frequency
|
||||||
|
// that the whole app becomes unresponsive due to the completion ISR, need to verify the value manually.
|
||||||
|
|
||||||
|
LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum); |
||||||
|
LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum); |
||||||
|
LL_DMA_DisableIT_HT(priv->DMAx, priv->dma_chnum); |
||||||
|
LL_DMA_DisableIT_TC(priv->DMAx, priv->dma_chnum); |
||||||
|
|
||||||
|
LL_TIM_DisableCounter(priv->TIMx); |
||||||
|
UADC_SetSampleRate(unit, 10000); // fallback to a known safe value
|
||||||
|
|
||||||
|
LL_ADC_ClearFlag_EOS(priv->ADCx); |
||||||
|
LL_ADC_DisableIT_EOS(priv->ADCx); |
||||||
|
|
||||||
|
unit->tick_interval = 0; |
||||||
|
unit->_tick_cnt = 250; // 1-off
|
||||||
|
} |
||||||
|
else if (new_mode == ADC_OPMODE_ARMED) { |
||||||
|
adc_dbg("ADC switch -> ARMED"); |
||||||
|
assert_param(old_mode == ADC_OPMODE_IDLE || old_mode == ADC_OPMODE_REARM_PENDING); |
||||||
|
|
||||||
|
// avoid firing immediately by the value jumping across the scale
|
||||||
|
priv->trig_prev_level = priv->last_samples[priv->trigger_source]; |
||||||
|
} |
||||||
|
else if (new_mode == ADC_OPMODE_TRIGD || new_mode == ADC_OPMODE_STREAM || new_mode == ADC_OPMODE_BLCAP) { |
||||||
|
adc_dbg("ADC switch -> CAPTURE"); |
||||||
|
|
||||||
|
assert_param(old_mode == ADC_OPMODE_ARMED || old_mode == ADC_OPMODE_IDLE); |
||||||
|
|
||||||
|
// during the capture, we disallow direct readout and averaging to reduce overhead
|
||||||
|
LL_ADC_DisableIT_EOS(priv->ADCx); |
||||||
|
|
||||||
|
// Enable the DMA buffer interrupts
|
||||||
|
|
||||||
|
// we must first clear the flags, otherwise it will cause WEIRD bugs in the handler
|
||||||
|
LL_DMA_ClearFlag_HT(priv->DMAx, priv->dma_chnum); |
||||||
|
LL_DMA_ClearFlag_TC(priv->DMAx, priv->dma_chnum); |
||||||
|
|
||||||
|
// those must be as close as possible to the enabling
|
||||||
|
// if not trig'd, we don't care for lost samples before (this could cause a DMA irq miss / ht/tc mismatch with the startpos)
|
||||||
|
if (new_mode != ADC_OPMODE_TRIGD) { |
||||||
|
priv->stream_startpos = DMA_POS(priv); |
||||||
|
priv->stream_serial = 0; |
||||||
|
} |
||||||
|
LL_DMA_EnableIT_HT(priv->DMAx, priv->dma_chnum); |
||||||
|
LL_DMA_EnableIT_TC(priv->DMAx, priv->dma_chnum); |
||||||
|
} |
||||||
|
|
||||||
|
priv->opmode = new_mode; |
||||||
|
} |
@ -0,0 +1,264 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/03.
|
||||||
|
//
|
||||||
|
// ADC unit init and de-init functions
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "platform.h" |
||||||
|
#include "unit_base.h" |
||||||
|
|
||||||
|
#define ADC_INTERNAL |
||||||
|
#include "_adc_internal.h" |
||||||
|
|
||||||
|
/** Allocate data structure and set defaults */ |
||||||
|
error_t UADC_preInit(Unit *unit) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv)); |
||||||
|
if (priv == NULL) return E_OUT_OF_MEM; |
||||||
|
|
||||||
|
priv->cfg.channels = 1<<16; // Tsense by default - always available, easy testing
|
||||||
|
priv->cfg.sample_time = 0b010; // 13.5c - good enough and the default 0b00 value really is useless
|
||||||
|
priv->cfg.frequency = 1000; |
||||||
|
priv->cfg.buffer_size = 256; // in half-words
|
||||||
|
priv->cfg.averaging_factor = 500; // 0.5
|
||||||
|
|
||||||
|
priv->opmode = ADC_OPMODE_UNINIT; |
||||||
|
|
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** Configure frequency */ |
||||||
|
error_t UADC_SetSampleRate(Unit *unit, uint32_t hertz) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
uint16_t presc; |
||||||
|
uint32_t count; |
||||||
|
if (!hw_solve_timer(PLAT_APB1_HZ, hertz, true, &presc, &count, &priv->real_frequency)) { |
||||||
|
dbg("Failed to resolve timer params."); |
||||||
|
return E_BAD_VALUE; |
||||||
|
} |
||||||
|
adc_dbg("Frequency error %d ppm, presc %d, count %d", |
||||||
|
(int) lrintf(1000000.0f * ((priv->real_frequency - hertz) / (float) hertz)), |
||||||
|
(int) presc, (int) count); |
||||||
|
|
||||||
|
LL_TIM_SetPrescaler(priv->TIMx, (uint32_t) (presc - 1)); |
||||||
|
LL_TIM_SetAutoReload(priv->TIMx, count - 1); |
||||||
|
|
||||||
|
priv->real_frequency_int = hertz; |
||||||
|
|
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up the ADC DMA. |
||||||
|
* This is split to its own function because it's also called when the user adjusts the |
||||||
|
* enabled channels and we need to re-configure it. |
||||||
|
* |
||||||
|
* @param unit |
||||||
|
*/ |
||||||
|
void UADC_SetupDMA(Unit *unit) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
adc_dbg("Setting up DMA"); |
||||||
|
{ |
||||||
|
uint32_t itemcount = priv->nb_channels * (priv->cfg.buffer_size / (priv->nb_channels)); |
||||||
|
if (itemcount % 2 == 1) itemcount -= priv->nb_channels; // ensure the count is even
|
||||||
|
priv->buf_itemcount = itemcount; |
||||||
|
|
||||||
|
adc_dbg("DMA item count is %d (%d bytes), There are %d samples per group.", |
||||||
|
(int)priv->buf_itemcount, |
||||||
|
(int)(priv->buf_itemcount * sizeof(uint16_t)), |
||||||
|
(int)priv->nb_channels); |
||||||
|
|
||||||
|
{ |
||||||
|
LL_DMA_InitTypeDef init; |
||||||
|
LL_DMA_StructInit(&init); |
||||||
|
init.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY; |
||||||
|
|
||||||
|
init.Mode = LL_DMA_MODE_CIRCULAR; |
||||||
|
init.NbData = itemcount; |
||||||
|
|
||||||
|
init.PeriphOrM2MSrcAddress = (uint32_t) &priv->ADCx->DR; |
||||||
|
init.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_HALFWORD; |
||||||
|
init.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; |
||||||
|
|
||||||
|
init.MemoryOrM2MDstAddress = (uint32_t) priv->dma_buffer; |
||||||
|
init.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_HALFWORD; |
||||||
|
init.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; |
||||||
|
|
||||||
|
assert_param(SUCCESS == LL_DMA_Init(priv->DMAx, priv->dma_chnum, &init)); |
||||||
|
} |
||||||
|
|
||||||
|
// LL_DMA_EnableChannel(priv->DMAx, priv->dma_chnum); // this is done in the switch mode func now
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** Finalize unit set-up */ |
||||||
|
error_t UADC_init(Unit *unit) |
||||||
|
{ |
||||||
|
bool suc = true; |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
// Written for F072 which has only one ADC
|
||||||
|
|
||||||
|
TRY(rsc_claim(unit, R_ADC1)); |
||||||
|
TRY(rsc_claim(unit, R_DMA1_1)); |
||||||
|
TRY(rsc_claim(unit, R_TIM15)); |
||||||
|
|
||||||
|
priv->DMAx = DMA1; |
||||||
|
priv->DMA_CHx = DMA1_Channel1; |
||||||
|
priv->dma_chnum = 1; |
||||||
|
priv->ADCx = ADC1; |
||||||
|
priv->ADCx_Common = ADC1_COMMON; |
||||||
|
priv->TIMx = TIM15; |
||||||
|
|
||||||
|
// ----------------------- CONFIGURE PINS --------------------------
|
||||||
|
{ |
||||||
|
// Claim and configure all analog pins
|
||||||
|
priv->nb_channels = 0; |
||||||
|
for (uint8_t i = 0; i <= UADC_MAX_CHANNEL; i++) { |
||||||
|
if (priv->cfg.channels & (1 << i)) { |
||||||
|
priv->nb_channels++; |
||||||
|
|
||||||
|
char c; |
||||||
|
uint8_t num; |
||||||
|
if (i <= 7) { |
||||||
|
c = 'A'; |
||||||
|
num = i; |
||||||
|
} |
||||||
|
else if (i <= 9) { |
||||||
|
c = 'B'; |
||||||
|
num = (uint8_t) (i - 8); |
||||||
|
} |
||||||
|
else if (i <= 15) { |
||||||
|
c = 'C'; |
||||||
|
num = (uint8_t) (i - 10); |
||||||
|
} else { |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
TRY(rsc_claim_pin(unit, c, num)); |
||||||
|
uint32_t ll_pin = hw_pin2ll(num, &suc); |
||||||
|
GPIO_TypeDef *port = hw_port2periph(c, &suc); |
||||||
|
assert_param(suc); |
||||||
|
|
||||||
|
LL_GPIO_SetPinPull(port, ll_pin, LL_GPIO_PULL_NO); |
||||||
|
LL_GPIO_SetPinMode(port, ll_pin, LL_GPIO_MODE_ANALOG); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (priv->nb_channels == 0) { |
||||||
|
dbg("Need at least 1 channel"); |
||||||
|
return E_BAD_CONFIG; |
||||||
|
} |
||||||
|
|
||||||
|
// ensure some minimal space is available
|
||||||
|
if (priv->cfg.buffer_size < priv->nb_channels * 2) { |
||||||
|
dbg("Insufficient buf size"); |
||||||
|
return E_BAD_CONFIG; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ---------------- Alloc the buffer ----------------------
|
||||||
|
adc_dbg("Allocating buffer of size %d half-words", (int)priv->cfg.buffer_size); |
||||||
|
priv->dma_buffer = calloc_ck(priv->cfg.buffer_size, sizeof(uint16_t)); |
||||||
|
if (NULL == priv->dma_buffer) return E_OUT_OF_MEM; |
||||||
|
assert_param(((uint32_t) priv->dma_buffer & 3) == 0); // must be aligned
|
||||||
|
|
||||||
|
// ------------------- ENABLE CLOCKS --------------------------
|
||||||
|
{ |
||||||
|
// enable peripherals clock
|
||||||
|
hw_periph_clock_enable(priv->ADCx); |
||||||
|
hw_periph_clock_enable(priv->TIMx); |
||||||
|
// DMA and GPIO clocks are enabled on startup automatically
|
||||||
|
} |
||||||
|
|
||||||
|
// ------------------- CONFIGURE THE TIMER --------------------------
|
||||||
|
adc_dbg("Setting up TIMER"); |
||||||
|
{ |
||||||
|
TRY(UADC_SetSampleRate(unit, priv->cfg.frequency)); |
||||||
|
|
||||||
|
LL_TIM_EnableARRPreload(priv->TIMx); |
||||||
|
LL_TIM_EnableUpdateEvent(priv->TIMx); |
||||||
|
LL_TIM_SetTriggerOutput(priv->TIMx, LL_TIM_TRGO_UPDATE); |
||||||
|
LL_TIM_GenerateEvent_UPDATE(priv->TIMx); // load the prescaller value
|
||||||
|
} |
||||||
|
|
||||||
|
// --------------------- CONFIGURE THE ADC ---------------------------
|
||||||
|
adc_dbg("Setting up ADC"); |
||||||
|
{ |
||||||
|
// Calibrate the ADC
|
||||||
|
adc_dbg("Wait for calib"); |
||||||
|
LL_ADC_StartCalibration(priv->ADCx); |
||||||
|
while (LL_ADC_IsCalibrationOnGoing(priv->ADCx)) {} |
||||||
|
adc_dbg("ADC calibrated."); |
||||||
|
|
||||||
|
// Let's just enable the internal channels always - makes toggling them on-line easier
|
||||||
|
LL_ADC_SetCommonPathInternalCh(priv->ADCx_Common, LL_ADC_PATH_INTERNAL_VREFINT | LL_ADC_PATH_INTERNAL_TEMPSENSOR); |
||||||
|
|
||||||
|
LL_ADC_SetDataAlignment(priv->ADCx, LL_ADC_DATA_ALIGN_RIGHT); |
||||||
|
LL_ADC_SetResolution(priv->ADCx, LL_ADC_RESOLUTION_12B); |
||||||
|
LL_ADC_REG_SetDMATransfer(priv->ADCx, LL_ADC_REG_DMA_TRANSFER_UNLIMITED); |
||||||
|
|
||||||
|
// configure channels
|
||||||
|
priv->channels_mask = priv->cfg.channels; |
||||||
|
|
||||||
|
priv->ADCx->CHSELR = priv->channels_mask; |
||||||
|
|
||||||
|
LL_ADC_REG_SetTriggerSource(priv->ADCx, LL_ADC_REG_TRIG_EXT_TIM15_TRGO); |
||||||
|
|
||||||
|
LL_ADC_SetSamplingTimeCommonChannels(priv->ADCx, LL_ADC_SAMPLETIMES[priv->cfg.sample_time]); |
||||||
|
|
||||||
|
// will be enabled when switching to INIT mode
|
||||||
|
} |
||||||
|
|
||||||
|
// --------------------- CONFIGURE DMA -------------------------------
|
||||||
|
UADC_SetupDMA(unit); |
||||||
|
|
||||||
|
// prepare the avg factor float for the ISR
|
||||||
|
if (priv->cfg.averaging_factor > 1000) priv->cfg.averaging_factor = 1000; // normalize
|
||||||
|
priv->avg_factor_as_float = priv->cfg.averaging_factor/1000.0f; |
||||||
|
|
||||||
|
adc_dbg("ADC peripherals configured."); |
||||||
|
|
||||||
|
irqd_attach(priv->DMA_CHx, UADC_DMA_Handler, unit); |
||||||
|
irqd_attach(priv->ADCx, UADC_ADC_EOS_Handler, unit); |
||||||
|
adc_dbg("irqs attached"); |
||||||
|
|
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_IDLE); |
||||||
|
adc_dbg("ADC done"); |
||||||
|
|
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
/** Tear down the unit */ |
||||||
|
void UADC_deInit(Unit *unit) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
// de-init peripherals
|
||||||
|
if (unit->status == E_SUCCESS ) { |
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_UNINIT); |
||||||
|
|
||||||
|
//LL_ADC_DeInit(priv->ADCx);
|
||||||
|
LL_ADC_CommonDeInit(priv->ADCx_Common); |
||||||
|
LL_TIM_DeInit(priv->TIMx); |
||||||
|
|
||||||
|
irqd_detach(priv->DMA_CHx, UADC_DMA_Handler); |
||||||
|
irqd_detach(priv->ADCx, UADC_ADC_EOS_Handler); |
||||||
|
|
||||||
|
LL_DMA_DeInit(priv->DMAx, priv->dma_chnum); |
||||||
|
} |
||||||
|
|
||||||
|
// free buffer if not NULL
|
||||||
|
free_ck(priv->dma_buffer); |
||||||
|
|
||||||
|
// Release all resources, deinit pins
|
||||||
|
rsc_teardown(unit); |
||||||
|
|
||||||
|
// Free memory
|
||||||
|
free_ck(unit->data); |
||||||
|
} |
@ -0,0 +1,158 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/03.
|
||||||
|
//
|
||||||
|
// Defines and prototypes used internally by the ADC unit.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef GEX_F072_ADC_INTERNAL_H |
||||||
|
#define GEX_F072_ADC_INTERNAL_H |
||||||
|
|
||||||
|
#ifndef ADC_INTERNAL |
||||||
|
#error bad include! |
||||||
|
#endif |
||||||
|
|
||||||
|
#include "unit_base.h" |
||||||
|
|
||||||
|
//#define adc_dbg dbg
|
||||||
|
#define adc_dbg(...) do {} while(0) |
||||||
|
|
||||||
|
#define UADC_MAX_FREQ_FOR_AVERAGING 20000 |
||||||
|
|
||||||
|
#define UADC_MAX_CHANNEL 17 |
||||||
|
|
||||||
|
enum uadc_opmode { |
||||||
|
ADC_OPMODE_UNINIT, //!< Not yet switched to any mode
|
||||||
|
ADC_OPMODE_IDLE, //!< Idle. Allows immediate value readout and averaging.
|
||||||
|
ADC_OPMODE_REARM_PENDING, //!< Idle, waiting for the next sample to re-arm (auto trigger).
|
||||||
|
ADC_OPMODE_ARMED, //!< Armed for a trigger. Direct access and averaging are disabled.
|
||||||
|
ADC_OPMODE_TRIGD, //!< Triggered, sending pre-trigger and streaming captured data.
|
||||||
|
ADC_OPMODE_BLCAP, //!< Capture of fixed length without a trigger
|
||||||
|
ADC_OPMODE_STREAM, //!< Unlimited capture
|
||||||
|
ADC_OPMODE_EMERGENCY_SHUTDOWN, //!< Used when the buffers overrun to safely transition to IDLE after a delay
|
||||||
|
}; |
||||||
|
|
||||||
|
enum uadc_event { |
||||||
|
EVT_CAPT_START = 50, //!< Capture start (used in event in the first frame when trigger is detected)
|
||||||
|
EVT_CAPT_MORE = 51, //!< Capture data payload (used as TYPE for all capture types)
|
||||||
|
EVT_CAPT_DONE = 52, //!< End of trig'd or block capture payload (last frame with data),
|
||||||
|
//!< or a farewell message after closing stream using abort(), in this case without data.
|
||||||
|
}; |
||||||
|
|
||||||
|
/** Private data structure */ |
||||||
|
struct priv { |
||||||
|
struct { |
||||||
|
uint32_t channels; //!< bit flags (will be recorded in order 0-15)
|
||||||
|
uint8_t sample_time; //!< 0-7 (corresponds to 1.5-239.5 cycles) - time for the sampling capacitor to charge
|
||||||
|
uint32_t frequency; //!< Timer frequency in Hz. Note: not all frequencies can be achieved accurately
|
||||||
|
uint32_t buffer_size; //!< Buffer size in bytes (count 2 bytes per channel per measurement) - faster sampling freq needs bigger buffer
|
||||||
|
uint16_t averaging_factor; //!< Exponential averaging factor 0-1000
|
||||||
|
} cfg; |
||||||
|
|
||||||
|
// Peripherals
|
||||||
|
ADC_TypeDef *ADCx; //!< The ADC peripheral used
|
||||||
|
ADC_Common_TypeDef *ADCx_Common; //!< The ADC common control block
|
||||||
|
TIM_TypeDef *TIMx; //!< ADC timing timer instance
|
||||||
|
DMA_TypeDef *DMAx; //!< DMA isnatnce used
|
||||||
|
uint8_t dma_chnum; //!< DMA channel number
|
||||||
|
DMA_Channel_TypeDef *DMA_CHx; //!< DMA channel instance
|
||||||
|
|
||||||
|
// Live config
|
||||||
|
float real_frequency; |
||||||
|
uint32_t real_frequency_int; |
||||||
|
uint32_t channels_mask; //!< channels bitfield including tsense and vref
|
||||||
|
float avg_factor_as_float; |
||||||
|
|
||||||
|
uint16_t *dma_buffer; //!< malloc'd buffer for the samples
|
||||||
|
uint8_t nb_channels; //!< nbr of enabled adc channels
|
||||||
|
uint32_t buf_itemcount; //!< real size of the buffer in samples (adjusted to fit 2x whole multiple of sample group)
|
||||||
|
|
||||||
|
// Trigger state
|
||||||
|
uint32_t trig_stream_remain; //!< Counter of samples remaining to be sent in the post-trigger stream
|
||||||
|
uint16_t trig_holdoff_remain; //!< Tmp counter for the currently active hold-off
|
||||||
|
uint16_t trig_prev_level; //!< Value of the previous sample, used to detect trigger edge
|
||||||
|
uint32_t stream_startpos; //!< Byte offset in the DMA buffer where the next capture for a stream should start.
|
||||||
|
//!< Updated in TH/TC and on trigger (after the preceding data is sent as a pretrig buffer)
|
||||||
|
|
||||||
|
enum uadc_opmode opmode; //!< OpMode (state machine state)
|
||||||
|
float averaging_bins[18]; //!< Averaging buffers, enough space to accommodate all channels (16 external + 2 internal)
|
||||||
|
uint16_t last_samples[18]; //!< If averaging is disabled, the last captured sample is stored here.
|
||||||
|
|
||||||
|
// Trigger config
|
||||||
|
uint8_t trigger_source; //!< number of the pin selected as a trigger source
|
||||||
|
uint32_t pretrig_len; //!< Pre-trigger length, nbr of historical samples to report when trigger occurs
|
||||||
|
uint32_t trig_len; //!< Trigger length, nbr of samples to report AFTER a trigger occurs
|
||||||
|
uint16_t trig_level; //!< Triggering level in LSB
|
||||||
|
uint8_t trig_edge; //!< Which edge we want to trigger on. 1-rising, 2-falling, 3-both
|
||||||
|
bool auto_rearm; //!< Flag that the trigger should be re-armed after the stream finishes
|
||||||
|
uint16_t trig_holdoff; //!< Trigger hold-off time, set when configuring the trigger
|
||||||
|
TF_ID stream_frame_id; //!< Session ID for multi-part stream (response or report)
|
||||||
|
uint8_t stream_serial; //!< Serial nr of a stream frame
|
||||||
|
|
||||||
|
bool tc_pending; |
||||||
|
bool ht_pending; |
||||||
|
}; |
||||||
|
|
||||||
|
/** Allocate data structure and set defaults */ |
||||||
|
error_t UADC_preInit(Unit *unit); |
||||||
|
|
||||||
|
/** Load from a binary buffer stored in Flash */ |
||||||
|
void UADC_loadBinary(Unit *unit, PayloadParser *pp); |
||||||
|
|
||||||
|
/** Write to a binary buffer for storing in Flash */ |
||||||
|
void UADC_writeBinary(Unit *unit, PayloadBuilder *pb); |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Parse a key-value pair from the INI file */ |
||||||
|
error_t UADC_loadIni(Unit *unit, const char *key, const char *value); |
||||||
|
|
||||||
|
/** Generate INI file section for the unit */ |
||||||
|
void UADC_writeIni(Unit *unit, IniWriter *iw); |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Finalize unit set-up */ |
||||||
|
error_t UADC_init(Unit *unit); |
||||||
|
|
||||||
|
/** Tear down the unit */ |
||||||
|
void UADC_deInit(Unit *unit); |
||||||
|
|
||||||
|
/** Configure DMA (buffer count etc) */ |
||||||
|
void UADC_SetupDMA(Unit *unit); |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** DMA half/complete handler */ |
||||||
|
void UADC_DMA_Handler(void *arg); |
||||||
|
|
||||||
|
/** ADC eod of sequence handler */ |
||||||
|
void UADC_ADC_EOS_Handler(void *arg); |
||||||
|
|
||||||
|
/** Switch to a different opmode */ |
||||||
|
void UADC_SwitchMode(Unit *unit, enum uadc_opmode new_mode); |
||||||
|
|
||||||
|
/** Handle trigger - process pre-trigger and start streaming the requested number of samples */ |
||||||
|
void UADC_HandleTrigger(Unit *unit, uint8_t edge_type, uint64_t timestamp); |
||||||
|
|
||||||
|
/** Handle a periodic tick - expiring the hold-off */ |
||||||
|
void UADC_updateTick(Unit *unit); |
||||||
|
|
||||||
|
/** Send a end-of-stream message to PC's stream listener so it can shut down. */ |
||||||
|
void UADC_ReportEndOfStream(Unit *unit); |
||||||
|
|
||||||
|
/** Start a block capture */ |
||||||
|
void UADC_StartBlockCapture(Unit *unit, uint32_t len, TF_ID frame_id); |
||||||
|
|
||||||
|
/** Start stream */ |
||||||
|
void UADC_StartStream(Unit *unit, TF_ID frame_id); |
||||||
|
|
||||||
|
/** End stream */ |
||||||
|
void UADC_StopStream(Unit *unit); |
||||||
|
|
||||||
|
/** Configure frequency */ |
||||||
|
error_t UADC_SetSampleRate(Unit *unit, uint32_t hertz); |
||||||
|
|
||||||
|
/** Abort capture */ |
||||||
|
void UADC_AbortCapture(Unit *unit); |
||||||
|
|
||||||
|
#endif //GEX_F072_ADC_INTERNAL_H
|
@ -0,0 +1,106 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/03.
|
||||||
|
//
|
||||||
|
// ADC unit settings reading / parsing
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "platform.h" |
||||||
|
#include "unit_base.h" |
||||||
|
|
||||||
|
#define ADC_INTERNAL |
||||||
|
#include "_adc_internal.h" |
||||||
|
|
||||||
|
/** Load from a binary buffer stored in Flash */ |
||||||
|
void UADC_loadBinary(Unit *unit, PayloadParser *pp) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
uint8_t version = pp_u8(pp); |
||||||
|
(void)version; |
||||||
|
|
||||||
|
priv->cfg.channels = pp_u32(pp); |
||||||
|
priv->cfg.sample_time = pp_u8(pp); |
||||||
|
priv->cfg.frequency = pp_u32(pp); |
||||||
|
priv->cfg.buffer_size = pp_u32(pp); |
||||||
|
priv->cfg.averaging_factor = pp_u16(pp); |
||||||
|
} |
||||||
|
|
||||||
|
/** Write to a binary buffer for storing in Flash */ |
||||||
|
void UADC_writeBinary(Unit *unit, PayloadBuilder *pb) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
pb_u8(pb, 0); // version
|
||||||
|
|
||||||
|
pb_u32(pb, priv->cfg.channels); |
||||||
|
pb_u8(pb, priv->cfg.sample_time); |
||||||
|
pb_u32(pb, priv->cfg.frequency); |
||||||
|
pb_u32(pb, priv->cfg.buffer_size); |
||||||
|
pb_u16(pb, priv->cfg.averaging_factor); |
||||||
|
} |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Parse a key-value pair from the INI file */ |
||||||
|
error_t UADC_loadIni(Unit *unit, const char *key, const char *value) |
||||||
|
{ |
||||||
|
bool suc = true; |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
if (streq(key, "channels")) { |
||||||
|
priv->cfg.channels = cfg_pinmask_parse_32(value, &suc); |
||||||
|
} |
||||||
|
else if (streq(key, "sample_time")) { |
||||||
|
priv->cfg.sample_time = cfg_u8_parse(value, &suc); |
||||||
|
if (priv->cfg.sample_time > 7) return E_BAD_VALUE; |
||||||
|
} |
||||||
|
else if (streq(key, "frequency")) { |
||||||
|
priv->cfg.frequency = cfg_u32_parse(value, &suc); |
||||||
|
} |
||||||
|
else if (streq(key, "buffer_size")) { |
||||||
|
priv->cfg.buffer_size = cfg_u32_parse(value, &suc); |
||||||
|
} |
||||||
|
else if (streq(key, "avg_factor")) { |
||||||
|
priv->cfg.averaging_factor = cfg_u16_parse(value, &suc); |
||||||
|
if (priv->cfg.averaging_factor > 1000) return E_BAD_VALUE; |
||||||
|
} |
||||||
|
else { |
||||||
|
return E_BAD_KEY; |
||||||
|
} |
||||||
|
|
||||||
|
if (!suc) return E_BAD_VALUE; |
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
/** Generate INI file section for the unit */ |
||||||
|
void UADC_writeIni(Unit *unit, IniWriter *iw) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
iw_comment(iw, "Enabled channels, comma separated"); |
||||||
|
iw_comment(iw, " 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17"); |
||||||
|
iw_comment(iw, "A0 A1 A2 A3 A4 A5 A6 A7 B0 B1 C0 C1 C2 C3 C4 C5 Tsens Vref"); |
||||||
|
iw_entry(iw, "channels", cfg_pinmask_encode(priv->cfg.channels, unit_tmp512, true)); |
||||||
|
|
||||||
|
iw_cmt_newline(iw); |
||||||
|
iw_comment(iw, "Sampling time (0-7)"); |
||||||
|
iw_entry(iw, "sample_time", "%d", (int)priv->cfg.sample_time); |
||||||
|
|
||||||
|
iw_comment(iw, "Sampling frequency (Hz)"); |
||||||
|
iw_entry(iw, "frequency", "%d", (int)priv->cfg.frequency); |
||||||
|
|
||||||
|
iw_cmt_newline(iw); |
||||||
|
iw_comment(iw, "Sample buffer size"); |
||||||
|
iw_comment(iw, "- shared by all enabled channels"); |
||||||
|
iw_comment(iw, "- defines the maximum pre-trigger size (divide by # of channels)"); |
||||||
|
iw_comment(iw, "- captured data is sent in half-buffer chunks"); |
||||||
|
iw_comment(iw, "- buffer overrun aborts the data capture"); |
||||||
|
iw_entry(iw, "buffer_size", "%d", (int)priv->cfg.buffer_size); |
||||||
|
|
||||||
|
iw_cmt_newline(iw); |
||||||
|
iw_comment(iw, "Exponential averaging coefficient (permil, range 0-1000 ~ 0.000-1.000)"); |
||||||
|
iw_comment(iw, "- used formula: y[t]=(1-k)*y[t-1]+k*u[t]"); |
||||||
|
iw_comment(iw, "- not available when a capture is running"); |
||||||
|
iw_entry(iw, "avg_factor", "%d", priv->cfg.averaging_factor); |
||||||
|
} |
||||||
|
|
@ -0,0 +1,405 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2017/11/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "unit_base.h" |
||||||
|
#include "unit_adc.h" |
||||||
|
|
||||||
|
#define ADC_INTERNAL |
||||||
|
#include "_adc_internal.h" |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
enum AdcCmd_ { |
||||||
|
CMD_READ_RAW = 0, |
||||||
|
CMD_READ_SMOOTHED = 1, |
||||||
|
|
||||||
|
CMD_GET_ENABLED_CHANNELS = 10, |
||||||
|
CMD_GET_SAMPLE_RATE = 11, |
||||||
|
|
||||||
|
CMD_SETUP_TRIGGER = 20, |
||||||
|
CMD_ARM = 21, |
||||||
|
CMD_DISARM = 22, |
||||||
|
CMD_ABORT = 23, // abort any ongoing capture or stream
|
||||||
|
CMD_FORCE_TRIGGER = 24, |
||||||
|
CMD_BLOCK_CAPTURE = 25, |
||||||
|
CMD_STREAM_START = 26, |
||||||
|
CMD_STREAM_STOP = 27, |
||||||
|
CMD_SET_SMOOTHING_FACTOR = 28, |
||||||
|
CMD_SET_SAMPLE_RATE = 29, |
||||||
|
CMD_ENABLE_CHANNELS = 30, |
||||||
|
CMD_SET_SAMPLE_TIME = 31, |
||||||
|
}; |
||||||
|
|
||||||
|
/** Handle a request message */ |
||||||
|
static error_t UADC_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
PayloadBuilder pb = pb_start(unit_tmp512, UNIT_TMP_LEN, NULL); |
||||||
|
|
||||||
|
switch (command) { |
||||||
|
/**
|
||||||
|
* Get enabled channels. |
||||||
|
* Response: bytes with indices of enabled channels, ascending order. |
||||||
|
*/ |
||||||
|
case CMD_GET_ENABLED_CHANNELS: |
||||||
|
for (uint8_t i = 0; i < 18; i++) { |
||||||
|
if (priv->channels_mask & (1 << i)) { |
||||||
|
pb_u8(&pb, i); |
||||||
|
} |
||||||
|
} |
||||||
|
com_respond_pb(frame_id, MSG_SUCCESS, &pb); |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the sample rate in Hz |
||||||
|
* plad: hz:u32 |
||||||
|
*/ |
||||||
|
case CMD_SET_SAMPLE_RATE: |
||||||
|
{ |
||||||
|
uint32_t freq = pp_u32(pp); |
||||||
|
if (freq == 0) return E_BAD_VALUE; |
||||||
|
|
||||||
|
TRY(UADC_SetSampleRate(unit, freq)); |
||||||
|
} |
||||||
|
// Pass through - send back the obtained sample rate
|
||||||
|
/**
|
||||||
|
* Read the real used frequency, expressed as float. |
||||||
|
* May differ from the configured or requested value due to prescaller limitations. |
||||||
|
*/ |
||||||
|
case CMD_GET_SAMPLE_RATE: |
||||||
|
pb_u32(&pb, priv->real_frequency_int); |
||||||
|
pb_float(&pb, priv->real_frequency); |
||||||
|
com_respond_pb(frame_id, MSG_SUCCESS, &pb); |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Set smoothing factor 0-1000. |
||||||
|
* pld: u16:factor |
||||||
|
*/ |
||||||
|
case CMD_SET_SMOOTHING_FACTOR: |
||||||
|
{ |
||||||
|
uint16_t fac = pp_u16(pp); |
||||||
|
if (fac > 1000) return E_BAD_VALUE; |
||||||
|
priv->avg_factor_as_float = fac / 1000.0f; |
||||||
|
} |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Set sample time |
||||||
|
* pld: u8:0-7 |
||||||
|
*/ |
||||||
|
case CMD_SET_SAMPLE_TIME: |
||||||
|
{ |
||||||
|
uint8_t tim = pp_u8(pp); |
||||||
|
if (tim > 7) return E_BAD_VALUE; |
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_UNINIT); |
||||||
|
{ |
||||||
|
LL_ADC_SetSamplingTimeCommonChannels(priv->ADCx, LL_ADC_SAMPLETIMES[tim]); |
||||||
|
} |
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_IDLE); |
||||||
|
} |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable channels. The channels must've been configured in the settings (except ch 16 and 17 which are available always) |
||||||
|
* pld: u32: bitmap of channels |
||||||
|
*/ |
||||||
|
case CMD_ENABLE_CHANNELS: |
||||||
|
{ |
||||||
|
uint32_t new_channels = pp_u32(pp); |
||||||
|
|
||||||
|
// this tears down the peripherals sufficiently so we can re-configure them. Going back to IDLE re-inits this
|
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_UNINIT); |
||||||
|
|
||||||
|
uint32_t illegal_channels = new_channels & ~(priv->cfg.channels | (1<<16) | (1<<17)); // 16 and 17 may be enabled always
|
||||||
|
if (illegal_channels != 0) { |
||||||
|
com_respond_str(MSG_ERROR, frame_id, "Some requested channels not available"); |
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_IDLE); |
||||||
|
return E_FAILURE; |
||||||
|
} |
||||||
|
|
||||||
|
uint8_t nb_channels = 0; |
||||||
|
// count the enabled channels
|
||||||
|
for(int i = 0; i < 32; i++) { |
||||||
|
if (new_channels & (1<<i)) { |
||||||
|
nb_channels++; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (nb_channels == 0) { |
||||||
|
com_respond_str(MSG_ERROR, frame_id, "Need at least 1 channel"); |
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_IDLE); |
||||||
|
return E_FAILURE; |
||||||
|
} |
||||||
|
|
||||||
|
if (priv->cfg.buffer_size < nb_channels * 2) { |
||||||
|
com_respond_str(MSG_ERROR, frame_id, "Insufficient buf size"); |
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_IDLE); |
||||||
|
return E_BAD_CONFIG; |
||||||
|
} |
||||||
|
|
||||||
|
priv->nb_channels = nb_channels; |
||||||
|
priv->ADCx->CHSELR = new_channels; // apply it to the ADC
|
||||||
|
priv->channels_mask = new_channels; |
||||||
|
|
||||||
|
UADC_SetupDMA(unit); |
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_IDLE); |
||||||
|
} |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Read raw values from the last measurement. |
||||||
|
* Response: interleaved (u8:channel, u16:value) for all channels |
||||||
|
*/ |
||||||
|
case CMD_READ_RAW: |
||||||
|
if(priv->opmode != ADC_OPMODE_IDLE && priv->opmode != ADC_OPMODE_ARMED) { |
||||||
|
return E_BUSY; |
||||||
|
} |
||||||
|
|
||||||
|
for (uint8_t i = 0; i < 18; i++) { |
||||||
|
if (priv->channels_mask & (1 << i)) { |
||||||
|
pb_u16(&pb, priv->last_samples[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
com_respond_pb(frame_id, MSG_SUCCESS, &pb); |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Read smoothed values. |
||||||
|
* Response: interleaved (u8:channel, f32:value) for all channels |
||||||
|
*/ |
||||||
|
case CMD_READ_SMOOTHED: |
||||||
|
if(priv->opmode != ADC_OPMODE_IDLE && priv->opmode != ADC_OPMODE_ARMED) { |
||||||
|
return E_BUSY; |
||||||
|
} |
||||||
|
|
||||||
|
if (priv->real_frequency_int > UADC_MAX_FREQ_FOR_AVERAGING) { |
||||||
|
com_respond_str(MSG_ERROR, frame_id, "Too fast for smoothing"); |
||||||
|
return E_FAILURE; |
||||||
|
} |
||||||
|
|
||||||
|
for (uint8_t i = 0; i < 18; i++) { |
||||||
|
if (priv->channels_mask & (1 << i)) { |
||||||
|
pb_float(&pb, priv->averaging_bins[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
com_respond_pb(frame_id, MSG_SUCCESS, &pb); |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure a trigger. This is legal only if the current state is IDLE or ARMED (will re-arm). |
||||||
|
* |
||||||
|
* Payload: |
||||||
|
* u8 - source channel |
||||||
|
* u16 - triggering level |
||||||
|
* u8 - edge to trigger on: 1-rising, 2-falling, 3-both |
||||||
|
* u16 - pre-trigger samples count |
||||||
|
* u32 - post-trigger samples count |
||||||
|
* u16 - trigger hold-off in ms (dead time after firing, before it cna fire again if armed) |
||||||
|
* u8(bool) - auto re-arm after firing and completing the capture |
||||||
|
*/ |
||||||
|
case CMD_SETUP_TRIGGER: |
||||||
|
adc_dbg("> Setup trigger"); |
||||||
|
if (priv->opmode != ADC_OPMODE_IDLE && |
||||||
|
priv->opmode != ADC_OPMODE_ARMED && |
||||||
|
priv->opmode != ADC_OPMODE_REARM_PENDING) { |
||||||
|
return E_BUSY; |
||||||
|
} |
||||||
|
|
||||||
|
{ |
||||||
|
const uint8_t source = pp_u8(pp); |
||||||
|
const uint16_t level = pp_u16(pp); |
||||||
|
const uint8_t edge = pp_u8(pp); |
||||||
|
const uint32_t pretrig = pp_u32(pp); |
||||||
|
const uint32_t count = pp_u32(pp); |
||||||
|
const uint16_t holdoff = pp_u16(pp); |
||||||
|
const bool auto_rearm = pp_bool(pp); |
||||||
|
|
||||||
|
if (source > UADC_MAX_CHANNEL) { |
||||||
|
com_respond_str(MSG_ERROR, frame_id, "Invalid trig source"); |
||||||
|
return E_FAILURE; |
||||||
|
} |
||||||
|
|
||||||
|
if (0 == (priv->channels_mask & (1 << source))) { |
||||||
|
com_respond_str(MSG_ERROR, frame_id, "Channel not enabled"); |
||||||
|
return E_FAILURE; |
||||||
|
} |
||||||
|
|
||||||
|
if (level > 4095) { |
||||||
|
com_respond_str(MSG_ERROR, frame_id, "Level out of range (0-4095)"); |
||||||
|
return E_FAILURE; |
||||||
|
} |
||||||
|
|
||||||
|
if (edge == 0 || edge > 3) { |
||||||
|
com_respond_str(MSG_ERROR, frame_id, "Bad edge"); |
||||||
|
return E_FAILURE; |
||||||
|
} |
||||||
|
|
||||||
|
// XXX the max size may be too much
|
||||||
|
const uint32_t max_pretrig = (priv->buf_itemcount / priv->nb_channels); |
||||||
|
if (pretrig > max_pretrig) { |
||||||
|
com_respond_snprintf(frame_id, MSG_ERROR, |
||||||
|
"Pretrig too large (max %d)", (int) max_pretrig); |
||||||
|
return E_FAILURE; |
||||||
|
} |
||||||
|
|
||||||
|
priv->trigger_source = source; |
||||||
|
priv->trig_level = level; |
||||||
|
priv->trig_prev_level = priv->last_samples[source]; |
||||||
|
priv->trig_edge = edge; |
||||||
|
priv->pretrig_len = pretrig; |
||||||
|
priv->trig_len = count; |
||||||
|
priv->trig_holdoff = holdoff; |
||||||
|
priv->auto_rearm = auto_rearm; |
||||||
|
} |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Arm (permissible only if idle and the trigger is configured) |
||||||
|
*/ |
||||||
|
case CMD_ARM: |
||||||
|
adc_dbg("> Arm"); |
||||||
|
uint8_t sticky = pp_u8(pp); |
||||||
|
|
||||||
|
if(priv->opmode == ADC_OPMODE_ARMED || priv->opmode == ADC_OPMODE_REARM_PENDING) { |
||||||
|
// We are armed or will re-arm promptly, act like the call succeeded
|
||||||
|
// The auto flag is set regardless
|
||||||
|
} else { |
||||||
|
if (priv->opmode != ADC_OPMODE_IDLE) { |
||||||
|
return E_BUSY; // capture in progress
|
||||||
|
} |
||||||
|
|
||||||
|
if (priv->trig_len == 0) { |
||||||
|
com_respond_str(MSG_ERROR, frame_id, "Trigger not configured."); |
||||||
|
return E_FAILURE; |
||||||
|
} |
||||||
|
|
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_ARMED); |
||||||
|
} |
||||||
|
|
||||||
|
if (sticky != 255) { |
||||||
|
priv->auto_rearm = (bool)sticky; |
||||||
|
} |
||||||
|
|
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Dis-arm. Permissible only when idle or armed. |
||||||
|
* Switches to idle. |
||||||
|
*/ |
||||||
|
case CMD_DISARM: |
||||||
|
adc_dbg("> Disarm"); |
||||||
|
|
||||||
|
priv->auto_rearm = false; |
||||||
|
|
||||||
|
if(priv->opmode == ADC_OPMODE_IDLE) { |
||||||
|
return E_SUCCESS; // already idle, success - no work to do
|
||||||
|
} |
||||||
|
|
||||||
|
// capture in progress
|
||||||
|
if (priv->opmode != ADC_OPMODE_ARMED && |
||||||
|
priv->opmode != ADC_OPMODE_REARM_PENDING) { |
||||||
|
// Capture in progress, we already cleared auto rearm, so we're done for now
|
||||||
|
// auto_rearm is checked in the EOS isr and if cleared, does not re-arm.
|
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
UADC_SwitchMode(unit, ADC_OPMODE_IDLE); |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Abort any ongoing capture and dis-arm. |
||||||
|
*/ |
||||||
|
case CMD_ABORT:; |
||||||
|
adc_dbg("> Abort capture"); |
||||||
|
UADC_AbortCapture(unit); |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Force a trigger (complete with pre-trigger capture and hold-off) |
||||||
|
* The reported edge will be 0b11, here meaning "manual trigger" |
||||||
|
*/ |
||||||
|
case CMD_FORCE_TRIGGER: |
||||||
|
adc_dbg("> Force trigger"); |
||||||
|
// This is similar to block capture, but includes the pre-trig buffer and has fixed size based on trigger config
|
||||||
|
// FORCE is useful for checking if the trigger is set up correctly
|
||||||
|
if (priv->opmode != ADC_OPMODE_ARMED && |
||||||
|
priv->opmode != ADC_OPMODE_IDLE && |
||||||
|
priv->opmode != ADC_OPMODE_REARM_PENDING) return E_BUSY; |
||||||
|
|
||||||
|
if (priv->trig_len == 0) { |
||||||
|
com_respond_str(MSG_ERROR, frame_id, "Trigger not configured."); |
||||||
|
return E_FAILURE; |
||||||
|
} |
||||||
|
|
||||||
|
UADC_HandleTrigger(unit, 0b11, PTIM_GetMicrotime()); |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a block capture (like manual trigger, but without pre-trigger and arming) |
||||||
|
* |
||||||
|
* Payload: |
||||||
|
* u32 - sample count (for each channel) |
||||||
|
*/ |
||||||
|
case CMD_BLOCK_CAPTURE: |
||||||
|
adc_dbg("> Block cpt"); |
||||||
|
if (priv->opmode != ADC_OPMODE_ARMED && |
||||||
|
priv->opmode != ADC_OPMODE_REARM_PENDING && |
||||||
|
priv->opmode != ADC_OPMODE_IDLE) return E_BUSY; |
||||||
|
|
||||||
|
uint32_t count = pp_u32(pp); |
||||||
|
|
||||||
|
UADC_StartBlockCapture(unit, count, frame_id); |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Start streaming (like block capture, but unlimited) |
||||||
|
* The stream can be terminated by the stop command. |
||||||
|
*/ |
||||||
|
case CMD_STREAM_START: |
||||||
|
adc_dbg("> Stream ON"); |
||||||
|
if (priv->opmode != ADC_OPMODE_ARMED && |
||||||
|
priv->opmode != ADC_OPMODE_REARM_PENDING && |
||||||
|
priv->opmode != ADC_OPMODE_IDLE) return E_BUSY; |
||||||
|
|
||||||
|
UADC_StartStream(unit, frame_id); |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop a stream. |
||||||
|
*/ |
||||||
|
case CMD_STREAM_STOP: |
||||||
|
adc_dbg("> Stream OFF"); |
||||||
|
if (priv->opmode != ADC_OPMODE_STREAM) { |
||||||
|
com_respond_str(MSG_ERROR, frame_id, "Not streaming"); |
||||||
|
return E_FAILURE; |
||||||
|
} |
||||||
|
|
||||||
|
UADC_StopStream(unit); |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
default: |
||||||
|
return E_UNKNOWN_COMMAND; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Unit template */ |
||||||
|
const UnitDriver UNIT_ADC = { |
||||||
|
.name = "ADC", |
||||||
|
.description = "Analog/digital converter", |
||||||
|
// Settings
|
||||||
|
.preInit = UADC_preInit, |
||||||
|
.cfgLoadBinary = UADC_loadBinary, |
||||||
|
.cfgWriteBinary = UADC_writeBinary, |
||||||
|
.cfgLoadIni = UADC_loadIni, |
||||||
|
.cfgWriteIni = UADC_writeIni, |
||||||
|
// Init
|
||||||
|
.init = UADC_init, |
||||||
|
.deInit = UADC_deInit, |
||||||
|
// Function
|
||||||
|
.handleRequest = UADC_handleRequest, |
||||||
|
.updateTick = UADC_updateTick, |
||||||
|
}; |
@ -0,0 +1,15 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2017/11/25.
|
||||||
|
//
|
||||||
|
// ADC unit with several DSO-like features, like triggering, pre-trigger, block capture,
|
||||||
|
// streaming, smoothing...
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef U_TPL_H |
||||||
|
#define U_TPL_H |
||||||
|
|
||||||
|
#include "unit.h" |
||||||
|
|
||||||
|
extern const UnitDriver UNIT_ADC; |
||||||
|
|
||||||
|
#endif //U_TPL_H
|
@ -0,0 +1,71 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/03.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "platform.h" |
||||||
|
#include "unit_base.h" |
||||||
|
#include "unit_din.h" |
||||||
|
|
||||||
|
#define DIN_INTERNAL |
||||||
|
|
||||||
|
#include "_din_internal.h" |
||||||
|
|
||||||
|
/** Read request */ |
||||||
|
error_t UU_DIn_Read(Unit *unit, uint16_t *packed) |
||||||
|
{ |
||||||
|
CHECK_TYPE(unit, &UNIT_DIN); |
||||||
|
struct priv *priv = unit->data; |
||||||
|
*packed = pinmask_pack((uint16_t) priv->port->IDR, priv->pins); |
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
/** Arm pins */ |
||||||
|
error_t UU_DIn_Arm(Unit *unit, uint16_t arm_single_packed, uint16_t arm_auto_packed) |
||||||
|
{ |
||||||
|
CHECK_TYPE(unit, &UNIT_DIN); |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
uint16_t arm_single = pinmask_spread(arm_single_packed, priv->pins); |
||||||
|
uint16_t arm_auto = pinmask_spread(arm_auto_packed, priv->pins); |
||||||
|
|
||||||
|
// abort if user tries to arm pin that doesn't have a trigger configured
|
||||||
|
if (0 != ((arm_single | arm_auto) & ~(priv->trig_fall | priv->trig_rise))) { |
||||||
|
return E_BAD_VALUE; |
||||||
|
} |
||||||
|
|
||||||
|
// arm and reset hold-offs
|
||||||
|
// we use critical section to avoid irq between the two steps
|
||||||
|
vPortEnterCritical(); |
||||||
|
{ |
||||||
|
priv->arm_auto |= arm_single; |
||||||
|
priv->arm_single |= arm_auto; |
||||||
|
const uint16_t combined = arm_single | arm_auto; |
||||||
|
for (int i = 0; i < 16; i++) { |
||||||
|
if (combined & (1 << i)) { |
||||||
|
priv->holdoff_countdowns[i] = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
vPortExitCritical(); |
||||||
|
|
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
/** DisArm pins */ |
||||||
|
error_t UU_DIn_DisArm(Unit *unit, uint16_t disarm_packed) |
||||||
|
{ |
||||||
|
CHECK_TYPE(unit, &UNIT_DIN); |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
uint16_t disarm = pinmask_spread(disarm_packed, priv->pins); |
||||||
|
|
||||||
|
// abort if user tries to disarm pin that doesn't have a trigger configured
|
||||||
|
if (0 != ((disarm) & ~(priv->trig_fall | priv->trig_rise))) { |
||||||
|
return E_BAD_VALUE; |
||||||
|
} |
||||||
|
|
||||||
|
priv->arm_auto &= ~disarm; |
||||||
|
priv->arm_single &= ~disarm; |
||||||
|
|
||||||
|
return E_SUCCESS; |
||||||
|
} |
@ -0,0 +1,80 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/03.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "platform.h" |
||||||
|
#include "unit_base.h" |
||||||
|
|
||||||
|
#define DIN_INTERNAL |
||||||
|
|
||||||
|
#include "_din_internal.h" |
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a trigger event to master (called on the message queue thread). |
||||||
|
* |
||||||
|
* unit - unit |
||||||
|
* timestamp - timestamp |
||||||
|
* data1 - packed, triggering pin |
||||||
|
* data2 - snapshot |
||||||
|
*/ |
||||||
|
static void DIn_SendTriggerReportToMaster(Job *job) |
||||||
|
{ |
||||||
|
PayloadBuilder pb = pb_start(unit_tmp512, UNIT_TMP_LEN, NULL); |
||||||
|
pb_u16(&pb, (uint16_t) job->data1); // packed, 1 on the triggering pin
|
||||||
|
pb_u16(&pb, (uint16_t) job->data2); // packed, snapshot
|
||||||
|
assert_param(pb.ok); |
||||||
|
|
||||||
|
EventReport event = { |
||||||
|
.unit = job->unit, |
||||||
|
.timestamp = job->timestamp, |
||||||
|
.data = pb.start, |
||||||
|
.length = (uint16_t) pb_length(&pb), |
||||||
|
}; |
||||||
|
|
||||||
|
EventReport_Send(&event); |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* EXTI callback for pin change interrupts |
||||||
|
* |
||||||
|
* @param arg - the unit is passed here |
||||||
|
*/ |
||||||
|
void DIn_handleExti(void *arg) |
||||||
|
{ |
||||||
|
const uint64_t ts = PTIM_GetMicrotime(); |
||||||
|
|
||||||
|
Unit *unit = arg; |
||||||
|
struct priv *priv = unit->data; |
||||||
|
const uint16_t snapshot = (uint16_t) priv->port->IDR; |
||||||
|
|
||||||
|
uint16_t trigger_map = 0; |
||||||
|
|
||||||
|
uint16_t mask = 1; |
||||||
|
const uint16_t armed_pins = priv->arm_single | priv->arm_auto; |
||||||
|
for (int i = 0; i < 16; i++, mask <<= 1) { |
||||||
|
if (!LL_EXTI_ReadFlag_0_31(LL_EXTI_LINES[i])) continue; |
||||||
|
LL_EXTI_ClearFlag_0_31(LL_EXTI_LINES[i]); |
||||||
|
|
||||||
|
// Armed and ready
|
||||||
|
if ((armed_pins & mask) && (priv->holdoff_countdowns[i] == 0)) { |
||||||
|
// Mark as captured
|
||||||
|
trigger_map |= (1 << i); |
||||||
|
// Start hold-off (no-op if zero hold-off)
|
||||||
|
priv->holdoff_countdowns[i] = priv->trig_holdoff; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Disarm all possibly used single triggers
|
||||||
|
priv->arm_single &= ~trigger_map; |
||||||
|
|
||||||
|
if (trigger_map != 0) { |
||||||
|
Job j = { |
||||||
|
.unit = unit, |
||||||
|
.timestamp = ts, |
||||||
|
.data1 = pinmask_pack(trigger_map, priv->pins), |
||||||
|
.data2 = pinmask_pack(snapshot, priv->pins), |
||||||
|
.cb = DIn_SendTriggerReportToMaster |
||||||
|
}; |
||||||
|
scheduleJob(&j); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,127 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/03.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "platform.h" |
||||||
|
#include "unit_base.h" |
||||||
|
|
||||||
|
#define DIN_INTERNAL |
||||||
|
#include "_din_internal.h" |
||||||
|
|
||||||
|
/** Allocate data structure and set defaults */ |
||||||
|
error_t DIn_preInit(Unit *unit) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv)); |
||||||
|
if (priv == NULL) return E_OUT_OF_MEM; |
||||||
|
|
||||||
|
// some defaults
|
||||||
|
priv->port_name = 'A'; |
||||||
|
priv->pins = 0x0001; |
||||||
|
priv->pulldown = 0x0000; |
||||||
|
priv->pullup = 0x0000; |
||||||
|
|
||||||
|
priv->trig_rise = 0x0000; |
||||||
|
priv->trig_fall = 0x0000; |
||||||
|
priv->trig_holdoff = 100; |
||||||
|
priv->def_auto = 0x0000; |
||||||
|
|
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
/** Finalize unit set-up */ |
||||||
|
error_t DIn_init(Unit *unit) |
||||||
|
{ |
||||||
|
bool suc = true; |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
priv->pulldown &= priv->pins; |
||||||
|
priv->pullup &= priv->pins; |
||||||
|
priv->trig_rise &= priv->pins; |
||||||
|
priv->trig_fall &= priv->pins; |
||||||
|
priv->def_auto &= (priv->trig_rise|priv->trig_fall); |
||||||
|
|
||||||
|
// copy auto-arm defaults to the auto-arm register (the register may be manipulated by commands)
|
||||||
|
priv->arm_auto = priv->def_auto; |
||||||
|
priv->arm_single = 0; |
||||||
|
|
||||||
|
// clear countdowns
|
||||||
|
memset(priv->holdoff_countdowns, 0, sizeof(priv->holdoff_countdowns)); |
||||||
|
|
||||||
|
// --- Parse config ---
|
||||||
|
priv->port = hw_port2periph(priv->port_name, &suc); |
||||||
|
if (!suc) return E_BAD_CONFIG; |
||||||
|
|
||||||
|
// Claim all needed pins
|
||||||
|
TRY(rsc_claim_gpios(unit, priv->port_name, priv->pins)); |
||||||
|
|
||||||
|
uint16_t mask = 1; |
||||||
|
for (int i = 0; i < 16; i++, mask <<= 1) { |
||||||
|
if (priv->pins & mask) { |
||||||
|
uint32_t ll_pin = hw_pin2ll((uint8_t) i, &suc); |
||||||
|
|
||||||
|
// --- Init hardware ---
|
||||||
|
LL_GPIO_SetPinMode(priv->port, ll_pin, LL_GPIO_MODE_INPUT); |
||||||
|
|
||||||
|
uint32_t pull = 0; |
||||||
|
|
||||||
|
#if PLAT_NO_FLOATING_INPUTS |
||||||
|
pull = LL_GPIO_PULL_UP; |
||||||
|
#else |
||||||
|
pull = LL_GPIO_PULL_NO; |
||||||
|
#endif |
||||||
|
|
||||||
|
if (priv->pulldown & mask) pull = LL_GPIO_PULL_DOWN; |
||||||
|
if (priv->pullup & mask) pull = LL_GPIO_PULL_UP; |
||||||
|
LL_GPIO_SetPinPull(priv->port, ll_pin, pull); |
||||||
|
|
||||||
|
if ((priv->trig_rise|priv->trig_fall) & mask) { |
||||||
|
LL_EXTI_EnableIT_0_31(LL_EXTI_LINES[i]); |
||||||
|
|
||||||
|
if (priv->trig_rise & mask) { |
||||||
|
LL_EXTI_EnableRisingTrig_0_31(LL_EXTI_LINES[i]); |
||||||
|
} |
||||||
|
if (priv->trig_fall & mask) { |
||||||
|
LL_EXTI_EnableFallingTrig_0_31(LL_EXTI_LINES[i]); |
||||||
|
} |
||||||
|
|
||||||
|
LL_SYSCFG_SetEXTISource(LL_SYSCFG_EXTI_PORTS[priv->port_name-'A'], LL_SYSCFG_EXTI_LINES[i]); |
||||||
|
|
||||||
|
irqd_attach(EXTIS[i], DIn_handleExti, unit); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// request ticks if we have triggers and any hold-offs configured
|
||||||
|
if ((priv->trig_rise|priv->trig_fall) && priv->trig_holdoff > 0) { |
||||||
|
unit->tick_interval = 1; |
||||||
|
} |
||||||
|
|
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** Tear down the unit */ |
||||||
|
void DIn_deInit(Unit *unit) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
// pins are de-inited during teardown
|
||||||
|
|
||||||
|
// Detach EXTI handlers and disable interrupts
|
||||||
|
const uint16_t triggs = priv->trig_rise | priv->trig_fall; |
||||||
|
if (unit->status == E_SUCCESS && triggs) { |
||||||
|
uint16_t mask = 1; |
||||||
|
for (int i = 0; i < 16; i++, mask <<= 1) { |
||||||
|
if (triggs & mask) { |
||||||
|
LL_EXTI_DisableIT_0_31(LL_EXTI_LINES[i]); |
||||||
|
irqd_detach(EXTIS[i], DIn_handleExti); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Release all resources
|
||||||
|
rsc_teardown(unit); |
||||||
|
|
||||||
|
// Free memory
|
||||||
|
free_ck(unit->data); |
||||||
|
} |
@ -0,0 +1,65 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/03.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef GEX_F072_DIN_INTERNAL_H |
||||||
|
#define GEX_F072_DIN_INTERNAL_H |
||||||
|
|
||||||
|
#ifndef DIN_INTERNAL |
||||||
|
#error bad include! |
||||||
|
#endif |
||||||
|
|
||||||
|
#include "unit_base.h" |
||||||
|
|
||||||
|
/** Private data structure */ |
||||||
|
struct priv { |
||||||
|
char port_name; |
||||||
|
uint16_t pins; // pin mask
|
||||||
|
uint16_t pulldown; // pull-downs (default is pull-up)
|
||||||
|
uint16_t pullup; // pull-ups
|
||||||
|
uint16_t trig_rise; // pins generating events on rising edge
|
||||||
|
uint16_t trig_fall; // pins generating events on falling edge
|
||||||
|
uint16_t trig_holdoff; // ms
|
||||||
|
uint16_t def_auto; // initial auto triggers
|
||||||
|
|
||||||
|
uint16_t arm_auto; // pins armed for auto reporting
|
||||||
|
uint16_t arm_single; // pins armed for single event
|
||||||
|
uint16_t holdoff_countdowns[16]; // countdowns to arm for each pin in the bit map
|
||||||
|
GPIO_TypeDef *port; |
||||||
|
}; |
||||||
|
|
||||||
|
/** Allocate data structure and set defaults */ |
||||||
|
error_t DIn_preInit(Unit *unit); |
||||||
|
|
||||||
|
/** Load from a binary buffer stored in Flash */ |
||||||
|
void DIn_loadBinary(Unit *unit, PayloadParser *pp); |
||||||
|
|
||||||
|
/** Write to a binary buffer for storing in Flash */ |
||||||
|
void DIn_writeBinary(Unit *unit, PayloadBuilder *pb); |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Parse a key-value pair from the INI file */ |
||||||
|
error_t DIn_loadIni(Unit *unit, const char *key, const char *value); |
||||||
|
|
||||||
|
/** Generate INI file section for the unit */ |
||||||
|
void DIn_writeIni(Unit *unit, IniWriter *iw); |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Finalize unit set-up */ |
||||||
|
error_t DIn_init(Unit *unit); |
||||||
|
|
||||||
|
/** Tear down the unit */ |
||||||
|
void DIn_deInit(Unit *unit); |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EXTI callback for pin change interrupts |
||||||
|
* |
||||||
|
* @param arg - the unit is passed here |
||||||
|
*/ |
||||||
|
void DIn_handleExti(void *arg); |
||||||
|
|
||||||
|
#endif //GEX_F072_DIN_INTERNAL_H
|
@ -0,0 +1,118 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/03.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "platform.h" |
||||||
|
#include "unit_base.h" |
||||||
|
|
||||||
|
#define DIN_INTERNAL |
||||||
|
#include "_din_internal.h" |
||||||
|
|
||||||
|
/** Load from a binary buffer stored in Flash */ |
||||||
|
void DIn_loadBinary(Unit *unit, PayloadParser *pp) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
uint8_t version = pp_u8(pp); |
||||||
|
(void)version; |
||||||
|
|
||||||
|
priv->port_name = pp_char(pp); |
||||||
|
priv->pins = pp_u16(pp); |
||||||
|
priv->pulldown = pp_u16(pp); |
||||||
|
priv->pullup = pp_u16(pp); |
||||||
|
priv->trig_rise = pp_u16(pp); |
||||||
|
priv->trig_fall = pp_u16(pp); |
||||||
|
priv->trig_holdoff = pp_u16(pp); |
||||||
|
priv->def_auto = pp_u16(pp); |
||||||
|
} |
||||||
|
|
||||||
|
/** Write to a binary buffer for storing in Flash */ |
||||||
|
void DIn_writeBinary(Unit *unit, PayloadBuilder *pb) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
pb_u8(pb, 0); // version
|
||||||
|
|
||||||
|
pb_char(pb, priv->port_name); |
||||||
|
pb_u16(pb, priv->pins); |
||||||
|
pb_u16(pb, priv->pulldown); |
||||||
|
pb_u16(pb, priv->pullup); |
||||||
|
pb_u16(pb, priv->trig_rise); |
||||||
|
pb_u16(pb, priv->trig_fall); |
||||||
|
pb_u16(pb, priv->trig_holdoff); |
||||||
|
pb_u16(pb, priv->def_auto); |
||||||
|
} |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Parse a key-value pair from the INI file */ |
||||||
|
error_t DIn_loadIni(Unit *unit, const char *key, const char *value) |
||||||
|
{ |
||||||
|
bool suc = true; |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
if (streq(key, "port")) { |
||||||
|
suc = cfg_port_parse(value, &priv->port_name); |
||||||
|
} |
||||||
|
else if (streq(key, "pins")) { |
||||||
|
priv->pins = cfg_pinmask_parse(value, &suc); |
||||||
|
} |
||||||
|
else if (streq(key, "pull-up")) { |
||||||
|
priv->pullup = cfg_pinmask_parse(value, &suc); |
||||||
|
} |
||||||
|
else if (streq(key, "pull-down")) { |
||||||
|
priv->pulldown = cfg_pinmask_parse(value, &suc); |
||||||
|
} |
||||||
|
else if (streq(key, "trig-rise")) { |
||||||
|
priv->trig_rise = cfg_pinmask_parse(value, &suc); |
||||||
|
} |
||||||
|
else if (streq(key, "trig-fall")) { |
||||||
|
priv->trig_fall = cfg_pinmask_parse(value, &suc); |
||||||
|
} |
||||||
|
else if (streq(key, "auto-trigger")) { |
||||||
|
priv->def_auto = cfg_pinmask_parse(value, &suc); |
||||||
|
} |
||||||
|
else if (streq(key, "hold-off")) { |
||||||
|
priv->trig_holdoff = cfg_u16_parse(value, &suc); |
||||||
|
} |
||||||
|
else { |
||||||
|
return E_BAD_KEY; |
||||||
|
} |
||||||
|
|
||||||
|
if (!suc) return E_BAD_VALUE; |
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
/** Generate INI file section for the unit */ |
||||||
|
void DIn_writeIni(Unit *unit, IniWriter *iw) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
iw_comment(iw, "Port name"); |
||||||
|
iw_entry(iw, "port", "%c", priv->port_name); |
||||||
|
|
||||||
|
iw_comment(iw, "Pins (comma separated, supports ranges)"); |
||||||
|
iw_entry(iw, "pins", cfg_pinmask_encode(priv->pins, unit_tmp512, 0)); |
||||||
|
|
||||||
|
iw_comment(iw, "Pins with pull-up"); |
||||||
|
iw_entry(iw, "pull-up", cfg_pinmask_encode(priv->pullup, unit_tmp512, 0)); |
||||||
|
|
||||||
|
iw_comment(iw, "Pins with pull-down"); |
||||||
|
iw_entry(iw, "pull-down", cfg_pinmask_encode(priv->pulldown, unit_tmp512, 0)); |
||||||
|
|
||||||
|
iw_cmt_newline(iw); |
||||||
|
iw_comment(iw, "Trigger pins activated by rising/falling edge"); |
||||||
|
iw_entry(iw, "trig-rise", cfg_pinmask_encode(priv->trig_rise, unit_tmp512, 0)); |
||||||
|
iw_entry(iw, "trig-fall", cfg_pinmask_encode(priv->trig_fall, unit_tmp512, 0)); |
||||||
|
|
||||||
|
iw_comment(iw, "Trigger pins auto-armed by default"); |
||||||
|
iw_entry(iw, "auto-trigger", cfg_pinmask_encode(priv->def_auto, unit_tmp512, 0)); |
||||||
|
|
||||||
|
iw_comment(iw, "Triggers hold-off time (ms)"); |
||||||
|
iw_entry(iw, "hold-off", "%d", (int)priv->trig_holdoff); |
||||||
|
|
||||||
|
#if PLAT_NO_FLOATING_INPUTS |
||||||
|
iw_comment(iw, "NOTE: Pins use pull-up by default.\r\n"); |
||||||
|
#endif |
||||||
|
} |
||||||
|
|
@ -0,0 +1,94 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2017/11/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "unit_base.h" |
||||||
|
#include "unit_din.h" |
||||||
|
|
||||||
|
#define DIN_INTERNAL |
||||||
|
#include "_din_internal.h" |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
enum PinCmd_ { |
||||||
|
CMD_READ = 0, |
||||||
|
CMD_ARM_SINGLE = 1, |
||||||
|
CMD_ARM_AUTO = 2, |
||||||
|
CMD_DISARM = 3, |
||||||
|
}; |
||||||
|
|
||||||
|
/** Handle a request message */ |
||||||
|
static error_t DIn_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp) |
||||||
|
{ |
||||||
|
uint16_t pins = 0; |
||||||
|
|
||||||
|
switch (command) { |
||||||
|
case CMD_READ:; |
||||||
|
TRY(UU_DIn_Read(unit, &pins)); |
||||||
|
|
||||||
|
PayloadBuilder pb = pb_start((uint8_t*)unit_tmp512, UNIT_TMP_LEN, NULL); |
||||||
|
pb_u16(&pb, pins); // packed input pins
|
||||||
|
com_respond_buf(frame_id, MSG_SUCCESS, (uint8_t *) unit_tmp512, pb_length(&pb)); |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
case CMD_ARM_SINGLE:; |
||||||
|
pins = pp_u16(pp); |
||||||
|
if (!pp->ok) return E_MALFORMED_COMMAND; |
||||||
|
|
||||||
|
TRY(UU_DIn_Arm(unit, pins, 0)); |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
case CMD_ARM_AUTO:; |
||||||
|
pins = pp_u16(pp); |
||||||
|
if (!pp->ok) return E_MALFORMED_COMMAND; |
||||||
|
|
||||||
|
TRY(UU_DIn_Arm(unit, 0, pins)); |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
case CMD_DISARM:; |
||||||
|
pins = pp_u16(pp); |
||||||
|
if (!pp->ok) return E_MALFORMED_COMMAND; |
||||||
|
|
||||||
|
TRY(UU_DIn_DisArm(unit, pins)); |
||||||
|
return E_SUCCESS; |
||||||
|
|
||||||
|
default: |
||||||
|
return E_UNKNOWN_COMMAND; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrement all the hold-off timers on tick |
||||||
|
* |
||||||
|
* @param unit |
||||||
|
*/ |
||||||
|
static void DIn_updateTick(Unit *unit) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
for (int i = 0; i < 16; i++) { |
||||||
|
if (priv->holdoff_countdowns[i] > 0) { |
||||||
|
priv->holdoff_countdowns[i]--; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Unit template */ |
||||||
|
const UnitDriver UNIT_DIN = { |
||||||
|
.name = "DI", |
||||||
|
.description = "Digital input with triggers", |
||||||
|
// Settings
|
||||||
|
.preInit = DIn_preInit, |
||||||
|
.cfgLoadBinary = DIn_loadBinary, |
||||||
|
.cfgWriteBinary = DIn_writeBinary, |
||||||
|
.cfgLoadIni = DIn_loadIni, |
||||||
|
.cfgWriteIni = DIn_writeIni, |
||||||
|
// Init
|
||||||
|
.init = DIn_init, |
||||||
|
.deInit = DIn_deInit, |
||||||
|
// Function
|
||||||
|
.handleRequest = DIn_handleRequest, |
||||||
|
.updateTick = DIn_updateTick, |
||||||
|
}; |
@ -0,0 +1,42 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2017/11/25.
|
||||||
|
//
|
||||||
|
// Digital input unit; single or multiple pin read access on one port (A-F)
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef U_DIN_H |
||||||
|
#define U_DIN_H |
||||||
|
|
||||||
|
#include "unit.h" |
||||||
|
|
||||||
|
extern const UnitDriver UNIT_DIN; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Read pins |
||||||
|
* |
||||||
|
* @param unit - unit instance |
||||||
|
* @param packed - output; the packed (right aligned) bits representing the pins, highest to lowest, are written here. |
||||||
|
* @return success |
||||||
|
*/ |
||||||
|
error_t UU_DIn_Read(Unit *unit, uint16_t *packed); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Arm pins for trigger generation |
||||||
|
* |
||||||
|
* @param unit - unit instance |
||||||
|
* @param arm_single_packed - packed bit map of pins to arm for single trigger |
||||||
|
* @param arm_auto_packed - packed bit map of pins to arm for auto trigger (repeated) |
||||||
|
* @return success |
||||||
|
*/ |
||||||
|
error_t UU_DIn_Arm(Unit *unit, uint16_t arm_single_packed, uint16_t arm_auto_packed); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Dis-arm pins to not generate events |
||||||
|
* |
||||||
|
* @param unit - unit instance |
||||||
|
* @param disarm_packed - packed bit map of pins to dis-arm |
||||||
|
* @return success |
||||||
|
*/ |
||||||
|
error_t UU_DIn_DisArm(Unit *unit, uint16_t disarm_packed); |
||||||
|
|
||||||
|
#endif //U_DIN_H
|
@ -0,0 +1,73 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/03.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "platform.h" |
||||||
|
#include "unit_base.h" |
||||||
|
#include "unit_dout.h" |
||||||
|
|
||||||
|
#define DOUT_INTERNAL |
||||||
|
#include "_dout_internal.h" |
||||||
|
|
||||||
|
error_t UU_DOut_Write(Unit *unit, uint16_t packed) |
||||||
|
{ |
||||||
|
CHECK_TYPE(unit, &UNIT_DOUT); |
||||||
|
|
||||||
|
struct priv *priv = unit->data; |
||||||
|
uint16_t mask = priv->pins; |
||||||
|
uint16_t spread = pinmask_spread(packed, mask); |
||||||
|
|
||||||
|
uint16_t set = spread; |
||||||
|
uint16_t reset = ((~spread) & mask); |
||||||
|
priv->port->BSRR = set | (reset << 16); |
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
error_t UU_DOut_Set(Unit *unit, uint16_t packed) |
||||||
|
{ |
||||||
|
CHECK_TYPE(unit, &UNIT_DOUT); |
||||||
|
|
||||||
|
struct priv *priv = unit->data; |
||||||
|
uint16_t mask = priv->pins; |
||||||
|
uint16_t spread = pinmask_spread(packed, mask); |
||||||
|
|
||||||
|
priv->port->BSRR = spread; |
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
error_t UU_DOut_Clear(Unit *unit, uint16_t packed) |
||||||
|
{ |
||||||
|
CHECK_TYPE(unit, &UNIT_DOUT); |
||||||
|
|
||||||
|
struct priv *priv = unit->data; |
||||||
|
uint16_t mask = priv->pins; |
||||||
|
uint16_t spread = pinmask_spread(packed, mask); |
||||||
|
|
||||||
|
priv->port->BSRR = (spread<<16); |
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
error_t UU_DOut_Toggle(Unit *unit, uint16_t packed) |
||||||
|
{ |
||||||
|
CHECK_TYPE(unit, &UNIT_DOUT); |
||||||
|
|
||||||
|
struct priv *priv = unit->data; |
||||||
|
uint16_t mask = priv->pins; |
||||||
|
uint16_t spread = pinmask_spread(packed, mask); |
||||||
|
|
||||||
|
uint16_t flipped = (uint16_t) (~priv->port->ODR) & mask; |
||||||
|
uint16_t set = flipped & spread; |
||||||
|
uint16_t reset = ((~flipped) & mask) & spread; |
||||||
|
priv->port->BSRR = set | (reset<<16); |
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
error_t UU_DOut_GetPinCount(Unit *unit, uint8_t *count) |
||||||
|
{ |
||||||
|
CHECK_TYPE(unit, &UNIT_DOUT); |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
uint32_t packed = pinmask_pack(0xFFFF, priv->pins); |
||||||
|
*count = (uint8_t)(32 - __CLZ(packed)); |
||||||
|
return E_SUCCESS; |
||||||
|
} |
@ -0,0 +1,71 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/03.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "platform.h" |
||||||
|
#include "unit_base.h" |
||||||
|
|
||||||
|
#define DOUT_INTERNAL |
||||||
|
#include "_dout_internal.h" |
||||||
|
|
||||||
|
/** Allocate data structure and set defaults */ |
||||||
|
error_t DOut_preInit(Unit *unit) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv)); |
||||||
|
if (priv == NULL) return E_OUT_OF_MEM; |
||||||
|
|
||||||
|
// some defaults
|
||||||
|
priv->port_name = 'A'; |
||||||
|
priv->pins = 0x0001; |
||||||
|
priv->open_drain = 0x0000; |
||||||
|
priv->initial = 0x0000; |
||||||
|
|
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
/** Finalize unit set-up */ |
||||||
|
error_t DOut_init(Unit *unit) |
||||||
|
{ |
||||||
|
bool suc = true; |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
priv->initial &= priv->pins; |
||||||
|
priv->open_drain &= priv->pins; |
||||||
|
|
||||||
|
// --- Parse config ---
|
||||||
|
priv->port = hw_port2periph(priv->port_name, &suc); |
||||||
|
if (!suc) return E_BAD_CONFIG; |
||||||
|
|
||||||
|
// Claim all needed pins
|
||||||
|
TRY(rsc_claim_gpios(unit, priv->port_name, priv->pins)); |
||||||
|
|
||||||
|
for (int i = 0; i < 16; i++) { |
||||||
|
if (priv->pins & (1 << i)) { |
||||||
|
uint32_t ll_pin = hw_pin2ll((uint8_t) i, &suc); |
||||||
|
|
||||||
|
// --- Init hardware ---
|
||||||
|
LL_GPIO_SetPinMode(priv->port, ll_pin, LL_GPIO_MODE_OUTPUT); |
||||||
|
LL_GPIO_SetPinOutputType(priv->port, ll_pin, |
||||||
|
(priv->open_drain & (1 << i)) ? LL_GPIO_OUTPUT_OPENDRAIN : LL_GPIO_OUTPUT_PUSHPULL); |
||||||
|
LL_GPIO_SetPinSpeed(priv->port, ll_pin, LL_GPIO_SPEED_FREQ_HIGH); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Set the initial state
|
||||||
|
priv->port->ODR &= ~priv->pins; |
||||||
|
priv->port->ODR |= priv->initial; |
||||||
|
|
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
/** Tear down the unit */ |
||||||
|
void DOut_deInit(Unit *unit) |
||||||
|
{ |
||||||
|
// pins are de-inited during teardown
|
||||||
|
|
||||||
|
// Release all resources
|
||||||
|
rsc_teardown(unit); |
||||||
|
|
||||||
|
// Free memory
|
||||||
|
free_ck(unit->data); |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/03.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef GEX_F072_DOUT_INTERNAL_H |
||||||
|
#define GEX_F072_DOUT_INTERNAL_H |
||||||
|
|
||||||
|
#ifndef DOUT_INTERNAL |
||||||
|
#error bad include! |
||||||
|
#endif |
||||||
|
|
||||||
|
#include "unit_base.h" |
||||||
|
|
||||||
|
/** Private data structure */ |
||||||
|
struct priv { |
||||||
|
char port_name; |
||||||
|
uint16_t pins; // pin mask
|
||||||
|
uint16_t initial; // initial pin states
|
||||||
|
uint16_t open_drain; // open drain pins
|
||||||
|
|
||||||
|
GPIO_TypeDef *port; |
||||||
|
}; |
||||||
|
|
||||||
|
/** Allocate data structure and set defaults */ |
||||||
|
error_t DOut_preInit(Unit *unit); |
||||||
|
|
||||||
|
/** Load from a binary buffer stored in Flash */ |
||||||
|
void DOut_loadBinary(Unit *unit, PayloadParser *pp); |
||||||
|
|
||||||
|
/** Write to a binary buffer for storing in Flash */ |
||||||
|
void DOut_writeBinary(Unit *unit, PayloadBuilder *pb); |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Parse a key-value pair from the INI file */ |
||||||
|
error_t DOut_loadIni(Unit *unit, const char *key, const char *value); |
||||||
|
|
||||||
|
/** Generate INI file section for the unit */ |
||||||
|
void DOut_writeIni(Unit *unit, IniWriter *iw); |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Finalize unit set-up */ |
||||||
|
error_t DOut_init(Unit *unit); |
||||||
|
|
||||||
|
/** Tear down the unit */ |
||||||
|
void DOut_deInit(Unit *unit); |
||||||
|
|
||||||
|
#endif //GEX_F072_DOUT_INTERNAL_H
|
@ -0,0 +1,82 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/03.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "platform.h" |
||||||
|
#include "unit_base.h" |
||||||
|
|
||||||
|
#define DOUT_INTERNAL |
||||||
|
#include "_dout_internal.h" |
||||||
|
|
||||||
|
/** Load from a binary buffer stored in Flash */ |
||||||
|
void DOut_loadBinary(Unit *unit, PayloadParser *pp) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
uint8_t version = pp_u8(pp); |
||||||
|
(void)version; |
||||||
|
|
||||||
|
priv->port_name = pp_char(pp); |
||||||
|
priv->pins = pp_u16(pp); |
||||||
|
priv->initial = pp_u16(pp); |
||||||
|
priv->open_drain = pp_u16(pp); |
||||||
|
} |
||||||
|
|
||||||
|
/** Write to a binary buffer for storing in Flash */ |
||||||
|
void DOut_writeBinary(Unit *unit, PayloadBuilder *pb) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
pb_u8(pb, 0); // version
|
||||||
|
|
||||||
|
pb_char(pb, priv->port_name); |
||||||
|
pb_u16(pb, priv->pins); |
||||||
|
pb_u16(pb, priv->initial); |
||||||
|
pb_u16(pb, priv->open_drain); |
||||||
|
} |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Parse a key-value pair from the INI file */ |
||||||
|
error_t DOut_loadIni(Unit *unit, const char *key, const char *value) |
||||||
|
{ |
||||||
|
bool suc = true; |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
if (streq(key, "port")) { |
||||||
|
suc = cfg_port_parse(value, &priv->port_name); |
||||||
|
} |
||||||
|
else if (streq(key, "pins")) { |
||||||
|
priv->pins = cfg_pinmask_parse(value, &suc); |
||||||
|
} |
||||||
|
else if (streq(key, "initial")) { |
||||||
|
priv->initial = cfg_pinmask_parse(value, &suc); |
||||||
|
} |
||||||
|
else if (streq(key, "open-drain")) { |
||||||
|
priv->open_drain = cfg_pinmask_parse(value, &suc); |
||||||
|
} |
||||||
|
else { |
||||||
|
return E_BAD_KEY; |
||||||
|
} |
||||||
|
|
||||||
|
if (!suc) return E_BAD_VALUE; |
||||||
|
return E_SUCCESS; |
||||||
|
} |
||||||
|
|
||||||
|
/** Generate INI file section for the unit */ |
||||||
|
void DOut_writeIni(Unit *unit, IniWriter *iw) |
||||||
|
{ |
||||||
|
struct priv *priv = unit->data; |
||||||
|
|
||||||
|
iw_comment(iw, "Port name"); |
||||||
|
iw_entry(iw, "port", "%c", priv->port_name); |
||||||
|
|
||||||
|
iw_comment(iw, "Pins (comma separated, supports ranges)"); |
||||||
|
iw_entry(iw, "pins", cfg_pinmask_encode(priv->pins, unit_tmp512, 0)); |
||||||
|
|
||||||
|
iw_comment(iw, "Initially high pins"); |
||||||
|
iw_entry(iw, "initial", cfg_pinmask_encode(priv->initial, unit_tmp512, 0)); |
||||||
|
|
||||||
|
iw_comment(iw, "Open-drain pins"); |
||||||
|
iw_entry(iw, "open-drain", cfg_pinmask_encode(priv->open_drain, unit_tmp512, 0)); |
||||||
|
} |
@ -0,0 +1,58 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2017/11/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "unit_base.h" |
||||||
|
#include "unit_dout.h" |
||||||
|
|
||||||
|
#define DOUT_INTERNAL |
||||||
|
#include "_dout_internal.h" |
||||||
|
|
||||||
|
enum PinCmd_ { |
||||||
|
CMD_TEST = 0, |
||||||
|
CMD_SET = 1, |
||||||
|
CMD_CLEAR = 2, |
||||||
|
CMD_TOGGLE = 3, |
||||||
|
}; |
||||||
|
|
||||||
|
/** Handle a request message */ |
||||||
|
static error_t DOut_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp) |
||||||
|
{ |
||||||
|
uint16_t packed = pp_u16(pp); |
||||||
|
|
||||||
|
switch (command) { |
||||||
|
case CMD_TEST: |
||||||
|
return UU_DOut_Write(unit, packed); |
||||||
|
|
||||||
|
case CMD_SET: |
||||||
|
return UU_DOut_Set(unit, packed); |
||||||
|
|
||||||
|
case CMD_CLEAR: |
||||||
|
return UU_DOut_Clear(unit, packed); |
||||||
|
|
||||||
|
case CMD_TOGGLE: |
||||||
|
return UU_DOut_Toggle(unit, packed); |
||||||
|
|
||||||
|
default: |
||||||
|
return E_UNKNOWN_COMMAND; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Unit template */ |
||||||
|
const UnitDriver UNIT_DOUT = { |
||||||
|
.name = "DO", |
||||||
|
.description = "Digital output", |
||||||
|
// Settings
|
||||||
|
.preInit = DOut_preInit, |
||||||
|
.cfgLoadBinary = DOut_loadBinary, |
||||||
|
.cfgWriteBinary = DOut_writeBinary, |
||||||
|
.cfgLoadIni = DOut_loadIni, |
||||||
|
.cfgWriteIni = DOut_writeIni, |
||||||
|
// Init
|
||||||
|
.init = DOut_init, |
||||||
|
.deInit = DOut_deInit, |
||||||
|
// Function
|
||||||
|
.handleRequest = DOut_handleRequest, |
||||||
|
}; |
@ -0,0 +1,59 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2017/11/25.
|
||||||
|
//
|
||||||
|
// Digital output unit; single or multiple pin write access on one port (A-F)
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef U_DOUT_H |
||||||
|
#define U_DOUT_H |
||||||
|
|
||||||
|
#include "unit.h" |
||||||
|
|
||||||
|
extern const UnitDriver UNIT_DOUT; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Write pins (e.g. writing 0b10 if configured pins are PA5 and PA0 sets PA5=1,PA0=0) |
||||||
|
* |
||||||
|
* @param unit |
||||||
|
* @param packed - packed pin states (aligned to right) |
||||||
|
* @return success |
||||||
|
*/ |
||||||
|
error_t UU_DOut_Write(Unit *unit, uint16_t packed); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Set pins (clear none) |
||||||
|
* |
||||||
|
* @param unit |
||||||
|
* @param packed - packed pins, 1 if pin should be set |
||||||
|
* @return success |
||||||
|
*/ |
||||||
|
error_t UU_DOut_Set(Unit *unit, uint16_t packed); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear multiple pins |
||||||
|
* |
||||||
|
* @param unit |
||||||
|
* @param packed - packed pins, 1 if pin should be cleared |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
error_t UU_DOut_Clear(Unit *unit, uint16_t packed); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle pins |
||||||
|
* |
||||||
|
* @param unit |
||||||
|
* @param packed - packed pins, 1 if pin should be toggled |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
error_t UU_DOut_Toggle(Unit *unit, uint16_t packed); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Get number of configured pins |
||||||
|
* |
||||||
|
* @param unit |
||||||
|
* @param count output, written with 0-16 |
||||||
|
* @return success |
||||||
|
*/ |
||||||
|
error_t UU_DOut_GetPinCount(Unit *unit, uint8_t *count); |
||||||
|
|
||||||
|
#endif //U_DOUT_H
|
@ -0,0 +1,11 @@ |
|||||||
|
//
|
||||||
|
// Created by MightyPork on 2018/02/03.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "platform.h" |
||||||
|
#include "unit_base.h" |
||||||
|
#include "unit_fcap.h" |
||||||
|
|
||||||
|
#define FCAP_INTERNAL |
||||||
|
#include "_fcap_internal.h" |
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue