Compare commits

..

46 Commits

Author SHA1 Message Date
Ondřej Hruška 3d3d74f381
updated readme 6 years ago
Ondřej Hruška 20639b77a6
clean up for porting - move platform specific stuff into outer project 6 years ago
Ondřej Hruška b8909fca06
makefile correction 6 years ago
Ondřej Hruška 54b72a4bf1
makefile improvements for a faster build 6 years ago
Ondřej Hruška afee9ab6a3
fix build if debug uart is disabled 6 years ago
Ondřej Hruška a1e9cf2a57
Modularization, stage 1 (core side) - units moved to F072 6 years ago
Ondřej Hruška 482a67c5d2
bump version to 1.0.0 7 years ago
Ondřej Hruška 9703408307
implemented makefile hack for optional unit inclusion (needs integration in platform.c) 7 years ago
Ondřej Hruška b40825a75f
renumbered neopixel to make more sense in decadic 7 years ago
Ondřej Hruška 6bfa40ce9f
better neopixel commands for sparse writing 7 years ago
Ondřej Hruška 315da1fc82
Merge branch 'zero-port' 7 years ago
Ondřej Hruška 0bce53403f
gex zero support, also nrf remapping based on platform and optional disable 7 years ago
Ondřej Hruška 30d1762710
version 7 years ago
Ondřej Hruška 49c291dade
fix adc triggers frame format broken 7 years ago
Ondřej Hruška 02b0ceb139
adc EOS IRQ speedup with a lookup array, achievable up to 100kHz with short capture window 7 years ago
Ondřej Hruška c0846f0bb7
Fix incorrect ADC channel counting with 16,17. Also make averaging toggleable. 7 years ago
Ondřej Hruška 396501564a
Fixed DAC not correctly deinited causing INI crashes 7 years ago
Ondřej Hruška 041e4f5b70
version increment 7 years ago
Ondřej Hruška 4ede660471
Merge branch 'nordic' 7 years ago
Ondřej Hruška 73e134d0b6
cleaned up nordic config 7 years ago
Ondřej Hruška dc78c4da41
add default settings for nordic 7 years ago
Ondřej Hruška 2bad5d69e6
possibly improved the rx routine 7 years ago
Ondřej Hruška e4ae8e23fc
working nordic comm, tested. May need cleaning 7 years ago
Ondřej Hruška 2f4101a6e0
nordic fixes, still not working 7 years ago
Ondřej Hruška 5eec46c041
nordic implemented, need testing and fixes 7 years ago
Ondřej Hruška 9182c666ea
now it's possible to read and write SYSTEM.INI using bulk 7 years ago
Ondřej Hruška a66c453820
support for reading ADC calibration values 7 years ago
Ondřej Hruška de75f2d91c
use new GEX vid pid 7 years ago
Ondřej Hruška e667a2dd3a
fixed some wrongly named enum constants 7 years ago
Ondřej Hruška 9b1d084308
Merge branch 'uart-comm' 7 years ago
Ondřej Hruška 102d5f5a4c
implemented fallback to physical usart and basis for wireless support in the future 7 years ago
Ondřej Hruška 1797f179b4
version 0.1, added readme and license 7 years ago
Ondřej Hruška 218d26fb7b
fix a spi bulk read bug 7 years ago
Ondřej Hruška b9f82c8077
fix pb2 claimed on f072 as boot1 when boot1 is only a register flag on this chip 7 years ago
Ondřej Hruška d1f8804a93
fixes in lock button 7 years ago
Ondřej Hruška 2e2dab9244
fix unit registry wrongly parsing comma separated list 7 years ago
Ondřej Hruška e046be30b2
added MCO function for oscillator debugging 7 years ago
Ondřej Hruška f9200fcc7d
some fixes for cfg button 7 years ago
Ondřej Hruška ffec192a27
Merge branch 'dac' 7 years ago
Ondřej Hruška 9f40c6e4dd
DAC advanced features 7 years ago
Ondřej Hruška b2066a9010
basic static DAC 7 years ago
Ondřej Hruška 590d550a09
pulse generation for digital out 7 years ago
Ondřej Hruška 274f2be6e0
compat with 072hub 7 years ago
Ondřej Hruška 76d1cd3a24
Merge branch 'faster' 7 years ago
Ondřej Hruška 3654bf2206
various speed-ups in TF_WriteImpl and elsewhere 7 years ago
Ondřej Hruška 5687196bdd
small speed up using larger buffer and multipart Tx 7 years ago
  1. 35
      FreeRTOSConfig.h
  2. 373
      LICENSE.txt
  3. 13
      README.md
  4. 58
      TinyFrame/TF_Integration.c
  5. 10
      TinyFrame/TinyFrame.c
  6. 2
      TinyFrame/TinyFrame.h
  7. 12
      USB/README.TXT
  8. 10
      USB/STM32_USB_Device_Library/Class/MSC_CDC/usbd_msc_cdc.c
  9. 4
      USB/usbd_cdc_if.c
  10. 25
      USB/usbd_conf.c
  11. 2
      USB/usbd_conf.h
  12. 6
      USB/usbd_desc.c
  13. 2
      USB/usbd_storage_if.c
  14. 6
      comm/event_reports.h
  15. 192
      comm/iface_nordic.c
  16. 23
      comm/iface_nordic.h
  17. 105
      comm/iface_uart.c
  18. 20
      comm/iface_uart.h
  19. 73
      comm/iface_usb.c
  20. 20
      comm/iface_usb.h
  21. 156
      comm/interfaces.c
  22. 37
      comm/interfaces.h
  23. 55
      comm/messages.c
  24. 6
      comm/msg_bulkread.h
  25. 6
      comm/msg_bulkwrite.h
  26. 6
      comm/msg_responses.h
  27. 551
      comm/nrf.c
  28. 163
      comm/nrf.h
  29. 8
      framework/rsc_enum.h
  30. 11
      framework/settings.c
  31. 229
      framework/system_settings.c
  32. 17
      framework/system_settings.h
  33. 3
      framework/unit_registry.c
  34. 2
      freertos.c
  35. 340
      gex.mk
  36. 11
      gex_hooks.c
  37. 43
      platform/cfg_utils.c
  38. 18
      platform/cfg_utils.h
  39. 79
      platform/debug_uart.c
  40. 10
      platform/debug_uart.h
  41. 13
      platform/hw_utils.c
  42. 2
      platform/hw_utils.h
  43. 7
      platform/irq_dispatcher.c
  44. 6
      platform/irq_dispatcher.h
  45. 6
      platform/ll_extension.h
  46. 6
      platform/lock_jumper.c
  47. 2
      platform/lock_jumper.h
  48. 64
      platform/plat_config.h
  49. 242
      platform/platform.c
  50. 8
      platform/platform.h
  51. 27
      platform/timebase.c
  52. 28
      platform/timebase.h
  53. 6
      platform/watchdog.h
  54. 14
      tasks/task_main.c
  55. 6
      tasks/task_msg.h
  56. 41
      template/nrf_pins.h.txt
  57. 28
      template/old/FreeRTOSConfig.plat.txt
  58. 41
      template/old/debug_uart.h.txt
  59. 109
      template/old/plat_compat.h.txt
  60. 194
      template/old/plat_init.c.txt
  61. 46
      template/plat_compat.h.txt
  62. BIN
      template/template_unit.zip
  63. 129
      units/1wire/_ow_api.c
  64. 18
      units/1wire/_ow_commands.h
  65. 59
      units/1wire/_ow_init.c
  66. 60
      units/1wire/_ow_internal.h
  67. 223
      units/1wire/_ow_low_level.c
  68. 84
      units/1wire/_ow_low_level.h
  69. 129
      units/1wire/_ow_search.c
  70. 83
      units/1wire/_ow_search.h
  71. 68
      units/1wire/_ow_settings.c
  72. 248
      units/1wire/unit_1wire.c
  73. 79
      units/1wire/unit_1wire.h
  74. 653
      units/adc/_adc_core.c
  75. 264
      units/adc/_adc_init.c
  76. 158
      units/adc/_adc_internal.h
  77. 106
      units/adc/_adc_settings.c
  78. 405
      units/adc/unit_adc.c
  79. 15
      units/adc/unit_adc.h
  80. 71
      units/digital_in/_din_api.c
  81. 80
      units/digital_in/_din_exti.c
  82. 127
      units/digital_in/_din_init.c
  83. 65
      units/digital_in/_din_internal.h
  84. 118
      units/digital_in/_din_settings.c
  85. 94
      units/digital_in/unit_din.c
  86. 42
      units/digital_in/unit_din.h
  87. 73
      units/digital_out/_dout_api.c
  88. 71
      units/digital_out/_dout_init.c
  89. 49
      units/digital_out/_dout_internal.h
  90. 82
      units/digital_out/_dout_settings.c
  91. 58
      units/digital_out/unit_dout.c
  92. 59
      units/digital_out/unit_dout.h
  93. 11
      units/fcap/_fcap_api.c
  94. 462
      units/fcap/_fcap_core.c
  95. 147
      units/fcap/_fcap_init.c
  96. 117
      units/fcap/_fcap_internal.h
  97. 114
      units/fcap/_fcap_settings.c
  98. 322
      units/fcap/unit_fcap.c
  99. 16
      units/fcap/unit_fcap.h
  100. 123
      units/i2c/_i2c_api.c
  101. Some files were not shown because too many files have changed in this diff Show More

@ -90,6 +90,7 @@
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
#include <stdint.h>
#include "main.h"
#include "plat_config.h"
#include "plat_compat.h"
extern uint32_t SystemCoreClock;
#endif
@ -109,7 +110,6 @@ extern uint32_t SystemCoreClock;
#define configQUEUE_REGISTRY_SIZE 0
#define configCHECK_FOR_STACK_OVERFLOW 2
#define configENABLE_BACKWARD_COMPATIBILITY 0
#define configUSE_COUNTING_SEMAPHORES 0
#define configUSE_TIMERS 0
#define configTIMER_TASK_PRIORITY TSK_TIMERS_PRIO // above normal
@ -135,34 +135,11 @@ to exclude the API function. */
///* Cortex-M specific definitions. */
#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(GEX_PLAT_F072_DISCOVERY)
// 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
// These are defined in plat_compat.h:
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY PLAT_FREERTOS_LOWEST_INTERRUPT_PRIORITY
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY PLAT_FREERTOS_MAX_SYSCALL_INTERRUPT_PRIORITY
#define configPRIO_BITS PLAT_FREERTOS_PRIO_BITS
#define configUSE_PORT_OPTIMISED_TASK_SELECTION PLAT_FREERTOS_USE_PORT_OPTIMISED_TASK_SELECTION
/* Interrupt priorities used by the kernel port layer itself. These are generic
to all Cortex-M ports, and do not rely on any particular library functions. */

@ -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.

