Compare commits
46 Commits
bad-double
...
master
@ -0,0 +1,373 @@ |
||||
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. |
@ -0,0 +1,13 @@ |
||||
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. |
@ -0,0 +1,192 @@ |
||||
//
|
||||
// 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
|
@ -0,0 +1,23 @@ |
||||
//
|
||||
// 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
|
@ -0,0 +1,105 @@ |
||||
//
|
||||
// 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
|
||||
} |
@ -0,0 +1,20 @@ |
||||
//
|
||||
// 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
|
@ -0,0 +1,73 @@ |
||||
//
|
||||
// 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 |
||||
} |
@ -0,0 +1,20 @@ |
||||
//
|
||||
// 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
|
@ -0,0 +1,156 @@ |
||||
//
|
||||
// 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); |
||||
} |
||||
} |
@ -0,0 +1,37 @@ |
||||
//
|
||||
// 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
|
@ -0,0 +1,551 @@ |
||||
//
|
||||
// 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 |
@ -0,0 +1,163 @@ |
||||
//
|
||||
// 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
|
@ -0,0 +1,64 @@ |
||||
//
|
||||
// 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
|
@ -0,0 +1,41 @@ |
||||
// |
||||
// 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 |
@ -0,0 +1,28 @@ |
||||
#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 |
@ -0,0 +1,41 @@ |
||||
|
||||
#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 |
@ -0,0 +1,194 @@ |
||||
|
||||
#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 |
@ -0,0 +1,46 @@ |
||||
#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.
@ -1,129 +0,0 @@ |
||||
//
|
||||
// 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; |
||||
} |
@ -1,18 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,59 +0,0 @@ |
||||
//
|
||||
// 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; |
||||
|
||||
// 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); |
||||
|
||||
// Free memory
|
||||
free_ck(unit->data); |
||||
} |
@ -1,60 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,223 +0,0 @@ |
||||
//
|
||||
// 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; |
||||
} |
@ -1,84 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,129 +0,0 @@ |
||||
//
|
||||
// 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; |
||||
} |
||||
|
||||
|
@ -1,83 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,68 +0,0 @@ |
||||
//
|
||||
// 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_s(iw, "parasitic", str_yn(priv->parasitic)); |
||||
} |
@ -1,248 +0,0 @@ |
||||
//
|
||||
// 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_tickHandler(Unit *unit) |
||||
{ |
||||
struct priv *priv = unit->data; |
||||
if(!priv->busy) { |
||||
dbg("ow tick should be disabled now!"); |
||||
return; |
||||
} |
||||
|
||||
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) { |
||||
unit->tick_interval = 0; |
||||
unit->_tick_cnt = 0; |
||||
|
||||
Job j = { |
||||
.unit = unit, |
||||
.data1 = 0, // failure
|
||||
.cb = OW_TimerRespCb, |
||||
}; |
||||
scheduleJob(&j); |
||||
} |
||||
} |
||||
|
||||
return; |
||||
halt_ok: |
||||
unit->tick_interval = 0; |
||||
unit->_tick_cnt = 0; |
||||
|
||||
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
|
||||
unit->_tick_cnt = 0; |
||||
unit->tick_interval = 750; |
||||
if (priv->parasitic) { |
||||
unit->tick_interval = 750; |
||||
} else { |
||||
unit->tick_interval = 10; |
||||
} |
||||
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, |
||||
.updateTick = OW_tickHandler, |
||||
}; |
@ -1,79 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,653 +0,0 @@ |
||||
//
|
||||
// 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; |
||||
} |
@ -1,264 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
@ -1,158 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,106 +0,0 @@ |
||||
//
|
||||
// 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_s(iw, "channels", cfg_pinmask_encode(priv->cfg.channels, unit_tmp512, true)); |
||||
|
||||
iw_cmt_newline(iw); |
||||
iw_comment(iw, "Sampling time (0-7)"); |
||||
iw_entry_d(iw, "sample_time", priv->cfg.sample_time); |
||||
|
||||
iw_comment(iw, "Sampling frequency (Hz)"); |
||||
iw_entry_d(iw, "frequency", 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_d(iw, "buffer_size", 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_d(iw, "avg_factor", priv->cfg.averaging_factor); |
||||
} |
||||
|
@ -1,405 +0,0 @@ |
||||
//
|
||||
// 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, |
||||
}; |
@ -1,15 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,71 +0,0 @@ |
||||
//
|
||||
// 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; |
||||
} |
@ -1,80 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
||||
} |
@ -1,127 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
@ -1,65 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,118 +0,0 @@ |
||||
//
|
||||
// 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_s(iw, "pins", cfg_pinmask_encode(priv->pins, unit_tmp512, 0)); |
||||
|
||||
iw_comment(iw, "Pins with pull-up"); |
||||
iw_entry_s(iw, "pull-up", cfg_pinmask_encode(priv->pullup, unit_tmp512, 0)); |
||||
|
||||
iw_comment(iw, "Pins with pull-down"); |
||||
iw_entry_s(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_s(iw, "trig-rise", cfg_pinmask_encode(priv->trig_rise, unit_tmp512, 0)); |
||||
iw_entry_s(iw, "trig-fall", cfg_pinmask_encode(priv->trig_fall, unit_tmp512, 0)); |
||||
|
||||
iw_comment(iw, "Trigger pins auto-armed by default"); |
||||
iw_entry_s(iw, "auto-trigger", cfg_pinmask_encode(priv->def_auto, unit_tmp512, 0)); |
||||
|
||||
iw_comment(iw, "Triggers hold-off time (ms)"); |
||||
iw_entry_d(iw, "hold-off", priv->trig_holdoff); |
||||
|
||||
#if PLAT_NO_FLOATING_INPUTS |
||||
iw_comment(iw, "NOTE: Pins use pull-up by default.\r\n"); |
||||
#endif |
||||
} |
||||
|
@ -1,94 +0,0 @@ |
||||
//
|
||||
// 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, |
||||
}; |
@ -1,42 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,73 +0,0 @@ |
||||
//
|
||||
// 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; |
||||
} |
@ -1,71 +0,0 @@ |
||||
//
|
||||
// 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); |
||||
} |
@ -1,49 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,82 +0,0 @@ |
||||
//
|
||||
// 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_s(iw, "pins", cfg_pinmask_encode(priv->pins, unit_tmp512, 0)); |
||||
|
||||
iw_comment(iw, "Initially high pins"); |
||||
iw_entry_s(iw, "initial", cfg_pinmask_encode(priv->initial, unit_tmp512, 0)); |
||||
|
||||
iw_comment(iw, "Open-drain pins"); |
||||
iw_entry_s(iw, "open-drain", cfg_pinmask_encode(priv->open_drain, unit_tmp512, 0)); |
||||
} |
@ -1,58 +0,0 @@ |
||||
//
|
||||
// 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, |
||||
}; |
@ -1,59 +0,0 @@ |
||||
//
|
||||
// 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
|
@ -1,11 +0,0 @@ |
||||
//
|
||||
// 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" |
||||
|
@ -1,462 +0,0 @@ |
||||
//
|
||||
// Created by MightyPork on 2018/02/20.
|
||||
//
|
||||
|
||||
#include <stm32f072xb.h> |
||||
#include "platform.h" |
||||
|
||||
#define FCAP_INTERNAL |
||||
#include "_fcap_internal.h" |
||||
|
||||
static void UFCAP_StopMeasurement(Unit *unit); |
||||
static void UFCAP_ConfigureForIndirectCapture(Unit *unit); |
||||
static void UFCAP_ConfigureForDirectCapture(Unit *unit, uint16_t msec); |
||||
static void UFCAP_ConfigureForFreeCapture(Unit *unit); |
||||
|
||||
uint32_t UFCAP_GetFreeCounterValue(Unit *unit) |
||||
{ |
||||
struct priv * const priv = unit->data; |
||||
TIM_TypeDef * const TIMx = priv->TIMx; |
||||
return TIMx->CNT; |
||||
} |
||||
|
||||
uint32_t UFCAP_FreeCounterClear(Unit *unit) |
||||
{ |
||||
struct priv * const priv = unit->data; |
||||
TIM_TypeDef * const TIMx = priv->TIMx; |
||||
|
||||
// this isn't perfect, we can miss one clock
|
||||
// but it's probably the best we can do here ...
|
||||
vPortEnterCritical(); |
||||
uint32_t val = TIMx->CNT; |
||||
TIMx->CNT = 0; |
||||
vPortExitCritical(); |
||||
|
||||
return val; |
||||
} |
||||
|
||||
static void UFCAP_IndirectBurstReportJob(Job *job) |
||||
{ |
||||
Unit *unit = job->unit; |
||||
struct priv * const priv = unit->data; |
||||
|
||||
uint8_t buf[20]; |
||||
PayloadBuilder pb = pb_start(buf, 20, NULL); |
||||
|
||||
pb_u16(&pb, PLAT_AHB_MHZ); |
||||
pb_u16(&pb, priv->ind_burst.n_count); |
||||
pb_u64(&pb, priv->ind_burst.period_acu); |
||||
pb_u64(&pb, priv->ind_burst.ontime_acu); |
||||
|
||||
assert_param(pb.ok); |
||||
|
||||
com_respond_pb(priv->request_id, MSG_SUCCESS, &pb); |
||||
|
||||
// timer is already stopped, now in OPMODE_BUSY
|
||||
priv->opmode = OPMODE_IDLE; |
||||
} |
||||
|
||||
static void UFCAP_SinglePulseReportJob(Job *job) |
||||
{ |
||||
Unit *unit = job->unit; |
||||
struct priv * const priv = unit->data; |
||||
|
||||
uint8_t buf[6]; |
||||
PayloadBuilder pb = pb_start(buf, 6, NULL); |
||||
|
||||
pb_u16(&pb, PLAT_AHB_MHZ); |
||||
pb_u32(&pb, job->data1); |
||||
assert_param(pb.ok); |
||||
|
||||
com_respond_pb(priv->request_id, MSG_SUCCESS, &pb); |
||||
|
||||
// timer is already stopped, now in OPMODE_BUSY
|
||||
priv->opmode = OPMODE_IDLE; |
||||
} |
||||
|
||||
/**
|
||||
* Count is passed in data1 |
||||
* @param job |
||||
*/ |
||||
static void UFCAP_DirectBurstReportJob(Job *job) |
||||
{ |
||||
Unit *unit = job->unit; |
||||
struct priv * const priv = unit->data; |
||||
|
||||
uint8_t buf[8]; |
||||
PayloadBuilder pb = pb_start(buf, 8, NULL); |
||||
|
||||
pb_u8(&pb, priv->direct_presc); |
||||
pb_u16(&pb, priv->dir_burst.msec); |
||||
pb_u32(&pb, job->data1); |
||||
|
||||
assert_param(pb.ok); |
||||
|
||||
com_respond_pb(priv->request_id, MSG_SUCCESS, &pb); |
||||
|
||||
// timer is already stopped, now in OPMODE_BUSY
|
||||
priv->opmode = OPMODE_IDLE; |
||||
} |
||||
|
||||
void UFCAP_TIMxHandler(void *arg) |
||||
{ |
||||
Unit *unit = arg; |
||||
assert_param(unit); |
||||
struct priv * const priv = unit->data; |
||||
assert_param(priv); |
||||
|
||||
TIM_TypeDef * const TIMx = priv->TIMx; |
||||
|
||||
if (priv->opmode == OPMODE_INDIRECT_CONT) { |
||||
if (LL_TIM_IsActiveFlag_CC1(TIMx)) { |
||||
if (priv->n_skip > 0) { |
||||
priv->n_skip--; |
||||
} else { |
||||
priv->ind_cont.last_period = LL_TIM_IC_GetCaptureCH1(TIMx); |
||||
priv->ind_cont.last_ontime = priv->ind_cont.ontime; |
||||
} |
||||
LL_TIM_ClearFlag_CC1(TIMx); |
||||
LL_TIM_ClearFlag_CC1OVR(TIMx); |
||||
} |
||||
|
||||
if (LL_TIM_IsActiveFlag_CC2(TIMx)) { |
||||
priv->ind_cont.ontime = LL_TIM_IC_GetCaptureCH2(TIMx); |
||||
LL_TIM_ClearFlag_CC2(TIMx); |
||||
LL_TIM_ClearFlag_CC2OVR(TIMx); |
||||
} |
||||
} |
||||
else if (priv->opmode == OPMODE_SINGLE_PULSE) { |
||||
if (LL_TIM_IsActiveFlag_CC2(TIMx)) { |
||||
// single pulse - does not wait for the second edge
|
||||
uint32_t len = LL_TIM_IC_GetCaptureCH2(TIMx); |
||||
|
||||
priv->opmode = OPMODE_BUSY; |
||||
UFCAP_StopMeasurement(unit); |
||||
|
||||
Job j = { |
||||
.cb = UFCAP_SinglePulseReportJob, |
||||
.unit = unit, |
||||
.data1 = len, |
||||
}; |
||||
scheduleJob(&j); |
||||
} |
||||
} |
||||
else if (priv->opmode == OPMODE_INDIRECT_BURST) { |
||||
if (LL_TIM_IsActiveFlag_CC1(TIMx)) { |
||||
const uint32_t period = LL_TIM_IC_GetCaptureCH1(TIMx); |
||||
const uint32_t ontime = priv->ind_burst.ontime; |
||||
|
||||
if (priv->n_skip > 0) { |
||||
priv->n_skip--; |
||||
} else { |
||||
priv->ind_burst.ontime_acu += ontime; |
||||
priv->ind_burst.period_acu += period; |
||||
if (++priv->ind_burst.n_count == priv->ind_burst.n_target) { |
||||
priv->opmode = OPMODE_BUSY; |
||||
UFCAP_StopMeasurement(unit); |
||||
|
||||
Job j = { |
||||
.cb = UFCAP_IndirectBurstReportJob, |
||||
.unit = unit, |
||||
}; |
||||
scheduleJob(&j); |
||||
} |
||||
} |
||||
|
||||
LL_TIM_ClearFlag_CC1(TIMx); |
||||
LL_TIM_ClearFlag_CC1OVR(TIMx); |
||||
} |
||||
|
||||
if (LL_TIM_IsActiveFlag_CC2(TIMx)) { |
||||
priv->ind_burst.ontime = LL_TIM_IC_GetCaptureCH2(TIMx); |
||||
LL_TIM_ClearFlag_CC2(TIMx); |
||||
LL_TIM_ClearFlag_CC2OVR(TIMx); |
||||
} |
||||
} |
||||
else if (priv->opmode == OPMODE_IDLE) { |
||||
// clear everything - in idle it would cycle in the handler forever
|
||||
TIMx->SR = 0; |
||||
} |
||||
else { |
||||
trap("Unhandled fcap TIMx irq"); |
||||
} |
||||
} |
||||
|
||||
void UFCAP_TIMyHandler(void *arg) |
||||
{ |
||||
Unit *unit = arg; |
||||
assert_param(unit); |
||||
struct priv *const priv = unit->data; |
||||
assert_param(priv); |
||||
|
||||
TIM_TypeDef * const TIMx = priv->TIMx; |
||||
TIM_TypeDef * const TIMy = priv->TIMy; |
||||
uint32_t cnt = TIMx->CNT; // TIMx should be stopped now
|
||||
|
||||
// dbg("> TIMy Handler, TIMx cntr is %d", cnt);
|
||||
priv->dir_cont.last_count = cnt; |
||||
|
||||
if (priv->opmode == OPMODE_DIRECT_CONT) { |
||||
LL_TIM_DisableCounter(TIMx); |
||||
LL_TIM_DisableCounter(TIMy); |
||||
LL_TIM_SetCounter(TIMx, 0); |
||||
LL_TIM_SetCounter(TIMy, 0); |
||||
LL_TIM_EnableCounter(TIMy); // next loop
|
||||
LL_TIM_EnableCounter(TIMx); |
||||
} |
||||
else if (priv->opmode == OPMODE_DIRECT_BURST) { |
||||
priv->opmode = OPMODE_BUSY; |
||||
UFCAP_StopMeasurement(unit); |
||||
|
||||
Job j = { |
||||
.cb = UFCAP_DirectBurstReportJob, |
||||
.unit = unit, |
||||
.data1 = cnt, |
||||
}; |
||||
scheduleJob(&j); |
||||
} |
||||
else if (priv->opmode == OPMODE_IDLE) { |
||||
// clear everything - in idle it would cycle in the handler forever
|
||||
TIMy->SR = 0; |
||||
} |
||||
else { |
||||
trap("Unhandled fcap TIMy irq"); |
||||
} |
||||
|
||||
LL_TIM_ClearFlag_UPDATE(TIMy); |
||||
} |
||||
|
||||
static void UFCAP_ClearTimerConfig(Unit *unit) |
||||
{ |
||||
struct priv * const priv = unit->data; |
||||
TIM_TypeDef * const TIMx = priv->TIMx; |
||||
|
||||
// CLEAR CURRENT STATE, STOP
|
||||
UFCAP_StopMeasurement(unit); |
||||
|
||||
// CONFIGURE TIMER BASIC PARAMS
|
||||
LL_TIM_SetPrescaler(TIMx, 0); |
||||
LL_TIM_SetAutoReload(TIMx, 0xFFFFFFFF); |
||||
LL_TIM_EnableARRPreload(TIMx); |
||||
LL_TIM_GenerateEvent_UPDATE(TIMx); |
||||
} |
||||
|
||||
/**
|
||||
* Reset all timer registers |
||||
* |
||||
* @param unit |
||||
*/ |
||||
static void UFCAP_StopMeasurement(Unit *unit) |
||||
{ |
||||
struct priv * const priv = unit->data; |
||||
|
||||
LL_TIM_DeInit(priv->TIMx); // clear all flags and settings
|
||||
LL_TIM_DeInit(priv->TIMy); // clear all flags and settings
|
||||
} |
||||
|
||||
/**
|
||||
* Switch the FCAP module opmode |
||||
* |
||||
* @param unit |
||||
* @param opmode |
||||
*/ |
||||
void UFCAP_SwitchMode(Unit *unit, enum fcap_opmode opmode) |
||||
{ |
||||
struct priv * const priv = unit->data; |
||||
|
||||
if (opmode == priv->opmode) return; |
||||
|
||||
priv->opmode = opmode; |
||||
|
||||
switch (opmode) { |
||||
case OPMODE_IDLE: |
||||
// XXX maybe we should report the abort to the PC-side listener
|
||||
UFCAP_StopMeasurement(unit); |
||||
break; |
||||
|
||||
case OPMODE_INDIRECT_CONT: |
||||
priv->ind_cont.last_ontime = 0; |
||||
priv->ind_cont.last_period = 0; |
||||
priv->ind_cont.ontime = 0; |
||||
priv->n_skip = 1; // discard the first cycle (will be incomplete)
|
||||
UFCAP_ConfigureForIndirectCapture(unit); // is also stopped and restarted
|
||||
break; |
||||
|
||||
case OPMODE_INDIRECT_BURST: |
||||
priv->ind_burst.ontime = 0; |
||||
priv->ind_burst.n_count = 0; |
||||
priv->ind_burst.period_acu = 0; |
||||
priv->ind_burst.ontime_acu = 0; |
||||
priv->n_skip = 1; // discard the first cycle (will be incomplete)
|
||||
UFCAP_ConfigureForIndirectCapture(unit); // is also stopped and restarted
|
||||
break; |
||||
|
||||
case OPMODE_SINGLE_PULSE: |
||||
priv->n_skip = 0; |
||||
UFCAP_ConfigureForIndirectCapture(unit); // is also stopped and restarted
|
||||
break; |
||||
|
||||
case OPMODE_DIRECT_CONT: |
||||
// msec is set by caller
|
||||
priv->dir_cont.last_count = 0; |
||||
priv->n_skip = 1; // discard the first cycle (will be incomplete)
|
||||
UFCAP_ConfigureForDirectCapture(unit, priv->direct_msec); |
||||
break; |
||||
|
||||
case OPMODE_DIRECT_BURST: |
||||
// msec is set by caller
|
||||
priv->n_skip = 0; // no skip here (if there was any)
|
||||
UFCAP_ConfigureForDirectCapture(unit, (uint16_t) priv->dir_burst.msec); |
||||
break; |
||||
|
||||
case OPMODE_FREE_COUNTER: |
||||
UFCAP_ConfigureForFreeCapture(unit); |
||||
break; |
||||
|
||||
default: |
||||
trap("Unhandled opmode %d", (int)opmode); |
||||
} |
||||
} |
||||
|
||||
/**
|
||||
* Configure peripherals for an indirect capture (PWM measurement) - continuous or burst |
||||
* @param unit |
||||
*/ |
||||
static void UFCAP_ConfigureForIndirectCapture(Unit *unit) |
||||
{ |
||||
struct priv * const priv = unit->data; |
||||
TIM_TypeDef * const TIMx = priv->TIMx; |
||||
const uint32_t ll_ch_a = priv->ll_ch_a; |
||||
const uint32_t ll_ch_b = priv->ll_ch_b; |
||||
|
||||
UFCAP_ClearTimerConfig(unit); |
||||
|
||||
// Enable channels and select mapping to TIx signals
|
||||
|
||||
// A - will be used to measure period
|
||||
// B - will be used to measure the duty cycle
|
||||
|
||||
// _________ ______
|
||||
// _______| |________________|
|
||||
// A B A
|
||||
// irq irq,cap irq
|
||||
// reset
|
||||
|
||||
// B irq may be used if we want to measure a pulse width
|
||||
|
||||
// Normally TI1 = CH1, TI2 = CH2.
|
||||
// It's possible to select the other channel, which we use to connect both TIx to the shame CHx.
|
||||
LL_TIM_IC_SetActiveInput(TIMx, ll_ch_a, priv->a_direct ? LL_TIM_ACTIVEINPUT_DIRECTTI : LL_TIM_ACTIVEINPUT_INDIRECTTI); |
||||
LL_TIM_IC_SetActiveInput(TIMx, ll_ch_b, priv->a_direct ? LL_TIM_ACTIVEINPUT_INDIRECTTI : LL_TIM_ACTIVEINPUT_DIRECTTI); |
||||
LL_TIM_IC_SetPolarity(TIMx, ll_ch_a, priv->active_level ? LL_TIM_IC_POLARITY_RISING : LL_TIM_IC_POLARITY_FALLING); |
||||
LL_TIM_IC_SetPolarity(TIMx, ll_ch_b, priv->active_level ? LL_TIM_IC_POLARITY_FALLING : LL_TIM_IC_POLARITY_RISING); |
||||
|
||||
if (priv->dfilter > 15) priv->dfilter = 15; |
||||
uint32_t filter = LL_TIM_IC_FILTERS[priv->dfilter]; |
||||
LL_TIM_IC_SetFilter(TIMx, ll_ch_a, filter); |
||||
LL_TIM_IC_SetFilter(TIMx, ll_ch_b, filter); |
||||
|
||||
LL_TIM_CC_EnableChannel(TIMx, ll_ch_a | ll_ch_b); |
||||
|
||||
LL_TIM_SetSlaveMode(TIMx, LL_TIM_SLAVEMODE_RESET); |
||||
LL_TIM_SetTriggerInput(TIMx, LL_TIM_TS_TI1FP1); // Use Filtered Input 1 (TI1)
|
||||
|
||||
LL_TIM_EnableMasterSlaveMode(TIMx); |
||||
|
||||
LL_TIM_ClearFlag_CC1(TIMx); |
||||
LL_TIM_ClearFlag_CC1OVR(TIMx); |
||||
LL_TIM_ClearFlag_CC2(TIMx); |
||||
LL_TIM_ClearFlag_CC2OVR(TIMx); |
||||
|
||||
LL_TIM_EnableIT_CC1(TIMx); |
||||
LL_TIM_EnableIT_CC2(TIMx); |
||||
LL_TIM_EnableCounter(TIMx); |
||||
} |
||||
|
||||
|
||||
/**
|
||||
* Configure peripherals for an indirect capture (PWM measurement) - continuous or burst |
||||
* @param unit |
||||
*/ |
||||
static void UFCAP_ConfigureForDirectCapture(Unit *unit, uint16_t msec) |
||||
{ |
||||
struct priv * const priv = unit->data; |
||||
|
||||
// dbg("Configuring Direct capture...");
|
||||
|
||||
UFCAP_ClearTimerConfig(unit); |
||||
|
||||
{ |
||||
TIM_TypeDef *const TIMy = priv->TIMy; |
||||
assert_param(PLAT_AHB_MHZ<=65); |
||||
uint16_t presc = PLAT_AHB_MHZ*1000; |
||||
uint32_t count = msec+1; // it's one tick longer because we generate OCREF on the exact msec count - it must be at least 1 tick long
|
||||
|
||||
LL_TIM_SetPrescaler(TIMy, (uint32_t) (presc - 1)); |
||||
LL_TIM_SetAutoReload(TIMy, count - 1); |
||||
LL_TIM_EnableARRPreload(TIMy); |
||||
LL_TIM_GenerateEvent_UPDATE(TIMy); |
||||
LL_TIM_SetOnePulseMode(TIMy, LL_TIM_ONEPULSEMODE_SINGLE); |
||||
LL_TIM_OC_EnableFast(TIMy, LL_TIM_CHANNEL_CH1); |
||||
|
||||
// dbg("TIMy presc %d, count %d", (int) presc, (int) count);
|
||||
|
||||
LL_TIM_SetTriggerOutput(TIMy, LL_TIM_TRGO_OC1REF); |
||||
LL_TIM_OC_SetMode(TIMy, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_PWM1); // 1 until CC, then 0
|
||||
LL_TIM_OC_SetCompareCH1(TIMy, count-1); |
||||
LL_TIM_CC_EnableChannel(TIMy, LL_TIM_CHANNEL_CH1); // enable the output channel that produces a trigger
|
||||
|
||||
LL_TIM_ClearFlag_UPDATE(TIMy); |
||||
LL_TIM_EnableIT_UPDATE(TIMy); |
||||
} |
||||
|
||||
{ |
||||
// TIMx - the slave
|
||||
TIM_TypeDef *const TIMx = priv->TIMx; |
||||
LL_TIM_SetSlaveMode(TIMx, LL_TIM_SLAVEMODE_GATED); |
||||
LL_TIM_SetTriggerInput(TIMx, LL_TIM_TS_ITR3); // ITR3 is TIM14 which we use as TIMy
|
||||
LL_TIM_EnableMasterSlaveMode(TIMx); |
||||
|
||||
uint32_t presc = LL_TIM_ETR_PRESCALER_DIV1; |
||||
switch (priv->direct_presc) { |
||||
case 1: presc = LL_TIM_ETR_PRESCALER_DIV1; break; |
||||
case 2: presc = LL_TIM_ETR_PRESCALER_DIV2; break; |
||||
case 4: presc = LL_TIM_ETR_PRESCALER_DIV4; break; |
||||
case 8: presc = LL_TIM_ETR_PRESCALER_DIV8; break; |
||||
default: |
||||
priv->direct_presc = 1; // will be sent with the response
|
||||
} |
||||
|
||||
if (priv->dfilter > 15) priv->dfilter = 15; |
||||
uint32_t filter = LL_TIM_ETR_FILTERS[priv->dfilter]; |
||||
|
||||
LL_TIM_ConfigETR(TIMx, |
||||
priv->active_level ? LL_TIM_ETR_POLARITY_NONINVERTED : LL_TIM_ETR_POLARITY_INVERTED, |
||||
presc, |
||||
filter); |
||||
|
||||
LL_TIM_EnableExternalClock(TIMx); // TODO must check and deny this mode if the pin is not on CH1 = external trigger input
|
||||
|
||||
LL_TIM_SetCounter(TIMx, 0); |
||||
LL_TIM_EnableCounter(TIMx); |
||||
} |
||||
|
||||
LL_TIM_EnableCounter(priv->TIMy); // XXX this will start the first pulse (maybe)
|
||||
} |
||||
|
||||
|
||||
/**
|
||||
* Freerunning capture (counting pulses - geiger) |
||||
* @param unit |
||||
*/ |
||||
static void UFCAP_ConfigureForFreeCapture(Unit *unit) |
||||
{ |
||||
struct priv * const priv = unit->data; |
||||
|
||||
UFCAP_ClearTimerConfig(unit); |
||||
|
||||
TIM_TypeDef *const TIMx = priv->TIMx; |
||||
LL_TIM_EnableExternalClock(TIMx); |
||||
LL_TIM_SetCounter(TIMx, 0); |
||||
LL_TIM_EnableCounter(TIMx); |
||||
} |
@ -1,147 +0,0 @@ |
||||
//
|
||||
// Created by MightyPork on 2018/02/03.
|
||||
//
|
||||
|
||||
#include "platform.h" |
||||
#include "unit_base.h" |
||||
|
||||
#define FCAP_INTERNAL |
||||
#include "_fcap_internal.h" |
||||
|
||||
/** Allocate data structure and set defaults */ |
||||
error_t UFCAP_preInit(Unit *unit) |
||||
{ |
||||
struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv)); |
||||
if (priv == NULL) return E_OUT_OF_MEM; |
||||
|
||||
priv->conf.signal_pname = 'A'; |
||||
priv->conf.signal_pnum = 0; |
||||
|
||||
priv->conf.active_level = 1; |
||||
priv->conf.direct_presc = 1; |
||||
priv->conf.dfilter = 0; |
||||
priv->conf.direct_msec = 1000; |
||||
priv->conf.startmode = OPMODE_IDLE; |
||||
|
||||
return E_SUCCESS; |
||||
} |
||||
|
||||
/** Finalize unit set-up */ |
||||
error_t UFCAP_init(Unit *unit) |
||||
{ |
||||
bool suc = true; |
||||
struct priv *priv = unit->data; |
||||
|
||||
// ---- Resolve what to configure ----
|
||||
|
||||
TIM_TypeDef * const TIMx = TIM2; |
||||
Resource timRsc = R_TIM2; |
||||
|
||||
TIM_TypeDef * const TIMy = TIM14; |
||||
Resource tim2Rsc = R_TIM14; |
||||
|
||||
uint32_t ll_ch_a = 0; |
||||
uint32_t ll_ch_b = 0; |
||||
|
||||
switch (priv->conf.signal_pname) { |
||||
case 'A': |
||||
switch (priv->conf.signal_pnum) { |
||||
case 5: |
||||
case 15: |
||||
case 0: ll_ch_a = LL_TIM_CHANNEL_CH1; break; |
||||
case 1: ll_ch_a = LL_TIM_CHANNEL_CH2; break; |
||||
default: |
||||
dbg("Bad signal pin!"); |
||||
return E_BAD_CONFIG; |
||||
} |
||||
break; |
||||
case 'B': |
||||
switch (priv->conf.signal_pnum) { |
||||
case 3: ll_ch_a = LL_TIM_CHANNEL_CH2; break; |
||||
default: |
||||
dbg("Bad signal pin!"); |
||||
return E_BAD_CONFIG; |
||||
} |
||||
break; |
||||
|
||||
default: |
||||
dbg("Bad signal pin port!"); |
||||
return E_BAD_CONFIG; |
||||
} |
||||
const uint32_t ll_timpin_af = LL_GPIO_AF_2; |
||||
|
||||
bool a_direct = true; |
||||
switch (ll_ch_a) { |
||||
case LL_TIM_CHANNEL_CH1: |
||||
ll_ch_b = LL_TIM_CHANNEL_CH2; |
||||
break; |
||||
|
||||
case LL_TIM_CHANNEL_CH2: |
||||
ll_ch_b = LL_TIM_CHANNEL_CH1; |
||||
a_direct = false; |
||||
break; |
||||
} |
||||
|
||||
// ---- CLAIM ----
|
||||
|
||||
TRY(rsc_claim_pin(unit, priv->conf.signal_pname, priv->conf.signal_pnum)); |
||||
TRY(rsc_claim(unit, timRsc)); |
||||
TRY(rsc_claim(unit, tim2Rsc)); |
||||
|
||||
// ---- INIT ----
|
||||
assert_param(ll_ch_a != ll_ch_b); |
||||
|
||||
priv->TIMx = TIMx; |
||||
priv->TIMy = TIMy; |
||||
priv->ll_ch_a = ll_ch_a; |
||||
priv->ll_ch_b = ll_ch_b; |
||||
priv->a_direct = a_direct; |
||||
|
||||
// Load defaults
|
||||
priv->active_level = priv->conf.active_level; |
||||
priv->direct_presc = priv->conf.direct_presc; |
||||
priv->dfilter = priv->conf.dfilter; |
||||
priv->direct_msec = priv->conf.direct_msec; |
||||
priv->opmode = priv->conf.startmode; |
||||
|
||||
TRY(hw_configure_gpio_af(priv->conf.signal_pname, priv->conf.signal_pnum, ll_timpin_af)); |
||||
|
||||
GPIO_TypeDef *gpio = hw_port2periph(priv->conf.signal_pname, &suc); |
||||
uint32_t ll_pin = hw_pin2ll(priv->conf.signal_pnum, &suc); |
||||
LL_GPIO_SetPinPull(gpio, ll_pin, LL_GPIO_PULL_DOWN); // XXX change to pull-up if the polarity is inverted
|
||||
|
||||
hw_periph_clock_enable(TIMx); |
||||
hw_periph_clock_enable(TIMy); |
||||
irqd_attach(TIMx, UFCAP_TIMxHandler, unit); |
||||
irqd_attach(TIMy, UFCAP_TIMyHandler, unit); |
||||
|
||||
UFCAP_SwitchMode(unit, priv->opmode); // switch to the default opmode
|
||||
|
||||
return E_SUCCESS; |
||||
} |
||||
|
||||
/** Tear down the unit */ |
||||
void UFCAP_deInit(Unit *unit) |
||||
{ |
||||
struct priv *priv = unit->data; |
||||
|
||||
// de-init peripherals
|
||||
if (unit->status == E_SUCCESS ) { |
||||
UFCAP_SwitchMode(unit, OPMODE_IDLE); |
||||
|
||||
TIM_TypeDef *TIMx = priv->TIMx; |
||||
TIM_TypeDef *TIMy = priv->TIMy; |
||||
LL_TIM_DeInit(TIMx); |
||||
LL_TIM_DeInit(TIMy); |
||||
irqd_detach(TIMx, UFCAP_TIMxHandler); |
||||
irqd_detach(TIMy, UFCAP_TIMyHandler); |
||||
hw_periph_clock_disable(TIMx); |
||||
hw_periph_clock_disable(TIMy); |
||||
} |
||||
|
||||
// Release all resources, deinit pins
|
||||
rsc_teardown(unit); |
||||
|
||||
// Free memory
|
||||
free_ck(unit->data); |
||||
} |
@ -1,117 +0,0 @@ |
||||
//
|
||||
// Created by MightyPork on 2018/02/03.
|
||||
//
|
||||
|
||||
#ifndef GEX_F072_FCAP_INTERNAL_H |
||||
#define GEX_F072_FCAP_INTERNAL_H |
||||
|
||||
#ifndef FCAP_INTERNAL |
||||
#error bad include! |
||||
#endif |
||||
|
||||
#include "unit_base.h" |
||||
|
||||
enum fcap_opmode { |
||||
OPMODE_IDLE = 0, |
||||
OPMODE_BUSY = 1, // used after capture is done, before it's reported
|
||||
OPMODE_INDIRECT_CONT = 2, |
||||
OPMODE_INDIRECT_BURST = 3, // averaging
|
||||
OPMODE_DIRECT_CONT = 4, |
||||
OPMODE_DIRECT_BURST = 5, |
||||
OPMODE_FREE_COUNTER = 6, |
||||
OPMODE_SINGLE_PULSE = 7, |
||||
}; |
||||
|
||||
/** Private data structure */ |
||||
struct priv { |
||||
// settings
|
||||
struct { |
||||
char signal_pname; // the input pin - one of TIM2 channels
|
||||
uint8_t signal_pnum; |
||||
|
||||
bool active_level; |
||||
uint8_t direct_presc; |
||||
uint8_t dfilter; |
||||
uint16_t direct_msec; |
||||
|
||||
enum fcap_opmode startmode; |
||||
} conf; |
||||
|
||||
// internal state
|
||||
TIM_TypeDef *TIMx; |
||||
TIM_TypeDef *TIMy; // used as a timebase source for TIMx in direct mode
|
||||
uint32_t ll_ch_b; |
||||
uint32_t ll_ch_a; |
||||
bool a_direct; |
||||
|
||||
enum fcap_opmode opmode; |
||||
|
||||
TF_ID request_id; |
||||
uint8_t n_skip; //!< Periods to skip before starting the real capture
|
||||
|
||||
bool active_level; // in PWM mode, the first part that is measured. (if 1, HHHLLL, else LLLHHH). In direct mode, clock polarity
|
||||
uint8_t direct_presc; |
||||
uint16_t direct_msec; |
||||
uint8_t dfilter; |
||||
|
||||
union { |
||||
struct { |
||||
uint32_t ontime; // length of the captured positive pulse in the current interval
|
||||
uint32_t last_period; //!< length of the captured interval between two rising edges
|
||||
uint32_t last_ontime; //!< length of the last captured ontime
|
||||
} ind_cont; |
||||
|
||||
struct { |
||||
uint32_t ontime; // length of the captured positive pulse in the current interval
|
||||
uint64_t period_acu; //!< length of the captured interval between two rising edges, sum
|
||||
uint64_t ontime_acu; //!< length of the last captured ontime, sum
|
||||
uint16_t n_count; //!< Periods captured
|
||||
uint16_t n_target; //!< Periods captured - requested count
|
||||
} ind_burst; |
||||
|
||||
struct { |
||||
uint32_t last_count; //!< Pulse count in the last capture window
|
||||
} dir_cont; |
||||
|
||||
struct { |
||||
uint16_t msec; // capture window length (used in the report callback) - different from the cont time, which is a semi-persistent config
|
||||
} dir_burst; |
||||
}; |
||||
}; |
||||
|
||||
/** Allocate data structure and set defaults */ |
||||
error_t UFCAP_preInit(Unit *unit); |
||||
|
||||
/** Load from a binary buffer stored in Flash */ |
||||
void UFCAP_loadBinary(Unit *unit, PayloadParser *pp); |
||||
|
||||
/** Write to a binary buffer for storing in Flash */ |
||||
void UFCAP_writeBinary(Unit *unit, PayloadBuilder *pb); |
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** Parse a key-value pair from the INI file */ |
||||
error_t UFCAP_loadIni(Unit *unit, const char *key, const char *value); |
||||
|
||||
/** Generate INI file section for the unit */ |
||||
void UFCAP_writeIni(Unit *unit, IniWriter *iw); |
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** Finalize unit set-up */ |
||||
error_t UFCAP_init(Unit *unit); |
||||
|
||||
/** Tear down the unit */ |
||||
void UFCAP_deInit(Unit *unit); |
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
void UFCAP_SwitchMode(Unit *unit, enum fcap_opmode opmode); |
||||
|
||||
void UFCAP_TIMxHandler(void *arg); |
||||
void UFCAP_TIMyHandler(void *arg); |
||||
|
||||
uint32_t UFCAP_GetFreeCounterValue(Unit *unit); |
||||
uint32_t UFCAP_FreeCounterClear(Unit *unit); |
||||
|
||||
#endif //GEX_F072_FCAP_INTERNAL_H
|
@ -1,114 +0,0 @@ |
||||
//
|
||||
// Created by MightyPork on 2018/02/03.
|
||||
//
|
||||
|
||||
#include "platform.h" |
||||
#include "unit_base.h" |
||||
|
||||
#define FCAP_INTERNAL |
||||
#include "_fcap_internal.h" |
||||
|
||||
/** Load from a binary buffer stored in Flash */ |
||||
void UFCAP_loadBinary(Unit *unit, PayloadParser *pp) |
||||
{ |
||||
struct priv *priv = unit->data; |
||||
|
||||
uint8_t version = pp_u8(pp); |
||||
(void)version; |
||||
|
||||
priv->conf.signal_pname = pp_char(pp); |
||||
priv->conf.signal_pnum = pp_u8(pp); |
||||
priv->conf.active_level = pp_bool(pp); |
||||
priv->conf.dfilter = pp_u8(pp); |
||||
priv->conf.direct_presc = pp_u8(pp); |
||||
priv->conf.direct_msec = pp_u16(pp); |
||||
priv->conf.startmode = (enum fcap_opmode) pp_u8(pp); |
||||
} |
||||
|
||||
/** Write to a binary buffer for storing in Flash */ |
||||
void UFCAP_writeBinary(Unit *unit, PayloadBuilder *pb) |
||||
{ |
||||
struct priv *priv = unit->data; |
||||
|
||||
pb_u8(pb, 0); // version
|
||||
|
||||
pb_char(pb, priv->conf.signal_pname); |
||||
pb_u8(pb, priv->conf.signal_pnum); |
||||
pb_bool(pb, priv->conf.active_level); |
||||
pb_u8(pb, priv->conf.dfilter); |
||||
pb_u8(pb, priv->conf.direct_presc); |
||||
pb_u16(pb, priv->conf.direct_msec); |
||||
pb_u8(pb, priv->conf.startmode); |
||||
} |
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** Parse a key-value pair from the INI file */ |
||||
error_t UFCAP_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->conf.signal_pname, &priv->conf.signal_pnum); |
||||
} |
||||
else if (streq(key, "active-level")) { |
||||
priv->conf.active_level = cfg_bool_parse(value, &suc); |
||||
} |
||||
else if (streq(key, "input-filter")) { |
||||
priv->conf.dfilter = cfg_u8_parse(value, &suc); |
||||
} |
||||
else if (streq(key, "direct-presc")) { |
||||
priv->conf.direct_presc = cfg_u8_parse(value, &suc); |
||||
} |
||||
else if (streq(key, "direct-time")) { |
||||
priv->conf.direct_msec = cfg_u16_parse(value, &suc); |
||||
} |
||||
else if (streq(key, "initial-mode")) { |
||||
priv->conf.startmode = (enum fcap_opmode) cfg_enum4_parse(value, |
||||
"N", OPMODE_IDLE, |
||||
"I", OPMODE_INDIRECT_CONT, |
||||
"D", OPMODE_DIRECT_CONT, |
||||
"F", OPMODE_FREE_COUNTER, |
||||
&suc); |
||||
} |
||||
else{ |
||||
return E_BAD_KEY; |
||||
} |
||||
|
||||
if (!suc) return E_BAD_VALUE; |
||||
return E_SUCCESS; |
||||
} |
||||
|
||||
/** Generate INI file section for the unit */ |
||||
void UFCAP_writeIni(Unit *unit, IniWriter *iw) |
||||
{ |
||||
struct priv *priv = unit->data; |
||||
|
||||
iw_comment(iw, "Signal input pin - one of:"); |
||||
iw_comment(iw, " Full support: A0, A5, A15"); |
||||
iw_comment(iw, " Indirect only: A1, B3"); |
||||
iw_entry(iw, "pin", "%c%d", priv->conf.signal_pname, priv->conf.signal_pnum); |
||||
iw_cmt_newline(iw); |
||||
|
||||
iw_comment(iw, "Active level or edge (0-low,falling; 1-high,rising)"); |
||||
iw_entry_d(iw, "active-level", priv->conf.active_level); |
||||
|
||||
iw_comment(iw, "Input filtering (0-15)"); |
||||
iw_entry_d(iw, "input-filter", priv->conf.dfilter); |
||||
|
||||
iw_comment(iw, "Pulse counter pre-divider (1,2,4,8)"); |
||||
iw_entry_d(iw, "direct-presc", priv->conf.direct_presc); |
||||
|
||||
iw_comment(iw, "Pulse counting interval (ms)"); |
||||
iw_entry_d(iw, "direct-time", priv->conf.direct_msec); |
||||
iw_cmt_newline(iw); |
||||
|
||||
iw_comment(iw, "Mode on startup: N-none, I-indirect, D-direct, F-free count"); |
||||
iw_entry_s(iw, "initial-mode", cfg_enum4_encode(priv->conf.startmode, |
||||
OPMODE_IDLE, "N", |
||||
OPMODE_INDIRECT_CONT, "I", |
||||
OPMODE_DIRECT_CONT, "D", |
||||
OPMODE_FREE_COUNTER, "F")); |
||||
} |
||||
|
@ -1,322 +0,0 @@ |
||||
//
|
||||
// Created by MightyPork on 2017/11/25.
|
||||
//
|
||||
|
||||
#include "unit_base.h" |
||||
#include "unit_fcap.h" |
||||
|
||||
#define FCAP_INTERNAL |
||||
#include "_fcap_internal.h" |
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
enum FcapCmd_ { |
||||
CMD_STOP = 0, |
||||
|
||||
// Measuring a waveform
|
||||
CMD_INDIRECT_CONT_START = 1, // keep measuring, read on demand
|
||||
CMD_INDIRECT_BURST_START = 2, // wait and reply
|
||||
|
||||
// Counting pulses
|
||||
CMD_DIRECT_CONT_START = 3, // keep measuring, read on demand
|
||||
CMD_DIRECT_BURST_START = 4, // wait and reply
|
||||
CMD_FREECOUNT_START = 5, // keep counting pulses until stopped, read on reply
|
||||
|
||||
CMD_MEASURE_SINGLE_PULSE = 6, // measure the first incoming pulse of the right polarity. NOTE: can glitch if the signal starts in the active level
|
||||
CMD_FREECOUNT_CLEAR = 7, // clear the free counter, return last value
|
||||
|
||||
// Results readout for continuous modes
|
||||
CMD_INDIRECT_CONT_READ = 10, |
||||
CMD_DIRECT_CONT_READ = 11, |
||||
CMD_FREECOUNT_READ = 12, |
||||
|
||||
// configs
|
||||
CMD_SET_POLARITY = 20, |
||||
CMD_SET_DIR_PRESC = 21, |
||||
CMD_SET_INPUT_FILTER = 22, |
||||
CMD_SET_DIR_MSEC = 23, |
||||
|
||||
// go back to the configured settings
|
||||
CMD_RESTORE_DEFAULTS = 30, |
||||
}; |
||||
|
||||
/** Handle a request message */ |
||||
static error_t UFCAP_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp) |
||||
{ |
||||
uint8_t presc; |
||||
uint16_t msec; |
||||
|
||||
struct priv *priv = unit->data; |
||||
PayloadBuilder pb = pb_start(unit_tmp512, UNIT_TMP_LEN, NULL); |
||||
const char* msg_denied_on_pin = "Not available on the selected pin!"; |
||||
|
||||
switch (command) { |
||||
/**
|
||||
* Stop any ongoing measurement and return to base state. |
||||
*/ |
||||
case CMD_STOP: |
||||
UFCAP_SwitchMode(unit, OPMODE_IDLE); |
||||
return E_SUCCESS; |
||||
|
||||
// ----------------------- CONFIG --------------------------
|
||||
|
||||
/**
|
||||
* Set the active polarity, or triggering edge (for direct) |
||||
* |
||||
* pld: pol:u8 (0,1) |
||||
*/ |
||||
case CMD_SET_POLARITY: |
||||
{ |
||||
priv->active_level = pp_bool(pp); |
||||
} |
||||
return E_SUCCESS; |
||||
|
||||
/**
|
||||
* Set the direct measurement prescaller 1,2,4,8 |
||||
* |
||||
* pld: presc:u8 |
||||
*/ |
||||
case CMD_SET_DIR_PRESC: |
||||
{ |
||||
presc = pp_u8(pp); |
||||
if (presc != 1 && presc != 2 && presc != 4 && presc != 8) return E_BAD_VALUE; |
||||
priv->direct_presc = presc; |
||||
} |
||||
return E_SUCCESS; |
||||
|
||||
/**
|
||||
* Set the input filter for all modes |
||||
* |
||||
* pld: filter:u8 (0-15) |
||||
*/ |
||||
case CMD_SET_INPUT_FILTER: |
||||
{ |
||||
uint8_t input_filter = pp_u8(pp); |
||||
if (input_filter >= 16) return E_BAD_VALUE; |
||||
priv->dfilter = input_filter; |
||||
} |
||||
return E_SUCCESS; |
||||
|
||||
/**
|
||||
* Set the direct sampling time. |
||||
* |
||||
* pld: msec:u16 |
||||
*/ |
||||
case CMD_SET_DIR_MSEC: |
||||
{ |
||||
msec = pp_u16(pp); |
||||
priv->direct_msec = msec; |
||||
} |
||||
return E_SUCCESS; |
||||
|
||||
/**
|
||||
* Reset all SET* settings to their default values, stop any ongoing measure. |
||||
*/ |
||||
case CMD_RESTORE_DEFAULTS: |
||||
UFCAP_SwitchMode(unit, OPMODE_IDLE); |
||||
|
||||
priv->active_level = priv->conf.active_level; |
||||
priv->direct_presc = priv->conf.direct_presc; |
||||
priv->direct_msec = priv->conf.direct_msec; |
||||
priv->dfilter = priv->conf.dfilter; |
||||
return E_SUCCESS; |
||||
|
||||
// ------------------ COMMANDS ------------------------
|
||||
|
||||
/**
|
||||
* Start indirect continuous measurement. |
||||
*/ |
||||
case CMD_INDIRECT_CONT_START: |
||||
if (priv->opmode == OPMODE_INDIRECT_CONT) return E_SUCCESS; // no-op
|
||||
if (priv->opmode != OPMODE_IDLE) return E_BUSY; |
||||
|
||||
UFCAP_SwitchMode(unit, OPMODE_INDIRECT_CONT); |
||||
return E_SUCCESS; |
||||
|
||||
/**
|
||||
* Start a continuous direct measurement (counting pulses in fixed time intervals) |
||||
* |
||||
* - meas_time_ms 0 = no change |
||||
* - prescaller 0 = no change |
||||
* |
||||
* pld: meas_time_ms:u16, prescaller:u8 |
||||
* - prescaller is 1,2,4,8; 0 = no change |
||||
*/ |
||||
case CMD_DIRECT_CONT_START: |
||||
if (!priv->a_direct) { |
||||
// This works only if we use the ETR pin. TIM2 shares CH1 with ETR.
|
||||
// If CH2 is selected as input, ETR is not available.
|
||||
com_respond_str(MSG_ERROR, frame_id, msg_denied_on_pin); |
||||
return E_FAILURE; |
||||
} |
||||
if (priv->opmode == OPMODE_DIRECT_CONT) return E_SUCCESS; // no-op
|
||||
if (priv->opmode != OPMODE_IDLE) return E_BUSY; |
||||
|
||||
msec = pp_u16(pp); |
||||
presc = pp_u8(pp); |
||||
if (msec != 0) priv->direct_msec = msec; |
||||
if (presc != 0) priv->direct_presc = presc; |
||||
|
||||
UFCAP_SwitchMode(unit, OPMODE_DIRECT_CONT); |
||||
return E_SUCCESS; |
||||
|
||||
/**
|
||||
* Start a burst of direct measurements with averaging. |
||||
* The measurement is performed on N consecutive pulses. |
||||
* |
||||
* pld: count:u16 |
||||
* |
||||
* resp: core_mhz:u16, count:u16, period_sum:u64, ontime_sum:u64 |
||||
*/ |
||||
case CMD_INDIRECT_BURST_START: |
||||
if (priv->opmode != OPMODE_IDLE) return E_BAD_MODE; |
||||
|
||||
priv->ind_burst.n_target = pp_u16(pp); |
||||
priv->request_id = frame_id; |
||||
UFCAP_SwitchMode(unit, OPMODE_INDIRECT_BURST); |
||||
return E_SUCCESS; |
||||
|
||||
/**
|
||||
* Start a single direct measurement of the given length (pulses in time period) |
||||
* If 'prescaller' is not 0, it is changed via the param field. |
||||
* |
||||
* pld: meas_time_ms:u16, prescaller:u8 |
||||
* - prescaller is 1,2,4,8; 0 = no change |
||||
* |
||||
* resp: prescaller:u8, meas_time_ms:u16, pulse_count:u32 |
||||
*/ |
||||
case CMD_DIRECT_BURST_START: |
||||
if (priv->opmode != OPMODE_IDLE) return E_BAD_MODE; |
||||
|
||||
priv->dir_burst.msec = pp_u16(pp); |
||||
|
||||
presc = pp_u8(pp); |
||||
if (presc != 0) priv->direct_presc = presc; |
||||
|
||||
priv->request_id = frame_id; |
||||
UFCAP_SwitchMode(unit, OPMODE_DIRECT_BURST); |
||||
return E_SUCCESS; |
||||
|
||||
/**
|
||||
* Measure a single pulse length of the given polarity. |
||||
* Measures time from a rising to a falling edge (or falling to rising, if polarity is 0) |
||||
* |
||||
* resp: core_mhz:u16, ontime:u32 |
||||
*/ |
||||
case CMD_MEASURE_SINGLE_PULSE: |
||||
if (priv->opmode != OPMODE_IDLE) return E_BAD_MODE; |
||||
priv->request_id = frame_id; |
||||
UFCAP_SwitchMode(unit, OPMODE_SINGLE_PULSE); |
||||
return E_SUCCESS; |
||||
|
||||
/**
|
||||
* Start a free-running pulse counter. |
||||
* |
||||
* pld: prescaller:u8 |
||||
* - prescaller is 1,2,4,8; 0 = no change |
||||
*/ |
||||
case CMD_FREECOUNT_START: |
||||
if (priv->opmode != OPMODE_IDLE) return E_BAD_MODE; |
||||
|
||||
presc = pp_u8(pp); |
||||
if (presc != 0) priv->direct_presc = presc; |
||||
|
||||
UFCAP_SwitchMode(unit, OPMODE_FREE_COUNTER); |
||||
return E_SUCCESS; |
||||
|
||||
/**
|
||||
* Reset the free-running pulse counter. |
||||
* |
||||
* resp: last_val:u32 |
||||
*/ |
||||
case CMD_FREECOUNT_CLEAR: |
||||
if (priv->opmode != OPMODE_FREE_COUNTER) { |
||||
return E_BAD_MODE; |
||||
} |
||||
|
||||
pb_u32(&pb, UFCAP_FreeCounterClear(unit)); |
||||
com_respond_pb(frame_id, MSG_SUCCESS, &pb); |
||||
return E_SUCCESS; |
||||
|
||||
// ------------------ READING ---------------------
|
||||
|
||||
/**
|
||||
* Read the most recent pulse measurement during continuous indirect measure. |
||||
* |
||||
* resp: core_mhz:u16, period:u32, ontime:u32 |
||||
*/ |
||||
case CMD_INDIRECT_CONT_READ: |
||||
if (priv->opmode != OPMODE_INDIRECT_CONT) { |
||||
return E_BAD_MODE; |
||||
} |
||||
if (priv->ind_cont.last_period == 0) { |
||||
return E_BUSY; |
||||
} |
||||
|
||||
pb_u16(&pb, PLAT_AHB_MHZ); |
||||
pb_u32(&pb, priv->ind_cont.last_period); |
||||
pb_u32(&pb, priv->ind_cont.last_ontime); |
||||
|
||||
com_respond_pb(frame_id, MSG_SUCCESS, &pb); |
||||
return E_SUCCESS; |
||||
|
||||
/**
|
||||
* Read the most recent result of a continuous direct measurement. |
||||
* |
||||
* resp: prescaller:u8, meas_time_ms:u16, pulse_count:u32 |
||||
*/ |
||||
case CMD_DIRECT_CONT_READ: |
||||
if (!priv->a_direct) { // see above
|
||||
com_respond_str(MSG_ERROR, frame_id, msg_denied_on_pin); |
||||
return E_FAILURE; |
||||
} |
||||
if (priv->opmode != OPMODE_DIRECT_CONT) return E_BAD_MODE; |
||||
if (priv->dir_cont.last_count == 0) return E_BUSY; |
||||
|
||||
pb_u8(&pb, priv->direct_presc); |
||||
pb_u16(&pb, priv->direct_msec); |
||||
pb_u32(&pb, priv->dir_cont.last_count); |
||||
|
||||
com_respond_pb(frame_id, MSG_SUCCESS, &pb); |
||||
return E_SUCCESS; |
||||
|
||||
/**
|
||||
* Read the current value of the free-running pulse counter. |
||||
* |
||||
* The timing may have a significant jitter, this function is practically useful only for |
||||
* slow pulse sources (like a geiger counter, item counting etc) |
||||
* |
||||
* resp: count:u32 |
||||
*/ |
||||
case CMD_FREECOUNT_READ: |
||||
if (priv->opmode != OPMODE_FREE_COUNTER) { |
||||
return E_BAD_MODE; |
||||
} |
||||
|
||||
pb_u32(&pb, UFCAP_GetFreeCounterValue(unit)); |
||||
com_respond_pb(frame_id, MSG_SUCCESS, &pb); |
||||
return E_SUCCESS; |
||||
|
||||
default: |
||||
return E_UNKNOWN_COMMAND; |
||||
} |
||||
} |
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** Frequency capture */ |
||||
const UnitDriver UNIT_FCAP = { |
||||
.name = "FCAP", |
||||
.description = "Frequency and pulse measurement", |
||||
// Settings
|
||||
.preInit = UFCAP_preInit, |
||||
.cfgLoadBinary = UFCAP_loadBinary, |
||||
.cfgWriteBinary = UFCAP_writeBinary, |
||||
.cfgLoadIni = UFCAP_loadIni, |
||||
.cfgWriteIni = UFCAP_writeIni, |
||||
// Init
|
||||
.init = UFCAP_init, |
||||
.deInit = UFCAP_deInit, |
||||
// Function
|
||||
.handleRequest = UFCAP_handleRequest, |
||||
}; |
@ -1,16 +0,0 @@ |
||||
//
|
||||
// Created by MightyPork on 2017/11/25.
|
||||
//
|
||||
// Digital input unit; single or multiple pin read access on one port (A-F)
|
||||
//
|
||||
|
||||
#ifndef U_FCAP_H |
||||
#define U_FCAP_H |
||||
|
||||
#include "unit.h" |
||||
|
||||
extern const UnitDriver UNIT_FCAP; |
||||
|
||||
// UU_ prototypes
|
||||
|
||||
#endif //U_FCAP_H
|
@ -1,123 +0,0 @@ |
||||
//
|
||||
// Created by MightyPork on 2018/02/03.
|
||||
//
|
||||
|
||||
#include "platform.h" |
||||
#include "unit_base.h" |
||||
#include "unit_i2c.h" |
||||
|
||||
#define I2C_INTERNAL |
||||
#include "_i2c_internal.h" |
||||
|
||||
static void i2c_reset(struct priv *priv) |
||||
{ |
||||
LL_I2C_Disable(priv->periph); |
||||
HAL_Delay(1); |
||||
LL_I2C_Enable(priv->periph); |
||||
} |
||||
|
||||
static error_t i2c_wait_until_flag(struct priv *priv, uint32_t flag, bool stop_state) |
||||
{ |
||||
uint32_t t_start = HAL_GetTick(); |
||||
while (((priv->periph->ISR & flag)!=0) != stop_state) { |
||||
if (HAL_GetTick() - t_start > 10) { |
||||
i2c_reset(priv); |
||||
return E_HW_TIMEOUT; |
||||
} |
||||
} |
||||
return E_SUCCESS; |
||||
} |
||||
|
||||
error_t UU_I2C_Write(Unit *unit, uint16_t addr, const uint8_t *bytes, uint32_t bcount) |
||||
{ |
||||
CHECK_TYPE(unit, &UNIT_I2C); |
||||
|
||||
struct priv *priv = unit->data; |
||||
|
||||
uint8_t addrsize = (uint8_t) (((addr & 0x8000) == 0) ? 7 : 10); |
||||
addr &= 0x3FF; |
||||
uint32_t ll_addrsize = (addrsize == 7) ? LL_I2C_ADDRSLAVE_7BIT : LL_I2C_ADDRSLAVE_10BIT; |
||||
if (addrsize == 7) addr <<= 1; // 7-bit address must be shifted to left for LL to use it correctly
|
||||
|
||||
TRY(i2c_wait_until_flag(priv, I2C_ISR_BUSY, 0)); |
||||
|
||||
bool first = true; |
||||
while (bcount > 0) { |
||||
uint32_t len = bcount; |
||||
uint32_t chunk_remain = (uint8_t) ((len > 255) ? 255 : len); // if more than 255, first chunk is 255
|
||||
LL_I2C_HandleTransfer(priv->periph, addr, ll_addrsize, chunk_remain, |
||||
(len > 255) ? LL_I2C_MODE_RELOAD : LL_I2C_MODE_AUTOEND, // Autoend if this is the last chunk
|
||||
first ? LL_I2C_GENERATE_START_WRITE : LL_I2C_GENERATE_NOSTARTSTOP); // no start/stop condition if we're continuing
|
||||
first = false; |
||||
bcount -= chunk_remain; |
||||
|
||||
for (; chunk_remain > 0; chunk_remain--) { |
||||
TRY(i2c_wait_until_flag(priv, I2C_ISR_TXIS, 1)); |
||||
uint8_t byte = *bytes++; |
||||
LL_I2C_TransmitData8(priv->periph, byte); |
||||
} |
||||
} |
||||
|
||||
TRY(i2c_wait_until_flag(priv, I2C_ISR_STOPF, 1)); |
||||
LL_I2C_ClearFlag_STOP(priv->periph); |
||||
return E_SUCCESS; |
||||
} |
||||
|
||||
error_t UU_I2C_Read(Unit *unit, uint16_t addr, uint8_t *dest, uint32_t bcount) |
||||
{ |
||||
CHECK_TYPE(unit, &UNIT_I2C); |
||||
|
||||
struct priv *priv = unit->data; |
||||
|
||||
uint8_t addrsize = (uint8_t) (((addr & 0x8000) == 0) ? 7 : 10); |
||||
addr &= 0x3FF; |
||||
uint32_t ll_addrsize = (addrsize == 7) ? LL_I2C_ADDRSLAVE_7BIT : LL_I2C_ADDRSLAVE_10BIT; |
||||
if (addrsize == 7) addr <<= 1; // 7-bit address must be shifted to left for LL to use it correctly
|
||||
|
||||
TRY(i2c_wait_until_flag(priv, I2C_ISR_BUSY, 0)); |
||||
|
||||
bool first = true; |
||||
while (bcount > 0) { |
||||
if (!first) { |
||||
TRY(i2c_wait_until_flag(priv, I2C_ISR_TCR, 1)); |
||||
} |
||||
|
||||
uint8_t chunk_remain = (uint8_t) ((bcount > 255) ? 255 : bcount); // if more than 255, first chunk is 255
|
||||
LL_I2C_HandleTransfer(priv->periph, addr, ll_addrsize, chunk_remain, |
||||
(bcount > 255) ? LL_I2C_MODE_RELOAD : LL_I2C_MODE_AUTOEND, // Autoend if this is the last chunk
|
||||
first ? LL_I2C_GENERATE_START_READ : LL_I2C_GENERATE_NOSTARTSTOP); // no start/stop condition if we're continuing
|
||||
first = false; |
||||
bcount -= chunk_remain; |
||||
|
||||
for (; chunk_remain > 0; chunk_remain--) { |
||||
TRY(i2c_wait_until_flag(priv, I2C_ISR_RXNE, 1)); |
||||
|
||||
uint8_t byte = LL_I2C_ReceiveData8(priv->periph); |
||||
*dest++ = byte; |
||||
} |
||||
} |
||||
|
||||
TRY(i2c_wait_until_flag(priv, I2C_ISR_STOPF, 1)); |
||||
LL_I2C_ClearFlag_STOP(priv->periph); |
||||
return E_SUCCESS; |
||||
} |
||||
|
||||
error_t UU_I2C_ReadReg(Unit *unit, uint16_t addr, uint8_t regnum, uint8_t *dest, uint32_t width) |
||||
{ |
||||
TRY(UU_I2C_Write(unit, addr, ®num, 1)); |
||||
TRY(UU_I2C_Read(unit, addr, dest, width)); |
||||
return E_SUCCESS; |
||||
} |
||||
|
||||
error_t UU_I2C_WriteReg(Unit *unit, uint16_t addr, uint8_t regnum, const uint8_t *bytes, uint32_t width) |
||||
{ |
||||
CHECK_TYPE(unit, &UNIT_I2C); |
||||
|
||||
// we have to insert the address first - needs a buffer (XXX realistically the buffer needs 1-4 bytes + addr)
|
||||
PayloadBuilder pb = pb_start((uint8_t*)unit_tmp512, UNIT_TMP_LEN, NULL); |
||||
pb_u8(&pb, regnum); |
||||
pb_buf(&pb, bytes, width); |
||||
|
||||
TRY(UU_I2C_Write(unit, addr, (uint8_t *) unit_tmp512, pb_length(&pb))); |
||||
return E_SUCCESS; |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue