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