@ -4,11 +4,17 @@
// TinyFrame integration
//
#include <USB/usb_device.h>
#include "platform.h"
#include "task_main.h"
#include "comm/messages.h"
#include "comm/interfaces.h"
#include "comm/iface_uart.h"
#include "comm/iface_nordic.h"
#include "comm/iface_usb.h"
#include "framework/system_settings.h"
#include "USB/usbd_cdc_if.h"
#include "USB/usb_device.h"
#include "TinyFrame.h"
extern osSemaphoreId semVcomTxReadyHandle;
@ -16,26 +22,20 @@ extern osMutexId mutTinyFrameTxHandle;
void TF_WriteImpl(TinyFrame *tf, const uint8_t *buff, uint32_t len)
{
(void) tf;
#define CHUNK 64 // same as TF_SENDBUF_LEN, so we should always have only one run of the loop
int32_t total = (int32_t) len;
while (total > 0) {
const int32_t mxStatus = osSemaphoreWait(semVcomTxReadyHandle, 100);
if (mxStatus != osOK) {
TF_Error("Tx stalled");
return;
if (gActiveComport == COMPORT_USB) {
iface_usb_transmit(buff, len);
}
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;
else if (gActiveComport == COMPORT_USART) {
iface_uart_transmit(buff, len);
}
#if SUPPORT_NRF
else if (gActiveComport == COMPORT_NORDIC) {
iface_nordic_transmit(buff, len);
}
#endif // SUPPORT_NRF
else {
// TODO other transports
trap("not implemented.");
}
}
@ -43,10 +43,20 @@ void TF_WriteImpl(TinyFrame *tf, const uint8_t *buff, uint32_t len)
bool TF_ClaimTx(TinyFrame *tf)
{
(void) tf;
// assert_param(osThreadGetId() != tskMainHandle);
assert_param(!inIRQ());
assert_param(osOK == osMutexWait(mutTinyFrameTxHandle, 5000));
assert_param(pdTRUE == xSemaphoreTake(mutTinyFrameTxHandle, 5000)); // trips the wd
// The last chunk from some previous frame may still be being transmitted,
// wait for it to finish (the semaphore is given in the CDC tx done handler)
if (pdTRUE != xSemaphoreTake(semVcomTxReadyHandle, 200)) {
TF_Error("Tx stalled in Claim");
// release the guarding mutex again
assert_param(pdTRUE == xSemaphoreGive(mutTinyFrameTxHandle));
return false;
}
return true;
}
@ -54,5 +64,7 @@ bool TF_ClaimTx(TinyFrame *tf)
void TF_ReleaseTx(TinyFrame *tf)
{
(void) tf;
assert_param(osOK == osMutexRelease(mutTinyFrameTxHandle));
assert_param(pdTRUE == xSemaphoreGive(mutTinyFrameTxHandle));
// the last payload is sent asynchronously
}

@ -870,7 +870,11 @@ static inline uint32_t _TF_FN TF_ComposeTail(uint8_t *outbuff, TF_CKSUM *cksum)
*/
static bool _TF_FN TF_SendFrame_Begin(TinyFrame *tf, TF_Msg *msg, TF_Listener listener, TF_TICKS timeout)
{
TF_TRY(TF_ClaimTx(tf));
bool suc = TF_ClaimTx(tf);
if (!suc) {
TF_Error("TF lock not free");
return false;
}
tf->tx_pos = (uint32_t) TF_ComposeHead(tf, tf->sendbuf, msg); // frame ID is incremented here if it's not a response
tf->tx_len = msg->len;
@ -1031,10 +1035,10 @@ bool _TF_FN TF_Query_Multipart(TinyFrame *tf, TF_Msg *msg, TF_Listener listener,
return TF_Query(tf, msg, listener, timeout);
}
void _TF_FN TF_Respond_Multipart(TinyFrame *tf, TF_Msg *msg)
bool _TF_FN TF_Respond_Multipart(TinyFrame *tf, TF_Msg *msg)
{
msg->data = NULL;
TF_Respond(tf, msg);
return TF_Respond(tf, msg);
}
void _TF_FN TF_Multipart_Payload(TinyFrame *tf, const uint8_t *buff, uint32_t length)

@ -369,7 +369,7 @@ bool TF_Query_Multipart(TinyFrame *tf, TF_Msg *msg, TF_Listener listener, TF_TIC
* TF_Respond() with multipart payload.
* msg.data is ignored and set to NULL
*/
void TF_Respond_Multipart(TinyFrame *tf, TF_Msg *msg);
bool TF_Respond_Multipart(TinyFrame *tf, TF_Msg *msg);
/**
* Send the payload for a started multipart frame. This can be called multiple times

@ -17,3 +17,15 @@ and processed by the message queue thread. This makes it possible to query hardw
also makes it possible to wait on a binary semaphore when sending data back to host. The
semaphore is set from the CDC TxComplete callback and taken by the TinyFrame write
function, serving as a form of flow control.
--------------------------------------------
COMM API:
GEX supports alternate command interfaces.
The active interface is set in the global variable gActiveComport
Due to special init procedures, the com_switch_transfer() function must be called to change it.
The TX function is defined in TF_Integration.c and any RX'd data is sent through rxQuePostMsg()

@ -25,8 +25,8 @@ __ALIGN_BEGIN uint8_t USBD_MSC_CDC_CfgFSDesc[USBD_MSC_CDC_CONFIG_DESC_SIZ] __AL
3, /* bNumInterfaces - 1 MSC + 2 composite CDC ACM */
0x01, /* bConfigurationValue: */
0, /* iConfiguration: - string descriptor */
0xC0, /* bmAttributes: - self powered */
150, /* MaxPower 300 mA */
0x80, /* bmAttributes: - bus powered */
250, /* MaxPower 500 mA */
/******************** Mass Storage interface ********************/
/*9*/ 0x09, /* bLength: Interface Descriptor size */
@ -63,7 +63,7 @@ __ALIGN_BEGIN uint8_t USBD_MSC_CDC_CfgFSDesc[USBD_MSC_CDC_CONFIG_DESC_SIZ] __AL
0x02, /* bInterfaceCount */
0x02, /* bFunctionClass */ // #36
0x02, /* bFunctionSubClass (ACM) */
0x01, /* bFunctionProtocol (AT-COMMANDS) */
0x01, /* bFunctionProtocol (AT-COMMANDS - we don't implement this but it's a good default) */
0x05, /* iFunction: string descriptor */
/********************** ACM interface **********************/
@ -131,7 +131,7 @@ __ALIGN_BEGIN uint8_t USBD_MSC_CDC_CfgFSDesc[USBD_MSC_CDC_CONFIG_DESC_SIZ] __AL
USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */
CDC_OUT_EP, /* bEndpointAddress */
0x02, /* bmAttributes: Bulk */
LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), /* wMaxPacketSize: TODO 8? */
LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), /* wMaxPacketSize */
HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),
0x00, /* bInterval: ignore for Bulk transfer */
@ -140,7 +140,7 @@ __ALIGN_BEGIN uint8_t USBD_MSC_CDC_CfgFSDesc[USBD_MSC_CDC_CONFIG_DESC_SIZ] __AL
USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */
CDC_IN_EP, /* bEndpointAddress */
0x02, /* bmAttributes: Bulk */
LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), /* wMaxPacketSize: TODO 16? */
LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), /* wMaxPacketSize */
HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),
0x00 /* bInterval: ignore for Bulk transfer */
};

@ -313,10 +313,8 @@ void USBD_CDC_TransmitDone(USBD_HandleTypeDef *pdev)
assert_param(semVcomTxReadyHandle != NULL);
assert_param(inIRQ());
if (uxSemaphoreGetCount(semVcomTxReadyHandle) == 1) return;
portBASE_TYPE taskWoken = pdFALSE;
assert_param(xSemaphoreGiveFromISR(semVcomTxReadyHandle, &taskWoken) == pdTRUE);
assert_param(pdTRUE == xSemaphoreGiveFromISR(semVcomTxReadyHandle, &taskWoken));
portYIELD_FROM_ISR(taskWoken);
}
/* USER CODE END PRIVATE_FUNCTIONS_IMPLEMENTATION */

@ -84,13 +84,14 @@ void HAL_PCD_MspInit(PCD_HandleTypeDef* pcdHandle)
__HAL_RCC_USB_CLK_ENABLE();
/* Peripheral interrupt init */
#if defined(GEX_PLAT_F103_BLUEPILL)
// TODO convert this to feature flags if possible - avoid tying to particular platforms
#if GEX_PLAT_F103
HAL_NVIC_SetPriority(USB_LP_CAN1_RX0_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn);
#elif defined(GEX_PLAT_F072_DISCOVERY)
#elif GEX_PLAT_F072
HAL_NVIC_SetPriority(USB_IRQn, 3, 0);
HAL_NVIC_EnableIRQ(USB_IRQn);
#elif defined(GEX_PLAT_F303_DISCOVERY)
#elif GEX_PLAT_F303
// Pins need to be configured here
/**USB GPIO Configuration
@ -149,11 +150,11 @@ void HAL_PCD_MspDeInit(PCD_HandleTypeDef* pcdHandle)
__HAL_RCC_USB_CLK_DISABLE();
/* Peripheral interrupt Deinit*/
#if defined(GEX_PLAT_F103_BLUEPILL)
#if GEX_PLAT_F103
HAL_NVIC_DisableIRQ(USB_LP_CAN1_RX0_IRQn);
#elif defined(GEX_PLAT_F072_DISCOVERY)
#elif GEX_PLAT_F072
HAL_NVIC_DisableIRQ(USB_IRQn);
#elif defined(GEX_PLAT_F303_DISCOVERY)
#elif GEX_PLAT_F303
HAL_NVIC_DisableIRQ(USB_LP_CAN_RX0_IRQn);
#else
#error "BAD PLATFORM"
@ -397,16 +398,8 @@ USBD_StatusTypeDef USBD_LL_Init (USBD_HandleTypeDef *pdev)
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , MSC_EPOUT_ADDR , PCD_SNG_BUF, ptr += 64); // 64
// CDC endpoints, EP2 two-way and EP3 in-only
uint32_t buf1addr, buf2addr;
buf1addr = (ptr += 64);
buf2addr = (ptr += 64);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_IN_EP , PCD_DBL_BUF, buf1addr | (buf2addr << 16)); // 64
buf1addr = (ptr += 64);
buf2addr = (ptr += 64);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_OUT_EP , PCD_DBL_BUF, buf1addr | (buf2addr << 16)); // 64
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_IN_EP , PCD_SNG_BUF, ptr += 64); // 64
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_OUT_EP , PCD_SNG_BUF, ptr += 64); // 64
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_CMD_EP , PCD_SNG_BUF, ptr += 16); // 16
(void)ptr;
#endif

@ -99,7 +99,7 @@
#define CDC_CUSTOM_EPS
#define CDC_IN_EP 0x82 /* EP1 for data IN */
#define CDC_OUT_EP 0x04 /* EP1 for data OUT */
#define CDC_OUT_EP 0x02 /* EP1 for data OUT */
#define CDC_CMD_EP 0x83 /* EP2 for CDC commands */
/** @defgroup USBD_Exported_Macros

@ -72,11 +72,11 @@
/** @defgroup USBD_DESC_Private_Defines
* @{
*/
#define USBD_VID 1155 // TODO choose a VID:PID (eg. via pid.codes - 0x1209)
#define USBD_PID_FS 22314
#define USBD_VID 0x1209
#define USBD_PID_FS 0x4c60
#define USBD_LANGID_NUM 1033
#define USBD_MANUFACTURER_STRING "MightyPork"
#define USBD_PRODUCT_STRING_FS "GEX"
#define USBD_PRODUCT_STRING_FS "GEX USB module"
#define USBD_VERSION_NUM 0x0001 // 0xMMmp
//#define USBD_SERIALNUMBER_STRING_FS "00000000001A"
//#define USBD_CONFIGURATION_STRING_FS "MSC Config"

@ -186,7 +186,7 @@ USBD_StorageTypeDef USBD_Storage_Interface_fops_FS =
int8_t STORAGE_Init_FS (uint8_t lun)
{
/* USER CODE BEGIN 2 */
dbg("Plug In");
dbg("MSC init request");
vfs_mngr_fs_enable(1);
return (USBD_OK);
/* USER CODE END 2 */

@ -2,8 +2,8 @@
// Created by MightyPork on 2018/01/27.
//
#ifndef GEX_F072_EVENT_REPORTS_H
#define GEX_F072_EVENT_REPORTS_H
#ifndef GEX_CORE_EVENT_REPORTS_H
#define GEX_CORE_EVENT_REPORTS_H
#ifndef GEX_MESSAGES_H
#error "Include messages.h instead!"
@ -31,4 +31,4 @@ void EventReport_Data(const uint8_t *buff, uint16_t len);
void EventReport_PB(PayloadBuilder *pb);
void EventReport_End(void);
#endif //GEX_F072_EVENT_REPORTS_H
#endif //GEX_CORE_EVENT_REPORTS_H

@ -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

@ -2,7 +2,6 @@
// Created by MightyPork on 2017/11/21.
//
#include <platform/status_led.h>
#include "platform.h"
#include "framework/settings.h"
#include "utils/ini_parser.h"
@ -11,6 +10,8 @@
#include "comm/messages.h"
#include "framework/system_settings.h"
#include "utils/malloc_safe.h"
#include "platform/status_led.h"
#include "interfaces.h"
static TinyFrame tf_;
TinyFrame *comm = &tf_;
@ -22,8 +23,9 @@ TinyFrame *comm = &tf_;
*/
static TF_Result lst_ping(TinyFrame *tf, TF_Msg *msg)
{
dbg("Ping!");
com_respond_snprintf(msg->frame_id, MSG_SUCCESS,
"GEX v%s on %s", GEX_VERSION, GEX_PLATFORM);
"GEX v%s on %s (%s)", GEX_VERSION, GEX_PLATFORM, COMPORT_NAMES[gActiveComport]);
return TF_STAY;
}
@ -58,42 +60,64 @@ static TF_Result lst_list_units(TinyFrame *tf, TF_Msg *msg)
// ---------------------------------------------------------------------------
/** Callback for bulk read of the settings file */
static void settings_bulkread_cb(BulkRead *bulk, uint32_t chunk, uint8_t *buffer)
/** Callback for bulk read of a settings file */
static void ini_bulkread_cb(BulkRead *bulk, uint32_t chunk, uint8_t *buffer)
{
// clean-up request
if (buffer == NULL) {
free_ck(bulk);
iw_end();
dbg("INI read complete.");
return;
}
if (bulk->offset == 0) iw_begin();
IniWriter iw = iw_init((char *)buffer, bulk->offset, chunk);
iw.tag = 1;
iw.tag = 1; // indicates this is read via the API (affects some comments)
uint8_t filenum = (uint8_t) (int) bulk->userdata;
if (filenum == 0) {
settings_build_units_ini(&iw);
}
else if (filenum == 1) {
settings_build_system_ini(&iw);
}
}
/**
* Listener: Export INI file via TF
* Listener: Export a file via TF
*/
static TF_Result lst_ini_export(TinyFrame *tf, TF_Msg *msg)
{
dbg("Bulk read INI file");
// dbg("Bulk read INI file");
BulkRead *bulk = malloc_ck(sizeof(BulkRead));
assert_param(bulk != NULL);
uint8_t filenum = 0;
// if any payload, the first byte defines the file to read
// 0 - units
// 1 - system
// (this is optional for backwards compatibility)
if (msg->len > 0) {
filenum = msg->data[0];
}
bulk->frame_id = msg->frame_id;
bulk->read = ini_bulkread_cb;
bulk->userdata = (void *) (int)filenum;
if (filenum == 0) {
bulk->len = iw_measure_total(settings_build_units_ini, 1);
bulk->read = settings_bulkread_cb;
bulk->userdata = NULL;
}
else if (filenum == 1) {
bulk->len = iw_measure_total(settings_build_system_ini, 1);
}
bulkread_start(tf, bulk);
Indicator_Effect(STATUS_DISK_BUSY_SHORT);
return TF_STAY;
}
@ -114,7 +138,7 @@ static void settings_bulkwrite_cb(BulkWrite *bulk, const uint8_t *chunk, uint32_
if (bulk->offset > 0) {
settings_load_ini_end();
dbg("INI write complete");
// dbg("INI write complete");
} else {
dbg("INI write failed");
}
@ -131,7 +155,7 @@ static void settings_bulkwrite_cb(BulkWrite *bulk, const uint8_t *chunk, uint32_
*/
static TF_Result lst_ini_import(TinyFrame *tf, TF_Msg *msg)
{
dbg("Bulk write INI file");
// dbg("Bulk write INI file");
BulkWrite *bulk = malloc_ck(sizeof(BulkWrite));
assert_param(bulk);
@ -142,7 +166,7 @@ static TF_Result lst_ini_import(TinyFrame *tf, TF_Msg *msg)
PayloadParser pp = pp_start(msg->data, msg->len, NULL);
uint32_t len = pp_u32(&pp);
if (!pp.ok) {
com_respond_error(msg->frame_id, E_PROTOCOL_BREACH);
com_respond_error(msg->frame_id, E_MALFORMED_COMMAND);
goto done;
}
@ -151,11 +175,8 @@ static TF_Result lst_ini_import(TinyFrame *tf, TF_Msg *msg)
settings_load_ini_begin();
ini_parse_begin(iniparser_cb, NULL);
bulkwrite_start(tf, bulk);
Indicator_Effect(STATUS_DISK_BUSY);
done:
return TF_STAY;
}

@ -4,8 +4,8 @@
// Bulk read (providing data for bulk read by the PC)
//
#ifndef GEX_F072_MSG_BULKREAD_H
#define GEX_F072_MSG_BULKREAD_H
#ifndef GEX_CORE_MSG_BULKREAD_H
#define GEX_CORE_MSG_BULKREAD_H
#ifndef GEX_MESSAGES_H
#error "Include messages.h instead!"
@ -45,4 +45,4 @@ struct bulk_read {
void bulkread_start(TinyFrame *tf, BulkRead *bulk);
#endif //GEX_F072_MSG_BULKREAD_H
#endif //GEX_CORE_MSG_BULKREAD_H

@ -4,8 +4,8 @@
// Bulk write (receiving bulk write from the PC)
//
#ifndef GEX_F072_MSG_BULKWRITE_H
#define GEX_F072_MSG_BULKWRITE_H
#ifndef GEX_CORE_MSG_BULKWRITE_H
#define GEX_CORE_MSG_BULKWRITE_H
#ifndef GEX_MESSAGES_H
#error "Include messages.h instead!"
@ -44,4 +44,4 @@ struct bulk_write {
void bulkwrite_start(TinyFrame *tf, BulkWrite *bulk);
#endif //GEX_F072_MSG_BULKWRITE_H
#endif //GEX_CORE_MSG_BULKWRITE_H

@ -4,8 +4,8 @@
// Routines for sending TinyFrame responses.
//
#ifndef GEX_F072_MSG_RESPONSES_H
#define GEX_F072_MSG_RESPONSES_H
#ifndef GEX_CORE_MSG_RESPONSES_H
#define GEX_CORE_MSG_RESPONSES_H
#ifndef GEX_MESSAGES_H
#error "Include messages.h instead!"
@ -109,4 +109,4 @@ void com_respond_u16(TF_ID frame_id, uint16_t d);
*/
void com_respond_u32(TF_ID frame_id, uint32_t d);
#endif //GEX_F072_MSG_RESPONSES_H
#endif //GEX_CORE_MSG_RESPONSES_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

@ -4,15 +4,15 @@
// Enum of all defined resources
//
#ifndef GEX_F072_RSC_ENUM_H
#define GEX_F072_RSC_ENUM_H
#ifndef GEX_CORE_RSC_ENUM_H
#define GEX_CORE_RSC_ENUM_H
// X macro: Resource name,
#define XX_RESOURCES \
X(SPI1) X(SPI2) X(SPI3) \
X(I2C1) X(I2C2) X(I2C3) \
X(ADC1) X(ADC2) X(ADC3) X(ADC4) \
X(DAC1) X(DAC2) \
X(DAC1) \
X(TSC) \
X(USART1) X(USART2) X(USART3) X(USART4) X(USART5) X(USART6) \
X(TIM1) X(TIM2) X(TIM3) X(TIM4) X(TIM5) \
@ -132,4 +132,4 @@ static inline void rscmap_free(ResourceMap *rscmap, Resource rsc)
#define RSC_CLAIM(rscmap, rsc) rscmap_claim(&rscmap, (rsc))
#define RSC_FREE(rscmap, rsc) rscmap_free(&rscmap, (rsc))
#endif //GEX_F072_RSC_ENUM_H
#endif //GEX_CORE_RSC_ENUM_H

@ -2,6 +2,7 @@
// Created by MightyPork on 2017/11/26.
//
#include <comm/interfaces.h>
#include "platform.h"
#include "utils/hexdump.h"
#include "settings.h"
@ -9,6 +10,7 @@
#include "system_settings.h"
#include "utils/str_utils.h"
#include "unit_base.h"
#include "platform/debug_uart.h"
#include "utils/avrlibc.h"
// pre-declarations
@ -108,7 +110,7 @@ void settings_save(void)
erase.Banks = FLASH_BANK_1; // TODO ?????
#endif
#if defined(GEX_PLAT_F407_DISCOVERY)
#if GEX_PLAT_F407
// specialty for F4 with too much flash
erase.NbSectors = 1;
erase.Sector = SETTINGS_FLASH_SECTOR;
@ -120,6 +122,7 @@ void settings_save(void)
erase.PageAddress = SETTINGS_FLASH_ADDR;
erase.TypeErase = FLASH_TYPEERASE_PAGES;
#endif
uint32_t pgerror = 0;
hst = HAL_FLASHEx_Erase(&erase, &pgerror);
assert_param(pgerror == 0xFFFFFFFFU);
@ -300,7 +303,6 @@ void settings_load_ini_begin(void)
SystemSettings.loading_inifile = 0;
}
void settings_load_ini_key(const char *restrict section, const char *restrict key, const char *restrict value)
{
// dbg("[%s] %s = %s", section, key, value);
@ -312,6 +314,7 @@ void settings_load_ini_key(const char *restrict section, const char *restrict ke
if (streq(section, "SYSTEM")) {
if (SystemSettings.loading_inifile == 0) {
SystemSettings.loading_inifile = 'S';
systemsettings_begin_load();
systemsettings_loadDefaults();
}
@ -356,4 +359,8 @@ void settings_load_ini_end(void)
bool suc = ureg_finalize_all_init();
if (!suc) dbg("Some units failed to init!!");
}
if (SystemSettings.loading_inifile == 'S') {
systemsettings_finalize_load();
}
}

@ -7,6 +7,15 @@
#include "utils/str_utils.h"
#include "platform/lock_jumper.h"
#include "cfg_utils.h"
#include "resources.h"
#include "unit_base.h"
#include "platform/debug_uart.h"
#include "comm/interfaces.h"
static void systemsettings_mco_teardown(void);
static void systemsettings_mco_init(void);
/** Init/deinit debug uart */
static void systemsettings_debug_uart_init_deinit(void);
struct system_settings SystemSettings;
@ -15,6 +24,24 @@ void systemsettings_loadDefaults(void)
{
SystemSettings.visible_vcom = true;
SystemSettings.ini_comments = true;
SystemSettings.enable_mco = false;
SystemSettings.mco_prediv = 7;
SystemSettings.use_comm_uart = false; // TODO configure those based on compile flags for a particular platform
SystemSettings.use_comm_lora = false;
SystemSettings.use_comm_nordic = false;
SystemSettings.comm_uart_baud = 115200; // TODO
// just a demo, user must change this
SystemSettings.nrf_network[0] = 0x12;
SystemSettings.nrf_network[2] = 0x09;
SystemSettings.nrf_network[3] = 0x4c;
SystemSettings.nrf_network[4] = 0x61;
SystemSettings.nrf_address = 1;
SystemSettings.nrf_channel = 76;
SystemSettings.enable_debug_uart = true;
}
/** Load defaults and init flags */
@ -31,18 +58,35 @@ void systemsettings_init(void)
void systemsettings_save(PayloadBuilder *pb)
{
pb_char(pb, 'S');
pb_u8(pb, 0); // settings format version
pb_u8(pb, 3); // settings format version
{ // system settings
pb_bool(pb, SystemSettings.visible_vcom);
pb_bool(pb, SystemSettings.ini_comments);
// 1
pb_bool(pb, SystemSettings.enable_mco);
pb_u8(pb, SystemSettings.mco_prediv);
// 2
pb_bool(pb, SystemSettings.use_comm_uart);
pb_bool(pb, SystemSettings.use_comm_nordic);
pb_bool(pb, SystemSettings.use_comm_lora);
pb_u32(pb, SystemSettings.comm_uart_baud);
pb_bool(pb, SystemSettings.enable_debug_uart);
// 3
pb_u8(pb, SystemSettings.nrf_channel);
pb_u8(pb, SystemSettings.nrf_address);
pb_buf(pb, SystemSettings.nrf_network, 4);
} // end system settings
}
// from binary
bool systemsettings_load(PayloadParser *pp)
{
if (pp_char(pp) != 'S') return false;
systemsettings_begin_load();
uint8_t version = pp_u8(pp);
{ // system settings
@ -50,13 +94,80 @@ bool systemsettings_load(PayloadParser *pp)
SystemSettings.ini_comments = pp_bool(pp);
// conditional fields based on version
(void) version;
if (version >= 1) {
SystemSettings.enable_mco = pp_bool(pp);
SystemSettings.mco_prediv = pp_u8(pp);
}
if (version >= 2) {
SystemSettings.use_comm_uart = pp_bool(pp);
SystemSettings.use_comm_nordic = pp_bool(pp);
SystemSettings.use_comm_lora = pp_bool(pp);
SystemSettings.comm_uart_baud = pp_u32(pp);
SystemSettings.enable_debug_uart = pp_bool(pp);
}
if (version >= 3) {
SystemSettings.nrf_channel = pp_u8(pp);
SystemSettings.nrf_address = pp_u8(pp);
pp_buf(pp, SystemSettings.nrf_network, 4);
}
} // end system settings
systemsettings_finalize_load();
return pp->ok;
}
void systemsettings_mco_teardown(void)
{
if (SystemSettings.enable_mco) {
rsc_free(&UNIT_SYSTEM, R_PA8);
LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_NOCLOCK, 0);
}
}
void systemsettings_mco_init(void)
{
if (SystemSettings.enable_mco) {
assert_param(rsc_claim(&UNIT_SYSTEM, R_PA8) == E_SUCCESS);
assert_param(E_SUCCESS == hw_configure_gpiorsc_af(R_PA8, LL_GPIO_AF_0));
LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_SYSCLK, SystemSettings.mco_prediv << RCC_CFGR_MCOPRE_Pos);
} else {
LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_NOCLOCK, 0);
}
}
void systemsettings_debug_uart_init_deinit(void)
{
#if USE_DEBUG_UART
if (SystemSettings.enable_debug_uart) {
DebugUart_Init();
} else {
DebugUart_Teardown();
}
#endif
}
/**
* Begin load of system settings, releasing resources etc
*/
void systemsettings_begin_load(void)
{
systemsettings_mco_teardown();
com_release_resources_for_alt_transfers();
}
/**
* Claim resources and set up system components based on the loaded settings
*/
void systemsettings_finalize_load(void)
{
systemsettings_mco_init();
systemsettings_debug_uart_init_deinit();
com_claim_resources_for_alt_transfers();
}
/**
* Write system settings to INI (without section)
*/
@ -65,10 +176,53 @@ void systemsettings_build_ini(IniWriter *iw)
iw_section(iw, "SYSTEM");
iw_comment(iw, "Data link accessible as virtual comport (Y, N)");
iw_entry(iw, "expose_vcom", str_yn(SystemSettings.visible_vcom));
iw_entry_s(iw, "expose-vcom", str_yn(SystemSettings.visible_vcom));
iw_comment(iw, "Show comments in INI files (Y, N)");
iw_entry(iw, "ini_comments", str_yn(SystemSettings.ini_comments));
iw_entry_s(iw, "ini-comments", str_yn(SystemSettings.ini_comments));
iw_comment(iw, "Enable debug UART-Tx on PA9 (Y, N)"); // TODO update if moved to a different pin
iw_entry_s(iw, "debug-uart", str_yn(SystemSettings.enable_debug_uart));
iw_cmt_newline(iw);
iw_comment(iw, "Output core clock on PA8 (Y, N)");
iw_entry_s(iw, "mco-enable", str_yn(SystemSettings.enable_mco));
iw_comment(iw, "Output clock prediv (1,2,...,128)");
iw_entry_d(iw, "mco-prediv", (1<<SystemSettings.mco_prediv));
iw_cmt_newline(iw);
iw_comment(iw, "--- Allowed fallback communication ports ---");
iw_cmt_newline(iw);
iw_comment(iw, "UART Tx:PA2, Rx:PA3");
iw_entry_s(iw, "com-uart", str_yn(SystemSettings.use_comm_uart));
iw_entry_d(iw, "com-uart-baud", SystemSettings.comm_uart_baud);
#if SUPPORT_NRF
iw_cmt_newline(iw);
iw_comment(iw, "nRF24L01+ radio");
iw_entry_s(iw, "com-nrf", str_yn(SystemSettings.use_comm_nordic));
iw_comment(iw, "Radio channel (0-125)");
iw_entry_d(iw, "nrf-channel", SystemSettings.nrf_channel);
iw_comment(iw, "Network prefix (hex, 4 bytes)");
iw_entry(iw, "nrf-network", "%02X:%02X:%02X:%02X",
SystemSettings.nrf_network[0],
SystemSettings.nrf_network[1],
SystemSettings.nrf_network[2],
SystemSettings.nrf_network[3]);
iw_comment(iw, "Node address (1-255)");
iw_entry(iw, "nrf-address", "%d", (int)SystemSettings.nrf_address);
#endif // SUPPORT_NRF
// those aren't implement yet, don't tease the user
// TODO show pin-out, extra settings if applicable
#if 0
iw_comment(iw, "LoRa/GFSK sx127x");
iw_entry_s(iw, "com-lora", str_yn(SystemSettings.use_comm_sx127x));
#endif
}
/**
@ -77,15 +231,78 @@ void systemsettings_build_ini(IniWriter *iw)
bool systemsettings_load_ini(const char *restrict key, const char *restrict value)
{
bool suc = true;
if (streq(key, "expose_vcom")) {
if (streq(key, "expose-vcom")) {
bool yn = cfg_bool_parse(value, &suc);
if (suc) SystemSettings.visible_vcom = yn;
}
if (streq(key, "ini_comments")) {
if (streq(key, "ini-comments")) {
bool yn = cfg_bool_parse(value, &suc);
if (suc) SystemSettings.ini_comments = yn;
}
if (streq(key, "mco-enable")) {
bool yn = cfg_bool_parse(value, &suc);
if (suc) SystemSettings.enable_mco = yn;
}
if (streq(key, "mco-prediv")) {
int val = cfg_u8_parse(value, &suc);
if (suc) {
switch (val) {
case 1: SystemSettings.mco_prediv = 0; break;
case 2: SystemSettings.mco_prediv = 1; break;
case 4: SystemSettings.mco_prediv = 2; break;
case 8: SystemSettings.mco_prediv = 3; break;
case 16: SystemSettings.mco_prediv = 4; break;
case 32: SystemSettings.mco_prediv = 5; break;
case 64: SystemSettings.mco_prediv = 6; break;
default:
case 128: SystemSettings.mco_prediv = 7; break;
}
}
}
if (streq(key, "debug-uart")) {
bool yn = cfg_bool_parse(value, &suc);
if (suc) SystemSettings.enable_debug_uart = yn;
}
if (streq(key, "com-uart")) {
bool yn = cfg_bool_parse(value, &suc);
if (suc) SystemSettings.use_comm_uart = yn;
}
if (streq(key, "com-uart-baud")) {
uint32_t baud = cfg_u32_parse(value, &suc);
if (suc) SystemSettings.comm_uart_baud = baud;
}
#if SUPPORT_NRF
if (streq(key, "com-nrf")) {
bool yn = cfg_bool_parse(value, &suc);
if (suc) SystemSettings.use_comm_nordic = yn;
}
if (streq(key, "nrf-channel")) {
SystemSettings.nrf_channel = cfg_u8_parse(value, &suc);
}
if (streq(key, "nrf-address")) {
SystemSettings.nrf_address = cfg_u8_parse(value, &suc);
}
if (streq(key, "nrf-network")) {
cfg_hex_parse(&SystemSettings.nrf_network[0], 4, value, &suc);
}
#endif // SUPPORT_NRF
#if 0
if (streq(key, "com-lora")) {
bool yn = cfg_bool_parse(value, &suc);
if (suc) SystemSettings.use_comm_lora = yn;
}
#endif
return suc;
}

@ -18,6 +18,18 @@
struct system_settings {
bool visible_vcom;
bool ini_comments;
bool enable_mco;
uint8_t mco_prediv;
bool enable_debug_uart;
// enable alternate communication ports if USB doesn't enumerate (e.g. running from battery / solar cell remotely)
bool use_comm_uart;
uint32_t comm_uart_baud; // baud rate for the uart transport
bool use_comm_lora; // SX1276/8
bool use_comm_nordic; // nRF24L01+
uint8_t nrf_channel;
uint8_t nrf_network[4];
uint8_t nrf_address;
// Support flags put here for scoping, but not atcually part of the persistent settings
volatile bool editable; //!< True if we booted with the LOCK jumper removed
@ -60,4 +72,9 @@ void systemsettings_build_ini(IniWriter *iw);
*/
bool systemsettings_load_ini(const char *restrict key, const char *restrict value);
/** Release system resources before system settings init */
void systemsettings_begin_load(void);
/** Claim system resources and apply system settings */
void systemsettings_finalize_load(void);
#endif //GEX_SYSTEM_SETTINGS_H

@ -276,7 +276,7 @@ bool ureg_instantiate_by_ini(const char *restrict driver_name, const char *restr
char *name = NULL;
if (delim != NULL) {
// not last
name = strndup_ck(p, delim - p);
name = strndup_ck(p, delim - p + 1);
p = delim + 1;
} else {
// last name
@ -334,6 +334,7 @@ bool ureg_finalize_all_init(void)
uint8_t callsign = 1;
while (li != NULL) {
Unit *const pUnit = &li->unit;
dbg("+ %s \"%s\"", pUnit->driver->name, pUnit->name);
if (pUnit->status == E_SUCCESS) {
dbg("! Unit seems already loaded, skipping");

@ -169,7 +169,7 @@ void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN RTOS_SEMAPHORES */
/* add semaphores, ... */
assert_param(pdTRUE == xSemaphoreGive(semVcomTxReadyHandle));
xSemaphoreGive(semVcomTxReadyHandle);
/* USER CODE END RTOS_SEMAPHORES */
/* USER CODE BEGIN RTOS_TIMERS */

340
gex.mk

@ -1,60 +1,28 @@
GEX_SRC_DIR = \
User \
User/utils \
User/comm \
User/framework \
User/platform \
User/units \
User/units/neopixel \
User/units/test \
User/units/digital_out \
User/units/digital_in \
User/units/usart \
User/units/1wire \
User/units/i2c \
User/units/spi \
User/units/adc \
User/units/sipo \
User/units/fcap \
User/units/touch \
User/units/simple_pwm \
User/TinyFrame \
User/CWPack \
User/tasks
GEX_SOURCES = \
User/USB/usb_device.c \
User/USB/usbd_cdc_if.c \
User/USB/usbd_conf.c \
User/USB/usbd_desc.c \
User/USB/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c \
User/USB/STM32_USB_Device_Library/Class/MSC_CDC/usbd_msc_cdc.c \
User/USB/STM32_USB_Device_Library/Core/Src/usbd_core.c \
User/USB/STM32_USB_Device_Library/Core/Src/usbd_ctlreq.c \
User/USB/STM32_USB_Device_Library/Core/Src/usbd_ioreq.c
GEX_INCLUDES = \
-IUser \
-IUser/USB \
-IUser/USB/MSC_CDC \
-IUser/TinyFrame \
-IUser/vfs \
-IUser/utils \
-IUser/units \
-IUser/framework \
-IUser/platform \
-IUser/tasks \
-IUser/USB/STM32_USB_Device_Library/Core/Inc \
-IUser/USB/STM32_USB_Device_Library/Class/AUDIO/Inc \
-IUser/USB/STM32_USB_Device_Library/Class/CDC/Inc \
-IUser/USB/STM32_USB_Device_Library/Class/CustomHID/Inc \
-IUser/USB/STM32_USB_Device_Library/Class/DFU/Inc \
-IUser/USB/STM32_USB_Device_Library/Class/HID/Inc \
-IUser/USB/STM32_USB_Device_Library/Class/MSC/Inc \
-IUser/USB/STM32_USB_Device_Library/Class/MSC_CDC
GEX_CFLAGS = \
-D__weak="__attribute__((weak))" -D__packed="__attribute__((__packed__))" \
#####################################################
# Platform-independent, not directly related to GEX
#####################################################
# C
COMMON_C_DIRS = \
Drivers \
Drivers/CMSIS \
Middlewares \
Middlewares/FreeRTOS
COMMON_C_FILES = \
Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS/cmsis_os.c \
Middlewares/Third_Party/FreeRTOS/Source/croutine.c \
Middlewares/Third_Party/FreeRTOS/Source/event_groups.c \
Middlewares/Third_Party/FreeRTOS/Source/list.c \
Middlewares/Third_Party/FreeRTOS/Source/queue.c \
Middlewares/Third_Party/FreeRTOS/Source/tasks.c \
Middlewares/Third_Party/FreeRTOS/Source/timers.c \
Middlewares/Third_Party/FreeRTOS/Source/portable/MemMang/heap_4.c \
Src/main.c
COMMON_C_FLAGS = \
-DUSE_HAL_DRIVER \
-DUSE_FULL_LL_DRIVER \
-std=gnu99 -Wfatal-errors \
-Wall -Wextra -Wshadow \
-Wwrite-strings -Wold-style-definition -Winline -Wstrict-prototypes -Wreturn-type \
@ -66,16 +34,88 @@ GEX_CFLAGS = \
-fno-exceptions -finline-small-functions -findirect-inlining -Wno-strict-aliasing -Wno-float-equal \
-Wno-discarded-qualifiers -fstack-usage
GEX_CDEFS_BASE = \
-D__weak="__attribute__((weak))" \
-D__packed="__attribute__((__packed__))" \
-DUSE_FULL_LL_DRIVER \
ifeq ($(DEBUG), 1)
COMMON_C_FLAGS += -ggdb -g -gdwarf-2
endif
COMMON_C_INCLUDES = \
-IInc \
-IMiddlewares/Third_Party/FreeRTOS/Source/include \
-IMiddlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS \
-IDrivers/CMSIS/Include
# ASM
COMMON_AS_DIRS = \
ifeq '$(DISABLE_DEBUG)' '1'
COMMON_AS_FILES = \
COMMON_AS_FLAGS = \
-Wall -fdata-sections -ffunction-sections
COMMON_AS_INCLUDES = \
-IInc
#####################################################
# Platform-independent, GEX specific
#####################################################
GEX_UNIT_DIRS := $(foreach x,$(GEX_UNITS), GexUnits/$(x))
GEX_UNIT_DEFS := $(foreach x,$(GEX_UNITS), -DENABLE_UNIT_$(shell echo $(x) | tr a-z A-Z)=1) \
-DUNITS_REGISTER_CMD="$(foreach x,$(GEX_UNITS),ureg_add_type(&UNIT_$(shell echo $(x) | tr a-z A-Z));)"
GEX_C_DIRS = \
GexCore \
GexCore/utils \
GexCore/comm \
GexCore/framework \
GexCore/platform \
GexCore/units \
GexCore/TinyFrame \
GexCore/tasks \
$(GEX_UNIT_DIRS)
GEX_C_FILES = \
GexCore/USB/usb_device.c \
GexCore/USB/usbd_cdc_if.c \
GexCore/USB/usbd_conf.c \
GexCore/USB/usbd_desc.c \
GexCore/USB/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c \
GexCore/USB/STM32_USB_Device_Library/Class/MSC_CDC/usbd_msc_cdc.c \
GexCore/USB/STM32_USB_Device_Library/Core/Src/usbd_core.c \
GexCore/USB/STM32_USB_Device_Library/Core/Src/usbd_ctlreq.c \
GexCore/USB/STM32_USB_Device_Library/Core/Src/usbd_ioreq.c \
Src/platform_resources.c
GEX_CDEFS = $(GEX_CDEFS_BASE) \
GEX_C_INCLUDES = \
-IGexUnits \
-IGexCore \
-IGexCore/USB \
-IGexCore/USB/MSC_CDC \
-IGexCore/TinyFrame \
-IGexCore/vfs \
-IGexCore/utils \
-IGexCore/units \
-IGexCore/framework \
-IGexCore/platform \
-IGexCore/tasks \
-IGexCore/USB/STM32_USB_Device_Library/Core/Inc \
-IGexCore/USB/STM32_USB_Device_Library/Class/AUDIO/Inc \
-IGexCore/USB/STM32_USB_Device_Library/Class/CDC/Inc \
-IGexCore/USB/STM32_USB_Device_Library/Class/CustomHID/Inc \
-IGexCore/USB/STM32_USB_Device_Library/Class/DFU/Inc \
-IGexCore/USB/STM32_USB_Device_Library/Class/HID/Inc \
-IGexCore/USB/STM32_USB_Device_Library/Class/MSC/Inc \
-IGexCore/USB/STM32_USB_Device_Library/Class/MSC_CDC
GEX_C_FLAGS := \
-DGEX_PLAT_$(GEX_PLAT) \
-D__weak="__attribute__((weak))" \
-D__packed="__attribute__((__packed__))" \
$(GEX_UNIT_DEFS)
# Option to disable debug
ifeq '$(DISABLE_DEBUG)' '1'
GEX_C_FLAGS += \
-DUSE_FULL_ASSERT=0 \
-DASSERT_FILENAMES=0 \
-DDEBUG_VFS=0 \
@ -85,45 +125,177 @@ GEX_CDEFS = $(GEX_CDEFS_BASE) \
-DUSE_DEBUG_UART=0 \
-DDEBUG_MALLOC=0 \
-DDEBUG_RSC=0
else
GEX_CDEFS = $(GEX_CDEFS_BASE) \
GEX_C_FLAGS += \
-DUSE_FULL_ASSERT=1 \
-DASSERT_FILENAMES=1 \
-DDEBUG_VFS=0 \
-DDEBUG_FLASH_WRITE=0 \
-DVERBOSE_HARDFAULT=1 \
-DUSE_STACK_MONITOR=1 \
-DUSE_STACK_MONITOR=0 \
-DUSE_DEBUG_UART=1 \
-DDEBUG_MALLOC=0 \
-DDEBUG_RSC=0
endif
# Option to disable mass storage
ifeq '$(DISABLE_MSC)' '1'
GEX_C_FLAGS += -DDISABLE_MSC
else
GEX_C_FILES += \
GexCore/USB/usbd_storage_if.c \
GexCore/USB/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc.c \
GexCore/USB/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_bot.c \
GexCore/USB/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_data.c \
GexCore/USB/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_scsi.c
GEX_C_DIRS += \
GexCore/vfs
endif
GEX_CDEFS += -DDISABLE_MSC
else
# Option to enable CDC loopback (testing USB)
ifeq '$(CDC_LOOPBACK_TEST)' '1'
GEX_C_FLAGS += -DCDC_LOOPBACK_TEST=1
endif
GEX_SOURCES += \
User/USB/usbd_storage_if.c \
User/USB/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc.c \
User/USB/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_bot.c \
User/USB/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_data.c \
User/USB/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_scsi.c
GEX_SRC_DIR += \
User/vfs
#####################################################
# Common build infrastructure
#####################################################
endif
# Build path
BUILD_DIR = build
# target
TARGET = firmware
# Specs (determines which stdlib version is included)
SPECS = nano.specs
ifeq '$(DISABLE_TEST_UNIT)' '1'
GEX_CDEFS += -DDISABLE_TEST_UNIT=1
endif
C_SOURCES := \
$(COMMON_C_FILES) $(PLAT_C_FILES) $(GEX_C_FILES) \
$(foreach x,$(COMMON_C_DIRS),$(wildcard $(x)/*.c)) \
$(foreach x,$(PLAT_C_DIRS),$(wildcard $(x)/*.c)) \
$(foreach x,$(GEX_C_DIRS),$(wildcard $(x)/*.c)) \
ifeq '$(CDC_LOOPBACK_TEST)' '1'
GEX_CDEFS += -DCDC_LOOPBACK_TEST=1
AS_SOURCES := \
$(COMMON_AS_FILES) $(PLAT_AS_FILES) $(GEX_AS_FILES)
$(foreach x,$(COMMON_AS_DIRS),$(wildcard $(x)/*.s)) \
$(foreach x,$(PLAT_AS_DIRS),$(wildcard $(x)/*.s)) \
$(foreach x,$(GEX_AS_DIRS),$(wildcard $(x)/*.s))
# mcu
MCU := $(PLAT_CPU) -mthumb $(PLAT_FPU) $(PLAT_FLOAT-ABI)
#######################################
# binaries
#######################################
BINPATH = /usr/bin
PREFIX = arm-none-eabi-
CC := $(BINPATH)/$(PREFIX)gcc
AS := $(BINPATH)/$(PREFIX)gcc -x assembler-with-cpp
CP := $(BINPATH)/$(PREFIX)objcopy
AR := $(BINPATH)/$(PREFIX)ar
SZ := $(BINPATH)/$(PREFIX)size
HEX := $(CP) -O ihex
BIN := $(CP) -O binary -S
LIBS = -lc -lm -lnosys
LDFLAGS := \
$(MCU) -specs=$(SPECS) -T$(PLAT_LDSCRIPT) $(LIBS) \
-Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref \
-Wl,--gc-sections
# optimization (possible to set in build.mk)
ifeq ($(OPT),)
OPT = -Os
endif
C_FLAGS := \
$(MCU) $(OPT) \
$(GEX_C_INCLUDES) $(PLAT_C_INCLUDES) $(COMMON_C_INCLUDES) \
$(GEX_C_FLAGS) $(PLAT_C_FLAGS) $(COMMON_C_FLAGS)
AS_FLAGS := \
$(MCU) $(OPT) \
$(GEX_AS_INCLUDES) $(PLAT_AS_INCLUDES) $(COMMON_AS_INCLUDES) \
$(GEX_AS_FLAGS) $(PLAT_AS_FLAGS) $(COMMON_AS_FLAGS)
# Generate dependency information
DEPENDENCY_TRACKER = -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)"
#######################################
# Targets
#######################################
# # list of objects
# OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
# vpath %.c $(sort $(dir $(C_SOURCES)))
# # list of ASM program objects
# OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(AS_SOURCES:.s=.o)))
# vpath %.s $(sort $(dir $(AS_SOURCES)))
# list of objects
OBJECTS := $(addprefix $(BUILD_DIR)/,$(C_SOURCES:.c=.o)) \
$(addprefix $(BUILD_DIR)/,$(AS_SOURCES:.s=.o))
vpath %.c $(sort $(dir $(C_SOURCES)))
vpath %.s $(sort $(dir $(AS_SOURCES)))
MAKEFILES = Makefile GexCore/gex.mk build.mk
# default action: build all
all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin
$(BUILD_DIR)/%.o: %.c $(MAKEFILES) | $(BUILD_DIR)
@echo -e "\x1b[32mCC\x1b[m $<\n \x1b[90m-> $@\x1b[m"
@mkdir -p `dirname "$(BUILD_DIR)/$(<)"`
@$(CC) -c $(C_FLAGS) $(DEPENDENCY_TRACKER) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(<:.c=.lst) $< -o $@
$(BUILD_DIR)/%.o: %.s $(MAKEFILES) | $(BUILD_DIR)
@$(AS) -c $(C_FLAGS) $(DEPENDENCY_TRACKER) $< -o $@
$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) $(MAKEFILES)
@printf "LD $< -> $@\n"
$(CC) $(OBJECTS) $(LDFLAGS) -o $@
@$(SZ) $@
$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf $(MAKEFILES) | $(BUILD_DIR)
@printf "OBJDUMP $< -> $(BUILD_DIR)/$(TARGET).hex \n"
@$(HEX) $< $@
$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf $(MAKEFILES) | $(BUILD_DIR)
@printf "OBJDUMP $< -> $(BUILD_DIR)/$(TARGET).bin \n"
@$(BIN) $< $@
flash: $(BUILD_DIR)/$(TARGET).bin $(MAKEFILES)
@printf "FLASH $<\n"
@st-flash write $< 0x8000000
$(BUILD_DIR)/$(TARGET).dfu: $(BUILD_DIR)/$(TARGET).hex $(MAKEFILES)
@printf "DFU GEN $<\n"
dfu-convert -i $< $@
dfu: $(BUILD_DIR)/$(TARGET).dfu $(MAKEFILES)
@printf "DFU UPLOAD $<\n"
dfu-util -a 0 -D $<
dis: $(BUILD_DIR)/$(TARGET).elf $(MAKEFILES)
@printf "DIS $<\n"
@arm-none-eabi-objdump -Sslrtd build/$(TARGET).elf > disassembly.lst
$(BUILD_DIR):
@mkdir -p $@
#######################################
# clean up
#######################################
clean:
-rm -fR .dep $(BUILD_DIR)
#######################################
# dependencies
#######################################
-include $(shell mkdir .dep 2>/dev/null) $(wildcard .dep/*)

@ -10,12 +10,18 @@
#include "platform/debug_uart.h"
#include "gex_hooks.h"
#include "unit_registry.h"
#include "comm/interfaces.h"
#include "system_settings.h"
/**
* This is a systick callback for GEX application logic
*/
void GEX_MsTick(void)
{
if (gActiveComport == COMPORT_USART) {
com_iface_flush_buffer();
}
TF_Tick(comm);
Indicator_Tick();
ureg_tick_units();
@ -26,6 +32,11 @@ void GEX_MsTick(void)
*/
void GEX_PreInit(void)
{
// this is a hack to make logging of the initial messages work
// it's problematic because we shouldn't even enable the debug uart if it's disabled in the system settings
// TODO move system settings load earlier and check the uart flag in advance
SystemSettings.enable_debug_uart = true;
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();

@ -297,3 +297,46 @@ uint32_t cfg_enum4_parse(const char *value,
*suc = false;
return na;
}
void cfg_hex_parse(uint8_t *dest, uint32_t count, const char *value, bool *suc)
{
// discard possible leading 0x
if (value[0] == '0' && value[1] == 'x') {
value += 2;
}
uint8_t bytebuf = 0;
for (uint32_t digit = 0; digit < count * 2;) {
char v = *value;
if (v != 0) value++;
uint8_t nibble = 0;
if (v == ' ' || v == '.' || v == ',' || v == '-' || v == ':') {
continue; // junk
}
else if (v >= '0' && v <= '9') {
nibble = (uint8_t) (v - '0');
}
else if (v >= 'a' && v <= 'f') {
nibble = (uint8_t) (10 + (v - 'a'));
}
else if (v >= 'A' && v <= 'F') {
nibble = (uint8_t) (10 + (v - 'A'));
}
else if (v == 0) {
nibble = 0; // pad with zeros
}
else {
*suc = false;
return;
}
digit++;
bytebuf <<= 4;
bytebuf |= nibble;
if ((digit % 2 == 0) && digit > 0) { // whole byte
*dest++ = bytebuf;
bytebuf = 0;
}
}
}

@ -2,8 +2,8 @@
// Created by MightyPork on 2018/02/23.
//
#ifndef GEX_F072_CFG_UTILS_H
#define GEX_F072_CFG_UTILS_H
#ifndef GEX_CORE_CFG_UTILS_H
#define GEX_CORE_CFG_UTILS_H
#include "platform.h"
#include "rsc_enum.h"
@ -116,6 +116,18 @@ uint32_t cfg_enum4_parse(const char *tpl,
const char *d, uint32_t nd,
bool *suc);
/**
* Parse a hexa string to a byte array.
* Skips 0x prefix, '.', '-', ':', ' '.
*
* @param[out] dest - storage array
* @param[in] count - expected number of bytes
* @param[in] value - parsed string
* @param[out] suc - success flag
*/
void cfg_hex_parse(uint8_t *dest, uint32_t count,
const char *value, bool *suc);
/** Convert bool to a Y or N constant string */
#define str_yn(cond) ((cond) ? ("Y") : ("N"))
@ -150,4 +162,4 @@ static inline int32_t cfg_i32_parse(const char *value, bool *suc)
return (int32_t) avr_atoi(value);
}
#endif //GEX_F072_CFG_UTILS_H
#endif //GEX_CORE_CFG_UTILS_H

@ -5,65 +5,31 @@
#include "platform.h"
#include "framework/resources.h"
#include "debug_uart.h"
#include "plat_compat.h"
#include "hw_utils.h"
#include "framework/system_settings.h"
#if USE_DEBUG_UART
#define DEBUG_USART_BAUD 115200
#if GEX_PLAT_F072_DISCOVERY
#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_BLUEPILL
#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_DISCOVERY
#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_DISCOVERY
#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
static bool debug_uart_inited = false;
static bool debug_uart_preinited = false;
/** Init the submodule. */
void DebugUart_Init(void)
{
if (debug_uart_inited) return;
if (!debug_uart_preinited) DebugUart_PreInit();
// Debug UART
assert_param(E_SUCCESS == rsc_claim(&UNIT_SYSTEM, DEBUG_USART_RSC));
assert_param(E_SUCCESS == rsc_claim_pin(&UNIT_SYSTEM, DEBUG_USART_PORT, DEBUG_USART_PIN));
debug_uart_inited = true;
}
/** Init the hardware peripheral - this is called early in the boot process */
void DebugUart_PreInit(void)
{
debug_uart_preinited = true;
// configure AF only if platform uses AF numbers
#if !PLAT_NO_AFNUM
#pragma GCC diagnostic push
@ -89,8 +55,35 @@ void DebugUart_PreInit(void)
LL_USART_Enable(DEBUG_USART);
}
void DebugUart_Teardown(void)
{
if (!debug_uart_inited) return;
dbg("Disabling debug UART!");
// TODO wait for Tx (after debug print DMA is implemented)
LL_USART_Disable(DEBUG_USART);
rsc_free(&UNIT_SYSTEM, DEBUG_USART_RSC);
hw_periph_clock_disable(DEBUG_USART);
bool suc = true;
Resource r = rsc_portpin2rsc(DEBUG_USART_PORT, DEBUG_USART_PIN, &suc);
rsc_free(&UNIT_SYSTEM, r);
hw_deinit_pin_rsc(r);
debug_uart_preinited = false;
debug_uart_inited = false;
assert_param(suc);
}
void debug_write(const char *buf, uint16_t len)
{
if (!SystemSettings.enable_debug_uart) return;
// TODO wait for DMA complete
// TODO use DMA
for (uint16_t i = 0; i < len; i++) {
while (!LL_USART_IsActiveFlag_TC(DEBUG_USART));
LL_USART_TransmitData8(DEBUG_USART, (uint8_t) *buf++);

@ -8,6 +8,8 @@
#ifndef GEX_DEBUG_UART_H
#define GEX_DEBUG_UART_H
#include "platform.h"
/**
* Pre-init the debug uart
*
@ -17,7 +19,13 @@
void DebugUart_PreInit(void);
/**
* Finalize the init (claim resources)
* Release the peripheral and deinit pin
*/
void DebugUart_Teardown(void);
/**
* Finalize the init (claim resources).
* If not pre-inited (i.e. Teardown was called before), also pre-init.
*/
void DebugUart_Init(void);

@ -99,13 +99,18 @@ void hw_deinit_unit_pins(Unit *unit)
{
for (uint32_t rsc = R_PA0; rsc <= R_PF15; rsc++) {
if (RSC_IS_HELD(unit->resources, (Resource)rsc)) {
hw_deinit_pin_rsc((Resource)rsc);
}
}
}
void hw_deinit_pin_rsc(Resource rsc)
{
rsc_dbg("Freeing pin %s", rsc_get_name((Resource)rsc));
GPIO_TypeDef *port = GPIO_PERIPHS[(rsc-R_PA0) / 16];
uint32_t ll_pin = LL_GPIO_PINS[(rsc-R_PA0)%16];
LL_GPIO_SetPinMode(port, ll_pin, LL_GPIO_MODE_ANALOG);
}
}
}
/** Configure a pin to alternate function */
error_t hw_configure_gpio_af(char port_name, uint8_t pin_num, uint32_t ll_af)
@ -263,7 +268,7 @@ void hw_periph_clock_enable(void *periph)
else if (periph == TIM5) __HAL_RCC_TIM5_CLK_ENABLE();
#endif
#ifdef TIM6
else if (periph == TIM6) __HAL_RCC_TIM7_CLK_ENABLE();
else if (periph == TIM6) __HAL_RCC_TIM6_CLK_ENABLE();
#endif
#ifdef TIM7
else if (periph == TIM7) __HAL_RCC_TIM7_CLK_ENABLE();
@ -374,7 +379,7 @@ void hw_periph_clock_disable(void *periph)
else if (periph == TIM5) __HAL_RCC_TIM5_CLK_DISABLE();
#endif
#ifdef TIM6
else if (periph == TIM6) __HAL_RCC_TIM7_CLK_DISABLE();
else if (periph == TIM6) __HAL_RCC_TIM6_CLK_DISABLE();
#endif
#ifdef TIM7
else if (periph == TIM7) __HAL_RCC_TIM7_CLK_DISABLE();

@ -40,6 +40,8 @@ GPIO_TypeDef *hw_port2periph(char port_name, bool *suc);
*/
bool hw_pinrsc2ll(Resource rsc, GPIO_TypeDef **port, uint32_t *llpin) __attribute__((warn_unused_result));
void hw_deinit_pin_rsc(Resource rsc);
/**
* Spread packed port pins using a mask
*

@ -113,7 +113,7 @@ void irqd_init(void)
// NVIC_EnableIRQ(TIM3_IRQn); /*!< TIM3 global Interrupt */
NVIC_EnableIRQ(TIM6_DAC_IRQn); /*!< TIM6 global and DAC channel underrun error Interrupt */
HAL_NVIC_SetPriority(TIM7_IRQn, 2, 0); // Used for DAC timing
HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 2, 0); // Used for DAC timing
NVIC_EnableIRQ(TIM7_IRQn); /*!< TIM7 global Interrupt */
HAL_NVIC_SetPriority(TIM7_IRQn, 2, 0);// this will be for dac (?)
@ -246,7 +246,12 @@ void* irqd_detach(void *periph, IrqCallback callback)
return oldArg;
} else {
// catch bugs, but ignore an attempt to unbind when not bound
if (slot->callback != NULL) {
trap("Detach IRQ %p() from %p but %p() bound instead", callback, periph, slot->callback);
} else {
return NULL; // the arg - none
}
}
}

@ -4,8 +4,8 @@
// Provides a trampoline system for redirecting IRQ calls to assigned callbacks.
//
#ifndef GEX_F072_IRQ_DISPATCHER_H
#define GEX_F072_IRQ_DISPATCHER_H
#ifndef GEX_CORE_IRQ_DISPATCHER_H
#define GEX_CORE_IRQ_DISPATCHER_H
// Dummy peripherals for use with the
extern void * const EXTIS[16];
@ -42,4 +42,4 @@ void irqd_attach(void *periph, IrqCallback callback, void *data);
*/
void* irqd_detach(void *periph, IrqCallback callback);
#endif //GEX_F072_IRQ_DISPATCHER_H
#endif //GEX_CORE_IRQ_DISPATCHER_H

@ -2,8 +2,8 @@
// Created by MightyPork on 2018/02/04.
//
#ifndef GEX_F072_LL_EXTENSION_H
#define GEX_F072_LL_EXTENSION_H
#ifndef GEX_CORE_LL_EXTENSION_H
#define GEX_CORE_LL_EXTENSION_H
#include "platform.h"
@ -57,4 +57,4 @@ static inline void LL_DMA_ClearFlags(DMA_TypeDef *DMAx, uint8_t channel)
}
#endif //GEX_F072_LL_EXTENSION_H
#endif //GEX_CORE_LL_EXTENSION_H

@ -42,7 +42,11 @@ void LockJumper_Init(void)
LL_GPIO_SetPinPull(lock_periph, lock_llpin, LL_GPIO_PULL_UP);
#endif
SystemSettings.editable = (bool) LL_GPIO_IsInputPinSet(lock_periph, lock_llpin);
#if PLAT_LOCK_BTN
SystemSettings.editable = false;
#else
SystemSettings.editable = (PLAT_LOCK_1CLOSED != LL_GPIO_IsInputPinSet(lock_periph, lock_llpin));
#endif
dbg("Settings editable? %d", SystemSettings.editable);
}

@ -7,8 +7,6 @@
#ifndef GEX_LOCK_JUMPER_H
#define GEX_LOCK_JUMPER_H
#include "plat_compat.h"
/**
* Init the lock jumper subsystem
*/

@ -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

@ -7,242 +7,37 @@
#include "USB/usb_device.h"
#include "framework/resources.h"
#include "framework/unit_registry.h"
#include "units/digital_out/unit_dout.h"
#include "units/digital_in/unit_din.h"
#include "units/neopixel/unit_neopixel.h"
#include "units/i2c/unit_i2c.h"
#include "units/1wire/unit_1wire.h"
#include "units/adc/unit_adc.h"
#include "units/test/unit_test.h"
#include "units/usart/unit_usart.h"
#include "units/spi/unit_spi.h"
#include "units/sipo/unit_sipo.h"
#include "units/fcap/unit_fcap.h"
#include "units/touch/unit_touch.h"
#include "units/simple_pwm/unit_pwmdim.h"
#include "comm/interfaces.h"
#include "hw_utils.h"
#include "units_manifest.h"
extern uint32_t plat_init_resources2(void);
// TODO split this and the plat_compat files to per-platform ones stored in the platform project
void plat_init_resources(void)
{
// we use |= here, even though return values are not booleans - success is 0,
// thus any problem results in nonzero and the assert will trip.
uint32_t rv = 0;
// enable clock for units we use
// periphs available everywhere - enable clock
hw_periph_clock_enable(DMA1);
#ifdef DMA2
hw_periph_clock_enable(DMA2);
#endif
// --- Common unit drivers ---
#if HAVE_TEST_UNIT
ureg_add_type(&UNIT_TEST);
#endif
// EXTI are always available
rsc_free_range(NULL, R_EXTI0, R_EXTI15);
// --- platform specific resource releases and claims ---
#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(GEX_PLAT_F072_DISCOVERY)
// Platform STM32F073RBT
// Additional GPIO ports
__HAL_RCC_GPIOF_CLK_ENABLE();
// Units supported by the platform (known to work correctly)
ureg_add_type(&UNIT_DOUT);
ureg_add_type(&UNIT_DIN);
ureg_add_type(&UNIT_NEOPIXEL);
ureg_add_type(&UNIT_I2C);
ureg_add_type(&UNIT_SPI);
ureg_add_type(&UNIT_USART);
ureg_add_type(&UNIT_1WIRE);
ureg_add_type(&UNIT_ADC);
ureg_add_type(&UNIT_SIPO);
ureg_add_type(&UNIT_FCAP);
ureg_add_type(&UNIT_TOUCH);
ureg_add_type(&UNIT_PWMDIM);
// 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_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_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
// - this is a macro created in the Makefile, registering all enabled units
UNITS_REGISTER_CMD;
rv = plat_init_resources2();
assert_param(rv == 0);
}
@ -254,13 +49,12 @@ void plat_init_resources(void)
*/
void plat_usb_reconnect(void)
{
// TODO add better reset methods available on different chips
if (gActiveComport != COMPORT_USB) return;
// TODO add other reset methods available on different chips (e.g. externam FET)
USBD_LL_Reset(&hUsbDeviceFS);
#if defined(GEX_PLAT_F103_BLUEPILL)
// F103 doesn't have pull-up control
#else
#if PLAT_USB_PU_CTL
HAL_PCD_DevDisconnect(&hpcd_USB_FS);
osDelay(100);
HAL_PCD_DevConnect(&hpcd_USB_FS);

@ -17,8 +17,15 @@
// FreeRTOS includes
#include <cmsis_os.h>
// platform-independent GEX config
#include "plat_config.h"
// platform-specific stuff (includes stm32 driver headers)
#include "plat_compat.h"
#define PLAT_AHB_HZ (PLAT_AHB_MHZ*1000000)
#define PLAT_APB1_HZ (PLAT_APB1_MHZ*1000000)
#define PLAT_APB2_HZ (PLAT_APB2_MHZ*1000000)
// assert_param, trap...
#include "stm32_assert.h"
// inIRQ etc
@ -34,7 +41,6 @@
// GEX version string
#include "version.h"
// ---
/**

@ -7,8 +7,6 @@
// ---------------------------- HAL TIMEBASE -----------------------------
#define TIMEBASE_TIMER TIM17
HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
// EDIT - used 17 instead because 14 was needed for fcap
@ -102,21 +100,36 @@ uint64_t PTIM_GetMicrotime(void)
return (uint64_t) uwMillis * 1000 + uwMicros;
}
/** microsecond delay */
void PTIM_MicroDelay(uint16_t usec)
void PTIM_MicroDelayAligned(uint32_t usec, register const uint32_t start)
{
assert_param(usec < 1000);
const uint16_t start = (uint16_t) TIMEBASE_TIMER->CNT;
const uint16_t remain = (uint16_t) (999 - start);
register const uint32_t remain = (1000 - start);
if (remain > usec) {
// timer still has enough space going forward to pass the required wait time
for (; TIMEBASE_TIMER->CNT < start + usec;);
register const uint32_t end = start + usec;
while (TIMEBASE_TIMER->CNT < end) {
__NOP();
__NOP();
__NOP();
}
return;
}
else {
// timer is too close to the end
usec -= remain;
for (; TIMEBASE_TIMER->CNT >= start || TIMEBASE_TIMER->CNT < usec;);
while (1) {
register const uint32_t t = TIMEBASE_TIMER->CNT;
if (t < start && t >= usec) return;
}
}
}
/** microsecond delay */
void PTIM_MicroDelay(const uint32_t usec)
{
PTIM_MicroDelayAligned(usec, TIMEBASE_TIMER->CNT);
}

@ -8,11 +8,13 @@
// and interrupts.
//
#ifndef GEX_F072_TIMEBASE_H
#define GEX_F072_TIMEBASE_H
#ifndef GEX_CORE_TIMEBASE_H
#define GEX_CORE_TIMEBASE_H
#include "platform.h"
#define TIMEBASE_TIMER TIM17
/**
* Precision timer: get microtime as uint64_t
* This timestamp should be monotonously increasing with a precision of ±0.5µs
@ -36,6 +38,24 @@ static inline uint32_t PTIM_GetTime(void)
*
* @param usec - max 998
*/
void PTIM_MicroDelay(uint16_t usec);
void PTIM_MicroDelay(uint32_t usec);
/**
* Microsecond busy delay with alignment (reduced length jitter but possible up to 1 us delay)
*
* @param usec
*/
void PTIM_MicroDelayAligned(uint32_t usec, uint32_t start);
/**
* Wait until the next micro timer tick
*/
static inline uint32_t PTIM_MicroDelayAlign(void)
{
const uint32_t c = TIMEBASE_TIMER->CNT;
uint32_t res;
while ((res = TIMEBASE_TIMER->CNT) == c);
return res;
}
#endif //GEX_F072_TIMEBASE_H
#endif //GEX_CORE_TIMEBASE_H

@ -2,8 +2,8 @@
// Created by MightyPork on 2018/02/27.
//
#ifndef GEX_F072_WATCHDOG_H
#define GEX_F072_WATCHDOG_H
#ifndef GEX_CORE_WATCHDOG_H
#define GEX_CORE_WATCHDOG_H
/**
* Initialize the application watchdog
@ -29,4 +29,4 @@ void wd_resume(void);
*/
void wd_restart(void);
#endif //GEX_F072_WATCHDOG_H
#endif //GEX_CORE_WATCHDOG_H

@ -12,6 +12,7 @@
#include "usb_device.h"
#include "usbd_msc.h"
#include "task_main.h"
#include "comm/interfaces.h"
/* TaskUsbEvent function */
void TaskMain(void const * argument)
@ -25,14 +26,16 @@ void TaskMain(void const * argument)
Indicator_Effect(STATUS_WELCOME);
uint32_t startTime = xTaskGetTickCount();
const uint32_t bootTime = HAL_GetTick();
uint32_t startTime = bootTime;
uint32_t cnt = 1;
bool waiting_for_usb = true;
while(1) {
uint32_t msg;
xTaskNotifyWait(0, UINT32_MAX, &msg, 100); // time out if nothing happened
// periodic updates to the VFS driver
uint32_t now = xTaskGetTickCount();
uint32_t now = HAL_GetTick();
uint32_t elapsed = now - startTime;
if (elapsed >= 100) {
// interval 100ms or more - slow periodic
@ -48,6 +51,9 @@ void TaskMain(void const * argument)
Indicator_Heartbeat();
wd_restart();
// If USB has no signal, set up alternate communication interface
com_switch_transfer_if_needed();
}
// if no message and it just timed out, go wait some more...
@ -61,6 +67,7 @@ void TaskMain(void const * argument)
continue;
}
if (gActiveComport == COMPORT_USB) {
// Endpoint 0 - control messages for the different classes
if (msg & USBEVT_FLAG_EP0_RX_RDY) {
USBD_CDC_EP0_RxReady(&hUsbDeviceFS);
@ -85,11 +92,14 @@ void TaskMain(void const * argument)
// if (msg & (USBEVT_FLAG_EPx_IN(CDC_IN_EP))) {
// USBD_CDC_DataIn(&hUsbDeviceFS, CDC_IN_EP);
// }
if (msg & (USBEVT_FLAG_EPx_IN(CDC_CMD_EP))) {
USBD_CDC_DataIn(&hUsbDeviceFS, CDC_CMD_EP);
}
if (msg & (USBEVT_FLAG_EPx_OUT(CDC_OUT_EP))) {
USBD_CDC_DataOut(&hUsbDeviceFS, CDC_OUT_EP);
}
}
}
}

@ -5,8 +5,8 @@
// and TinyFrame message handling
//
#ifndef GEX_F072_TASK_MSG_H
#define GEX_F072_TASK_MSG_H
#ifndef GEX_CORE_TASK_MSG_H
#define GEX_CORE_TASK_MSG_H
#include "platform.h"
#include "sched_queue.h"
@ -39,4 +39,4 @@ void rxQuePostMsg(uint8_t *buf, uint32_t len);
extern volatile uint32_t msgQueHighWaterMark;
#endif
#endif //GEX_F072_TASK_MSG_H
#endif //GEX_CORE_TASK_MSG_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

@ -5,72 +5,6 @@
#ifndef GEX_PLAT_COMPAT_H
#define GEX_PLAT_COMPAT_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 260 // 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 512 // 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 64 // 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 300 // Timeout for receiving & parsing a frame
#define BULK_LST_TIMEOUT_MS 500 // timeout for the bulk transaction to expire
#define MSG_QUE_POST_TIMEOUT 100 // Time to post to the messages / jobs queue
// -------- Platform specific includes and defines ---------
/// Feature flags:
// PLAT_FLASHBANKS - has the Banks field on the Flash config struct
// PLAT_NO_FLOATING_INPUTS - can't have digital inputs with no pull resistor
// PLAT_USB_PHYCLOCK - requires special config of phy clock for USB
// PLAT_USB_OTGFS - uses the USB OTG IP, needs different config code
// PLAT_LOCK_BTN - use a lock button instead of a lock jumper (push to toggle)
// PLAT_LOCK_1CLOSED - lock jumper is active (closed / button pressed) in logical 1
// PLAT_NO_AFNUM - legacy platform without numbered AF alternatives
#if defined(GEX_PLAT_F103_BLUEPILL)
// platform name for the version string
@ -124,10 +58,8 @@
#define STATUS_LED_PORT 'C'
#define STATUS_LED_PIN 13
#elif defined(GEX_PLAT_F072_DISCOVERY)
#elif defined(STM32F072xB)
// platform name for the version string
#define GEX_PLATFORM "STM32F072-Discovery"
#define PLAT_AHB_MHZ 48
#define PLAT_APB1_MHZ 48
@ -162,6 +94,10 @@
// Number of GPIO ports A,B,C...
#define PORTS_COUNT 6
#if defined(GEX_PLAT_F072_DISCOVERY)
// platform name for the version string
#define GEX_PLATFORM "STM32F072-Discovery"
// Lock jumper config
#define LOCK_JUMPER_PORT 'A'
#define LOCK_JUMPER_PIN 0
@ -171,6 +107,41 @@
// Status LED config
#define STATUS_LED_PORT 'C'
#define STATUS_LED_PIN 6 // RED LED "UP"
#elif defined(GEX_PLAT_F072_HUB)
// platform name for the version string
#define GEX_PLATFORM "STM32F072-HUB"
#define PLAT_FULL_XTAL 1
// Lock jumper config
#define LOCK_JUMPER_PORT 'D'
#define LOCK_JUMPER_PIN 2
#define PLAT_LOCK_BTN 1 // toggle button instead of a jumper
#define PLAT_LOCK_1CLOSED 0 // toggle button active in log. 1
// Status LED config
#define STATUS_LED_PORT 'A'
#define STATUS_LED_PIN 15 // RED LED "UP"
#elif defined(GEX_PLAT_F072_ZERO)
// platform name for the version string
#define GEX_PLATFORM "STM32F072-ZERO"
#define PLAT_FULL_XTAL 1
// Lock jumper config
#define LOCK_JUMPER_PORT 'D'
#define LOCK_JUMPER_PIN 2
#define PLAT_LOCK_BTN 1 // toggle button instead of a jumper
#define PLAT_LOCK_1CLOSED 1 // toggle button active in log. 1
// Status LED config
#define STATUS_LED_PORT 'A'
#define STATUS_LED_PIN 15 // RED LED "UP"
#define SUPPORT_NRF 1
#else
#error Bad platform
#endif
#elif defined(GEX_PLAT_F303_DISCOVERY)

@ -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, &regnum, 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…
Cancel
Save