Repository: ArmDeveloperEcosystem/microphone-library-for-pico Branch: main Commit: a837f633a6ad Files: 24 Total size: 85.8 KB Directory structure: gitextract_j80jyxyx/ ├── .github/ │ └── stale.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── examples/ │ ├── hello_analog_microphone/ │ │ ├── CMakeLists.txt │ │ └── main.c │ ├── hello_pdm_microphone/ │ │ ├── CMakeLists.txt │ │ └── main.c │ └── usb_microphone/ │ ├── CMakeLists.txt │ ├── main.c │ ├── tusb_config.h │ ├── usb_descriptors.c │ ├── usb_microphone.c │ └── usb_microphone.h ├── pico_sdk_import.cmake └── src/ ├── OpenPDM2PCM/ │ ├── LICENSE.txt │ ├── OpenPDMFilter.c │ └── OpenPDMFilter.h ├── analog_microphone.c ├── include/ │ └── pico/ │ ├── analog_microphone.h │ └── pdm_microphone.h ├── pdm_microphone.c └── pdm_microphone.pio ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/stale.yml ================================================ # Number of days of inactivity before an issue becomes stale daysUntilStale: 60 # Number of days of inactivity before a stale issue is closed daysUntilClose: 7 # Issues with these labels will never be considered stale exemptLabels: - pinned - security - help wanted # Label to use when marking an issue as stale staleLabel: stale # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable closeComment: false # Never stale pull requests only: issues ================================================ FILE: .gitignore ================================================ CMakeLists.txt.user CMakeCache.txt CMakeFiles CMakeScripts Testing Makefile cmake_install.cmake install_manifest.txt compile_commands.json CTestTestfile.cmake _deps build/ ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.12) # initialize pico_sdk from GIT # (note this can come from environment, CMake cache etc) # set(PICO_SDK_FETCH_FROM_GIT on) # pico_sdk_import.cmake is a single file copied from this SDK # note: this must happen before project() include(pico_sdk_import.cmake) project(pico_microphone) # initialize the Pico SDK pico_sdk_init() add_library(pico_pdm_microphone INTERFACE) target_sources(pico_pdm_microphone INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src/pdm_microphone.c ${CMAKE_CURRENT_LIST_DIR}/src/OpenPDM2PCM/OpenPDMFilter.c ) target_include_directories(pico_pdm_microphone INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src/include ) pico_generate_pio_header(pico_pdm_microphone ${CMAKE_CURRENT_LIST_DIR}/src/pdm_microphone.pio) target_link_libraries(pico_pdm_microphone INTERFACE pico_stdlib hardware_dma hardware_pio) add_library(pico_analog_microphone INTERFACE) target_sources(pico_analog_microphone INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src/analog_microphone.c ) target_include_directories(pico_analog_microphone INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src/include ) target_link_libraries(pico_analog_microphone INTERFACE pico_stdlib hardware_adc hardware_dma) add_subdirectory("examples/hello_analog_microphone") add_subdirectory("examples/hello_pdm_microphone") add_subdirectory("examples/usb_microphone") ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # Microphone Library for Pico Capture audio from a microphone on your [Raspberry Pi Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/) or any [RP2040](https://www.raspberrypi.org/products/rp2040/) based board. 🎤 ## Hardware * RP2040 board * [Raspberry Pi Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/) * Microphones * Analog * [Electret Microphone Amplifier - MAX9814 with Auto Gain Control](https://www.adafruit.com/product/1713) * PDM * [Adafruit PDM MEMS Microphone Breakout](https://www.adafruit.com/product/3492) ### Default Pinout #### Analog Microphone | Raspberry Pi Pico / RP2040 | Analog Microphone | | -------------------------- | ----------------- | | 3.3V | VCC | | GND | GND | | GPIO 26 | OUT | #### PDM Microphone | Raspberry Pi Pico / RP2040 | PDM Microphone | | -------------------------- | ----------------- | | 3.3V | VCC | | GND | GND | | GND | SEL | | GPIO 2 | DAT | | GPIO 3 | CLK | GPIO pins are configurable in examples or API. ## Examples See [examples](examples/) folder. ## Cloning ```sh git clone https://github.com/ArmDeveloperEcosystem/microphone-library-for-pico.git ``` ## Building 1. [Set up the Pico C/C++ SDK](https://datasheets.raspberrypi.org/pico/getting-started-with-pico.pdf) 2. Set `PICO_SDK_PATH` ```sh export PICO_SDK_PATH=/path/to/pico-sdk ``` 3. Create `build` dir, run `cmake` and `make`: ``` mkdir build cd build cmake .. -DPICO_BOARD=pico make ``` 4. Copy example `.uf2` to Pico when in BOOT mode. ## License [Apache-2.0 License](LICENSE) ## Acknowledgements This project was created on behalf of the [Arm Software Developers](https://developer.arm.com/) team, follow them on Twitter: [@ArmSoftwareDev](https://twitter.com/armsoftwaredev) and YouTube: [Arm Software Developers](https://www.youtube.com/channel/UCHUAckhCfRom2EHDGxwhfOg) for more resources! The [OpenPDM2PCM](https://os.mbed.com/teams/ST/code/X_NUCLEO_CCA02M1//file/53f8b511f2a1/Middlewares/OpenPDM2PCM/) library is used to filter raw PDM data into PCM. The [TinyUSB](https://github.com/hathach/tinyusb) library is used in the `usb_microphone` example. --- Disclaimer: This is not an official Arm product. ================================================ FILE: examples/hello_analog_microphone/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.12) # rest of your project add_executable(hello_analog_microphone main.c ) target_link_libraries(hello_analog_microphone pico_analog_microphone) # enable usb output, disable uart output pico_enable_stdio_usb(hello_analog_microphone 1) pico_enable_stdio_uart(hello_analog_microphone 0) # create map/bin/hex/uf2 file in addition to ELF. pico_add_extra_outputs(hello_analog_microphone) ================================================ FILE: examples/hello_analog_microphone/main.c ================================================ /* * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * * * This examples captures data from an analog microphone using a sample * rate of 8 kHz and prints the sample values over the USB serial * connection. */ #include #include "pico/stdlib.h" #include "pico/analog_microphone.h" #include "tusb.h" // configuration const struct analog_microphone_config config = { // GPIO to use for input, must be ADC compatible (GPIO 26 - 28) .gpio = 26, // bias voltage of microphone in volts .bias_voltage = 1.25, // sample rate in Hz .sample_rate = 8000, // number of samples to buffer .sample_buffer_size = 256, }; // variables int16_t sample_buffer[256]; volatile int samples_read = 0; void on_analog_samples_ready() { // callback from library when all the samples in the library // internal sample buffer are ready for reading samples_read = analog_microphone_read(sample_buffer, 256); } int main( void ) { // initialize stdio and wait for USB CDC connect stdio_init_all(); while (!tud_cdc_connected()) { tight_loop_contents(); } printf("hello analog microphone\n"); // initialize the analog microphone if (analog_microphone_init(&config) < 0) { printf("analog microphone initialization failed!\n"); while (1) { tight_loop_contents(); } } // set callback that is called when all the samples in the library // internal sample buffer are ready for reading analog_microphone_set_samples_ready_handler(on_analog_samples_ready); // start capturing data from the analog microphone if (analog_microphone_start() < 0) { printf("PDM microphone start failed!\n"); while (1) { tight_loop_contents(); } } while (1) { // wait for new samples while (samples_read == 0) { tight_loop_contents(); } // store and clear the samples read from the callback int sample_count = samples_read; samples_read = 0; // loop through any new collected samples for (int i = 0; i < sample_count; i++) { printf("%d\n", sample_buffer[i]); } } return 0; } ================================================ FILE: examples/hello_pdm_microphone/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.12) # rest of your project add_executable(hello_pdm_microphone main.c ) target_link_libraries(hello_pdm_microphone pico_pdm_microphone) # enable usb output, disable uart output pico_enable_stdio_usb(hello_pdm_microphone 1) pico_enable_stdio_uart(hello_pdm_microphone 0) # create map/bin/hex/uf2 file in addition to ELF. pico_add_extra_outputs(hello_pdm_microphone) ================================================ FILE: examples/hello_pdm_microphone/main.c ================================================ /* * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * * This examples captures data from a PDM microphone using a sample * rate of 8 kHz and prints the sample values over the USB serial * connection. */ #include #include #include "pico/stdlib.h" #include "pico/pdm_microphone.h" #include "tusb.h" // configuration const struct pdm_microphone_config config = { // GPIO pin for the PDM DAT signal .gpio_data = 2, // GPIO pin for the PDM CLK signal .gpio_clk = 3, // PIO instance to use .pio = pio0, // PIO State Machine instance to use .pio_sm = 0, // sample rate in Hz .sample_rate = 8000, // number of samples to buffer .sample_buffer_size = 256, }; // variables int16_t sample_buffer[256]; volatile int samples_read = 0; void on_pdm_samples_ready() { // callback from library when all the samples in the library // internal sample buffer are ready for reading samples_read = pdm_microphone_read(sample_buffer, 256); } int main( void ) { // initialize stdio and wait for USB CDC connect stdio_init_all(); while (!tud_cdc_connected()) { tight_loop_contents(); } printf("hello PDM microphone\n"); // initialize the PDM microphone if (pdm_microphone_init(&config) < 0) { printf("PDM microphone initialization failed!\n"); while (1) { tight_loop_contents(); } } // set callback that is called when all the samples in the library // internal sample buffer are ready for reading pdm_microphone_set_samples_ready_handler(on_pdm_samples_ready); // start capturing data from the PDM microphone if (pdm_microphone_start() < 0) { printf("PDM microphone start failed!\n"); while (1) { tight_loop_contents(); } } while (1) { // wait for new samples while (samples_read == 0) { tight_loop_contents(); } // store and clear the samples read from the callback int sample_count = samples_read; samples_read = 0; // loop through any new collected samples for (int i = 0; i < sample_count; i++) { printf("%d\n", sample_buffer[i]); } } return 0; } ================================================ FILE: examples/usb_microphone/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.12) # rest of your project add_executable(usb_microphone main.c usb_descriptors.c usb_microphone.c ) target_include_directories(usb_microphone PRIVATE ${CMAKE_CURRENT_LIST_DIR}) target_link_libraries(usb_microphone PRIVATE tinyusb_device tinyusb_board pico_pdm_microphone) # create map/bin/hex/uf2 file in addition to ELF. pico_add_extra_outputs(usb_microphone) ================================================ FILE: examples/usb_microphone/main.c ================================================ /* * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * * This examples creates a USB Microphone device using the TinyUSB * library and captures data from a PDM microphone using a sample * rate of 16 kHz, to be sent the to PC. * * The USB microphone code is based on the TinyUSB audio_test example. * * https://github.com/hathach/tinyusb/tree/master/examples/device/audio_test */ #include "pico/pdm_microphone.h" #include "usb_microphone.h" // configuration const struct pdm_microphone_config config = { .gpio_data = 2, .gpio_clk = 3, .pio = pio0, .pio_sm = 0, .sample_rate = SAMPLE_RATE, .sample_buffer_size = SAMPLE_BUFFER_SIZE, }; // variables uint16_t sample_buffer[SAMPLE_BUFFER_SIZE]; // callback functions void on_pdm_samples_ready(); void on_usb_microphone_tx_ready(); int main(void) { // initialize and start the PDM microphone pdm_microphone_init(&config); pdm_microphone_set_samples_ready_handler(on_pdm_samples_ready); pdm_microphone_start(); // initialize the USB microphone interface usb_microphone_init(); usb_microphone_set_tx_ready_handler(on_usb_microphone_tx_ready); while (1) { // run the USB microphone task continuously usb_microphone_task(); } return 0; } void on_pdm_samples_ready() { // Callback from library when all the samples in the library // internal sample buffer are ready for reading. // // Read new samples into local buffer. pdm_microphone_read(sample_buffer, SAMPLE_BUFFER_SIZE); } void on_usb_microphone_tx_ready() { // Callback from TinyUSB library when all data is ready // to be transmitted. // // Write local buffer to the USB microphone usb_microphone_write(sample_buffer, sizeof(sample_buffer)); } ================================================ FILE: examples/usb_microphone/tusb_config.h ================================================ /* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #ifndef _TUSB_CONFIG_H_ #define _TUSB_CONFIG_H_ #ifdef __cplusplus extern "C" { #endif //-------------------------------------------------------------------- // COMMON CONFIGURATION //-------------------------------------------------------------------- // defined by compiler flags for flexibility #ifndef CFG_TUSB_MCU #error CFG_TUSB_MCU must be defined #endif #if CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED) #else #define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE #endif #ifndef CFG_TUSB_OS #define CFG_TUSB_OS OPT_OS_NONE #endif #ifndef CFG_TUSB_DEBUG #define CFG_TUSB_DEBUG 0 #endif // CFG_TUSB_DEBUG is defined by compiler in DEBUG build // #define CFG_TUSB_DEBUG 0 /* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. * Tinyusb use follows macros to declare transferring memory so that they can be put * into those specific section. * e.g * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) */ #ifndef CFG_TUSB_MEM_SECTION #define CFG_TUSB_MEM_SECTION #endif #ifndef CFG_TUSB_MEM_ALIGN #define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) #endif //-------------------------------------------------------------------- // DEVICE CONFIGURATION //-------------------------------------------------------------------- #ifndef CFG_TUD_ENDPOINT0_SIZE #define CFG_TUD_ENDPOINT0_SIZE 64 #endif //------------- CLASS -------------// #define CFG_TUD_CDC 0 #define CFG_TUD_MSC 0 #define CFG_TUD_HID 0 #define CFG_TUD_MIDI 0 #define CFG_TUD_AUDIO 1 #define CFG_TUD_VENDOR 0 //-------------------------------------------------------------------- // AUDIO CLASS DRIVER CONFIGURATION //-------------------------------------------------------------------- // Have a look into audio_device.h for all configurations #define CFG_TUD_AUDIO_FUNC_1_DESC_LEN TUD_AUDIO_MIC_ONE_CH_DESC_LEN #define CFG_TUD_AUDIO_FUNC_1_N_AS_INT 1 // Number of Standard AS Interface Descriptors (4.9.1) defined per audio function - this is required to be able to remember the current alternate settings of these interfaces - We restrict us here to have a constant number for all audio functions (which means this has to be the maximum number of AS interfaces an audio function has and a second audio function with less AS interfaces just wastes a few bytes) #define CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ 64 // Size of control request buffer #define CFG_TUD_AUDIO_ENABLE_EP_IN 1 #define CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX 2 // Driver gets this info from the descriptors - we define it here to use it to setup the descriptors and to do calculations with it below #define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX 1 // Driver gets this info from the descriptors - we define it here to use it to setup the descriptors and to do calculations with it below - be aware: for different number of channels you need another descriptor! #define CFG_TUD_AUDIO_EP_SZ_IN (16 + 1) * CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX * CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX // 16 Samples (16 kHz) x 2 Bytes/Sample x 1 Channel #define CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX CFG_TUD_AUDIO_EP_SZ_IN // Maximum EP IN size for all AS alternate settings used #define CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ CFG_TUD_AUDIO_EP_SZ_IN #ifdef __cplusplus } #endif #endif /* _TUSB_CONFIG_H_ */ ================================================ FILE: examples/usb_microphone/usb_descriptors.c ================================================ /* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #include "tusb.h" /* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. * * Auto ProductID layout's Bitmap: * [MSB] AUDIO | MIDI | HID | MSC | CDC [LSB] */ #define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) #define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ _PID_MAP(MIDI, 3) | _PID_MAP(AUDIO, 4) | _PID_MAP(VENDOR, 5) ) //--------------------------------------------------------------------+ // Device Descriptors //--------------------------------------------------------------------+ tusb_desc_device_t const desc_device = { .bLength = sizeof(tusb_desc_device_t), .bDescriptorType = TUSB_DESC_DEVICE, .bcdUSB = 0x0200, // Use Interface Association Descriptor (IAD) for CDC // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1) .bDeviceClass = TUSB_CLASS_MISC, .bDeviceSubClass = MISC_SUBCLASS_COMMON, .bDeviceProtocol = MISC_PROTOCOL_IAD, .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, .idVendor = 0xCafe, .idProduct = USB_PID, .bcdDevice = 0x0100, .iManufacturer = 0x01, .iProduct = 0x02, .iSerialNumber = 0x03, .bNumConfigurations = 0x01 }; // Invoked when received GET DEVICE DESCRIPTOR // Application return pointer to descriptor uint8_t const * tud_descriptor_device_cb(void) { return (uint8_t const *) &desc_device; } //--------------------------------------------------------------------+ // Configuration Descriptor //--------------------------------------------------------------------+ enum { ITF_NUM_AUDIO_CONTROL = 0, ITF_NUM_AUDIO_STREAMING, ITF_NUM_TOTAL }; #define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_AUDIO * TUD_AUDIO_MIC_ONE_CH_DESC_LEN) #if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX // LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number // 0 control, 1 In, 2 Bulk, 3 Iso, 4 In etc ... #define EPNUM_AUDIO 0x03 #else #define EPNUM_AUDIO 0x01 #endif uint8_t const desc_configuration[] = { // Interface count, string index, total length, attribute, power in mA TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), // Interface number, string index, EP Out & EP In address, EP size TUD_AUDIO_MIC_ONE_CH_DESCRIPTOR(/*_itfnum*/ ITF_NUM_AUDIO_CONTROL, /*_stridx*/ 0, /*_nBytesPerSample*/ CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX, /*_nBitsUsedPerSample*/ CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX*8, /*_epin*/ 0x80 | EPNUM_AUDIO, /*_epsize*/ CFG_TUD_AUDIO_EP_SZ_IN) }; // Invoked when received GET CONFIGURATION DESCRIPTOR // Application return pointer to descriptor // Descriptor contents must exist long enough for transfer to complete uint8_t const * tud_descriptor_configuration_cb(uint8_t index) { (void) index; // for multiple configurations return desc_configuration; } //--------------------------------------------------------------------+ // String Descriptors //--------------------------------------------------------------------+ // array of pointer to string descriptors char const* string_desc_arr [] = { (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) "PaniRCorp", // 1: Manufacturer "MicNode", // 2: Product "123456", // 3: Serials, should use chip ID "UAC2", // 4: Audio Interface }; static uint16_t _desc_str[32]; // Invoked when received GET STRING DESCRIPTOR request // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) { (void) langid; uint8_t chr_count; if ( index == 0) { memcpy(&_desc_str[1], string_desc_arr[0], 2); chr_count = 1; }else { // Convert ASCII string into UTF-16 if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL; const char* str = string_desc_arr[index]; // Cap at max char chr_count = strlen(str); if ( chr_count > 31 ) chr_count = 31; for(uint8_t i=0; ibRequest == AUDIO_CS_REQ_CUR); // Page 91 in UAC2 specification uint8_t channelNum = TU_U16_LOW(p_request->wValue); uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); uint8_t ep = TU_U16_LOW(p_request->wIndex); (void) channelNum; (void) ctrlSel; (void) ep; return false; // Yet not implemented } // Invoked when audio class specific set request received for an interface bool tud_audio_set_req_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff) { (void) rhport; (void) pBuff; // We do not support any set range requests here, only current value requests TU_VERIFY(p_request->bRequest == AUDIO_CS_REQ_CUR); // Page 91 in UAC2 specification uint8_t channelNum = TU_U16_LOW(p_request->wValue); uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); uint8_t itf = TU_U16_LOW(p_request->wIndex); (void) channelNum; (void) ctrlSel; (void) itf; return false; // Yet not implemented } // Invoked when audio class specific set request received for an entity bool tud_audio_set_req_entity_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff) { (void) rhport; // Page 91 in UAC2 specification uint8_t channelNum = TU_U16_LOW(p_request->wValue); uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); uint8_t itf = TU_U16_LOW(p_request->wIndex); uint8_t entityID = TU_U16_HIGH(p_request->wIndex); (void) itf; // We do not support any set range requests here, only current value requests TU_VERIFY(p_request->bRequest == AUDIO_CS_REQ_CUR); // If request is for our feature unit if ( entityID == 2 ) { switch ( ctrlSel ) { case AUDIO_FU_CTRL_MUTE: // Request uses format layout 1 TU_VERIFY(p_request->wLength == sizeof(audio_control_cur_1_t)); mute[channelNum] = ((audio_control_cur_1_t*) pBuff)->bCur; TU_LOG2(" Set Mute: %d of channel: %u\r\n", mute[channelNum], channelNum); return true; case AUDIO_FU_CTRL_VOLUME: // Request uses format layout 2 TU_VERIFY(p_request->wLength == sizeof(audio_control_cur_2_t)); volume[channelNum] = ((audio_control_cur_2_t*) pBuff)->bCur; TU_LOG2(" Set Volume: %d dB of channel: %u\r\n", volume[channelNum], channelNum); return true; // Unknown/Unsupported control default: TU_BREAKPOINT(); return false; } } return false; // Yet not implemented } // Invoked when audio class specific get request received for an EP bool tud_audio_get_req_ep_cb(uint8_t rhport, tusb_control_request_t const * p_request) { (void) rhport; // Page 91 in UAC2 specification uint8_t channelNum = TU_U16_LOW(p_request->wValue); uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); uint8_t ep = TU_U16_LOW(p_request->wIndex); (void) channelNum; (void) ctrlSel; (void) ep; // return tud_control_xfer(rhport, p_request, &tmp, 1); return false; // Yet not implemented } // Invoked when audio class specific get request received for an interface bool tud_audio_get_req_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request) { (void) rhport; // Page 91 in UAC2 specification uint8_t channelNum = TU_U16_LOW(p_request->wValue); uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); uint8_t itf = TU_U16_LOW(p_request->wIndex); (void) channelNum; (void) ctrlSel; (void) itf; return false; // Yet not implemented } // Invoked when audio class specific get request received for an entity bool tud_audio_get_req_entity_cb(uint8_t rhport, tusb_control_request_t const * p_request) { (void) rhport; // Page 91 in UAC2 specification uint8_t channelNum = TU_U16_LOW(p_request->wValue); uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); // uint8_t itf = TU_U16_LOW(p_request->wIndex); // Since we have only one audio function implemented, we do not need the itf value uint8_t entityID = TU_U16_HIGH(p_request->wIndex); // Input terminal (Microphone input) if (entityID == 1) { switch (ctrlSel) { case AUDIO_TE_CTRL_CONNECTOR:; // The terminal connector control only has a get request with only the CUR attribute. audio_desc_channel_cluster_t ret; // Those are dummy values for now ret.bNrChannels = 1; ret.bmChannelConfig = 0; ret.iChannelNames = 0; TU_LOG2(" Get terminal connector\r\n"); return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, (void*)&ret, sizeof(ret)); // Unknown/Unsupported control selector default: TU_BREAKPOINT(); return false; } } // Feature unit if (entityID == 2) { switch (ctrlSel) { case AUDIO_FU_CTRL_MUTE: // Audio control mute cur parameter block consists of only one byte - we thus can send it right away // There does not exist a range parameter block for mute TU_LOG2(" Get Mute of channel: %u\r\n", channelNum); return tud_control_xfer(rhport, p_request, &mute[channelNum], 1); case AUDIO_FU_CTRL_VOLUME: switch (p_request->bRequest) { case AUDIO_CS_REQ_CUR: TU_LOG2(" Get Volume of channel: %u\r\n", channelNum); return tud_control_xfer(rhport, p_request, &volume[channelNum], sizeof(volume[channelNum])); case AUDIO_CS_REQ_RANGE: TU_LOG2(" Get Volume range of channel: %u\r\n", channelNum); // Copy values - only for testing - better is version below audio_control_range_2_n_t(1) ret; ret.wNumSubRanges = 1; ret.subrange[0].bMin = -90; // -90 dB ret.subrange[0].bMax = 90; // +90 dB ret.subrange[0].bRes = 1; // 1 dB steps return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, (void*)&ret, sizeof(ret)); // Unknown/Unsupported control default: TU_BREAKPOINT(); return false; } // Unknown/Unsupported control default: TU_BREAKPOINT(); return false; } } // Clock Source unit if (entityID == 4) { switch (ctrlSel) { case AUDIO_CS_CTRL_SAM_FREQ: // channelNum is always zero in this case switch (p_request->bRequest) { case AUDIO_CS_REQ_CUR: TU_LOG2(" Get Sample Freq.\r\n"); return tud_control_xfer(rhport, p_request, &sampFreq, sizeof(sampFreq)); case AUDIO_CS_REQ_RANGE: TU_LOG2(" Get Sample Freq. range\r\n"); return tud_control_xfer(rhport, p_request, &sampleFreqRng, sizeof(sampleFreqRng)); // Unknown/Unsupported control default: TU_BREAKPOINT(); return false; } case AUDIO_CS_CTRL_CLK_VALID: // Only cur attribute exists for this request TU_LOG2(" Get Sample Freq. valid\r\n"); return tud_control_xfer(rhport, p_request, &clkValid, sizeof(clkValid)); // Unknown/Unsupported control default: TU_BREAKPOINT(); return false; } } TU_LOG2(" Unsupported entity: %d\r\n", entityID); return false; // Yet not implemented } bool tud_audio_tx_done_pre_load_cb(uint8_t rhport, uint8_t itf, uint8_t ep_in, uint8_t cur_alt_setting) { (void) rhport; (void) itf; (void) ep_in; (void) cur_alt_setting; if (usb_microphone_tx_ready_handler) { usb_microphone_tx_ready_handler(); } return true; } bool tud_audio_tx_done_post_load_cb(uint8_t rhport, uint16_t n_bytes_copied, uint8_t itf, uint8_t ep_in, uint8_t cur_alt_setting) { (void) rhport; (void) n_bytes_copied; (void) itf; (void) ep_in; (void) cur_alt_setting; return true; } bool tud_audio_set_itf_close_EP_cb(uint8_t rhport, tusb_control_request_t const * p_request) { (void) rhport; (void) p_request; return true; } ================================================ FILE: examples/usb_microphone/usb_microphone.h ================================================ /* * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * */ #ifndef _USB_MICROPHONE_H_ #define _USB_MICROPHONE_H_ #include "tusb.h" #ifndef SAMPLE_RATE #define SAMPLE_RATE ((CFG_TUD_AUDIO_EP_SZ_IN / 2) - 1) * 1000 #endif #ifndef SAMPLE_BUFFER_SIZE #define SAMPLE_BUFFER_SIZE ((CFG_TUD_AUDIO_EP_SZ_IN/2) - 1) #endif typedef void (*usb_microphone_tx_ready_handler_t)(void); void usb_microphone_init(); void usb_microphone_set_tx_ready_handler(usb_microphone_tx_ready_handler_t handler); void usb_microphone_task(); uint16_t usb_microphone_write(const void * data, uint16_t len); #endif ================================================ FILE: pico_sdk_import.cmake ================================================ # This is a copy of /external/pico_sdk_import.cmake # This can be dropped into an external project to help locate this SDK # It should be include()ed prior to project() if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") endif () if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") endif () if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") endif () set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") if (NOT PICO_SDK_PATH) if (PICO_SDK_FETCH_FROM_GIT) include(FetchContent) set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) if (PICO_SDK_FETCH_FROM_GIT_PATH) get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") endif () FetchContent_Declare( pico_sdk GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk GIT_TAG master ) if (NOT pico_sdk) message("Downloading Raspberry Pi Pico SDK") FetchContent_Populate(pico_sdk) set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) endif () set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) else () message(FATAL_ERROR "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." ) endif () endif () get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") if (NOT EXISTS ${PICO_SDK_PATH}) message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") endif () set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") endif () set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) include(${PICO_SDK_INIT_CMAKE_FILE}) ================================================ FILE: src/OpenPDM2PCM/LICENSE.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Repository toolbox Import into Mbed Studio Export to desktop IDE Repository details Type: Library Created: 27 Apr 2017 Imports: 122 Forks: 1 Commits: 27 Dependents: 4 Dependencies: 3 Followers: 387 The code in this repository is Apache licensed. Components X-NUCLEO-CCA02M1 Digital MEMS Microphones Expansion Board. ================================================ FILE: src/OpenPDM2PCM/OpenPDMFilter.c ================================================ /** ******************************************************************************* * @file OpenPDMFilter.c * @author CL * @version V1.0.0 * @date 9-September-2015 * @brief Open PDM audio software decoding Library. * This Library is used to decode and reconstruct the audio signal * produced by ST MEMS microphone (MP45Dxxx, MP34Dxxx). ******************************************************************************* * @attention * *

© COPYRIGHT 2018 STMicroelectronics

* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************* */ /* Includes ------------------------------------------------------------------*/ #include "OpenPDMFilter.h" /* Variables -----------------------------------------------------------------*/ uint32_t div_const = 0; int64_t sub_const = 0; uint32_t sinc[DECIMATION_MAX * SINCN]; uint32_t sinc1[DECIMATION_MAX]; uint32_t sinc2[DECIMATION_MAX * 2]; uint32_t coef[SINCN][DECIMATION_MAX]; #ifdef USE_LUT int32_t lut[256][DECIMATION_MAX / 8][SINCN]; #endif /* Functions -----------------------------------------------------------------*/ #ifdef USE_LUT int32_t filter_table_mono_64(uint8_t *data, uint8_t sincn) { return (int32_t) lut[data[0]][0][sincn] + lut[data[1]][1][sincn] + lut[data[2]][2][sincn] + lut[data[3]][3][sincn] + lut[data[4]][4][sincn] + lut[data[5]][5][sincn] + lut[data[6]][6][sincn] + lut[data[7]][7][sincn]; } int32_t filter_table_stereo_64(uint8_t *data, uint8_t sincn) { return (int32_t) lut[data[0]][0][sincn] + lut[data[2]][1][sincn] + lut[data[4]][2][sincn] + lut[data[6]][3][sincn] + lut[data[8]][4][sincn] + lut[data[10]][5][sincn] + lut[data[12]][6][sincn] + lut[data[14]][7][sincn]; } int32_t filter_table_mono_128(uint8_t *data, uint8_t sincn) { return (int32_t) lut[data[0]][0][sincn] + lut[data[1]][1][sincn] + lut[data[2]][2][sincn] + lut[data[3]][3][sincn] + lut[data[4]][4][sincn] + lut[data[5]][5][sincn] + lut[data[6]][6][sincn] + lut[data[7]][7][sincn] + lut[data[8]][8][sincn] + lut[data[9]][9][sincn] + lut[data[10]][10][sincn] + lut[data[11]][11][sincn] + lut[data[12]][12][sincn] + lut[data[13]][13][sincn] + lut[data[14]][14][sincn] + lut[data[15]][15][sincn]; } int32_t filter_table_stereo_128(uint8_t *data, uint8_t sincn) { return (int32_t) lut[data[0]][0][sincn] + lut[data[2]][1][sincn] + lut[data[4]][2][sincn] + lut[data[6]][3][sincn] + lut[data[8]][4][sincn] + lut[data[10]][5][sincn] + lut[data[12]][6][sincn] + lut[data[14]][7][sincn] + lut[data[16]][8][sincn] + lut[data[18]][9][sincn] + lut[data[20]][10][sincn] + lut[data[22]][11][sincn] + lut[data[24]][12][sincn] + lut[data[26]][13][sincn] + lut[data[28]][14][sincn] + lut[data[30]][15][sincn]; } int32_t (* filter_tables_64[2]) (uint8_t *data, uint8_t sincn) = {filter_table_mono_64, filter_table_stereo_64}; int32_t (* filter_tables_128[2]) (uint8_t *data, uint8_t sincn) = {filter_table_mono_128, filter_table_stereo_128}; #else int32_t filter_table(uint8_t *data, uint8_t sincn, TPDMFilter_InitStruct *param) { uint8_t c, i; uint16_t data_index = 0; uint32_t *coef_p = &coef[sincn][0]; int32_t F = 0; uint8_t decimation = param->Decimation; uint8_t channels = param->In_MicChannels; for (i = 0; i < decimation; i += 8) { c = data[data_index]; F += ((c >> 7) ) * coef_p[i ] + ((c >> 6) & 0x01) * coef_p[i + 1] + ((c >> 5) & 0x01) * coef_p[i + 2] + ((c >> 4) & 0x01) * coef_p[i + 3] + ((c >> 3) & 0x01) * coef_p[i + 4] + ((c >> 2) & 0x01) * coef_p[i + 5] + ((c >> 1) & 0x01) * coef_p[i + 6] + ((c ) & 0x01) * coef_p[i + 7]; data_index += channels; } return F; } #endif void convolve(uint32_t Signal[/* SignalLen */], unsigned short SignalLen, uint32_t Kernel[/* KernelLen */], unsigned short KernelLen, uint32_t Result[/* SignalLen + KernelLen - 1 */]) { uint16_t n; for (n = 0; n < SignalLen + KernelLen - 1; n++) { unsigned short kmin, kmax, k; Result[n] = 0; kmin = (n >= KernelLen - 1) ? n - (KernelLen - 1) : 0; kmax = (n < SignalLen - 1) ? n : SignalLen - 1; for (k = kmin; k <= kmax; k++) { Result[n] += Signal[k] * Kernel[n - k]; } } } void Open_PDM_Filter_Init(TPDMFilter_InitStruct *Param) { uint16_t i, j; int64_t sum = 0; uint8_t decimation = Param->Decimation; for (i = 0; i < SINCN; i++) { Param->Coef[i] = 0; Param->bit[i] = 0; } for (i = 0; i < decimation; i++) { sinc1[i] = 1; } Param->OldOut = Param->OldIn = Param->OldZ = 0; Param->LP_ALFA = (Param->LP_HZ != 0 ? (uint16_t) (Param->LP_HZ * 256 / (Param->LP_HZ + Param->Fs / (2 * 3.14159))) : 0); Param->HP_ALFA = (Param->HP_HZ != 0 ? (uint16_t) (Param->Fs * 256 / (2 * 3.14159 * Param->HP_HZ + Param->Fs)) : 0); Param->FilterLen = decimation * SINCN; sinc[0] = 0; sinc[decimation * SINCN - 1] = 0; convolve(sinc1, decimation, sinc1, decimation, sinc2); convolve(sinc2, decimation * 2 - 1, sinc1, decimation, &sinc[1]); for(j = 0; j < SINCN; j++) { for (i = 0; i < decimation; i++) { coef[j][i] = sinc[j * decimation + i]; sum += sinc[j * decimation + i]; } } sub_const = sum >> 1; div_const = sub_const * Param->MaxVolume / 32768 / FILTER_GAIN; div_const = (div_const == 0 ? 1 : div_const); #ifdef USE_LUT /* Look-Up Table. */ uint16_t c, d, s; for (s = 0; s < SINCN; s++) { uint32_t *coef_p = &coef[s][0]; for (c = 0; c < 256; c++) for (d = 0; d < decimation / 8; d++) lut[c][d][s] = ((c >> 7) ) * coef_p[d * 8 ] + ((c >> 6) & 0x01) * coef_p[d * 8 + 1] + ((c >> 5) & 0x01) * coef_p[d * 8 + 2] + ((c >> 4) & 0x01) * coef_p[d * 8 + 3] + ((c >> 3) & 0x01) * coef_p[d * 8 + 4] + ((c >> 2) & 0x01) * coef_p[d * 8 + 5] + ((c >> 1) & 0x01) * coef_p[d * 8 + 6] + ((c ) & 0x01) * coef_p[d * 8 + 7]; } #endif } void Open_PDM_Filter_64(uint8_t* data, uint16_t* dataOut, uint16_t volume, TPDMFilter_InitStruct *Param) { uint8_t i, data_out_index; uint8_t channels = Param->In_MicChannels; uint8_t data_inc = ((DECIMATION_MAX >> 4) * channels); int64_t Z, Z0, Z1, Z2; int64_t OldOut, OldIn, OldZ; OldOut = Param->OldOut; OldIn = Param->OldIn; OldZ = Param->OldZ; #ifdef USE_LUT uint8_t j = channels - 1; #endif for (i = 0, data_out_index = 0; i < Param->Fs / 1000; i++, data_out_index += channels) { #ifdef USE_LUT Z0 = filter_tables_64[j](data, 0); Z1 = filter_tables_64[j](data, 1); Z2 = filter_tables_64[j](data, 2); #else Z0 = filter_table(data, 0, Param); Z1 = filter_table(data, 1, Param); Z2 = filter_table(data, 2, Param); #endif Z = Param->Coef[1] + Z2 - sub_const; Param->Coef[1] = Param->Coef[0] + Z1; Param->Coef[0] = Z0; OldOut = (Param->HP_ALFA * (OldOut + Z - OldIn)) >> 8; OldIn = Z; OldZ = ((256 - Param->LP_ALFA) * OldZ + Param->LP_ALFA * OldOut) >> 8; Z = OldZ * volume; Z = RoundDiv(Z, div_const); Z = SaturaLH(Z, -32700, 32700); dataOut[data_out_index] = Z; data += data_inc; } Param->OldOut = OldOut; Param->OldIn = OldIn; Param->OldZ = OldZ; } void Open_PDM_Filter_128(uint8_t* data, uint16_t* dataOut, uint16_t volume, TPDMFilter_InitStruct *Param) { uint8_t i, data_out_index; uint8_t channels = Param->In_MicChannels; uint8_t data_inc = ((DECIMATION_MAX >> 3) * channels); int64_t Z, Z0, Z1, Z2; int64_t OldOut, OldIn, OldZ; OldOut = Param->OldOut; OldIn = Param->OldIn; OldZ = Param->OldZ; #ifdef USE_LUT uint8_t j = channels - 1; #endif for (i = 0, data_out_index = 0; i < Param->Fs / 1000; i++, data_out_index += channels) { #ifdef USE_LUT Z0 = filter_tables_128[j](data, 0); Z1 = filter_tables_128[j](data, 1); Z2 = filter_tables_128[j](data, 2); #else Z0 = filter_table(data, 0, Param); Z1 = filter_table(data, 1, Param); Z2 = filter_table(data, 2, Param); #endif Z = Param->Coef[1] + Z2 - sub_const; Param->Coef[1] = Param->Coef[0] + Z1; Param->Coef[0] = Z0; OldOut = (Param->HP_ALFA * (OldOut + Z - OldIn)) >> 8; OldIn = Z; OldZ = ((256 - Param->LP_ALFA) * OldZ + Param->LP_ALFA * OldOut) >> 8; Z = OldZ * volume; Z = RoundDiv(Z, div_const); Z = SaturaLH(Z, -32700, 32700); dataOut[data_out_index] = Z; data += data_inc; } Param->OldOut = OldOut; Param->OldIn = OldIn; Param->OldZ = OldZ; } ================================================ FILE: src/OpenPDM2PCM/OpenPDMFilter.h ================================================ /** ******************************************************************************* * @file OpenPDMFilter.h * @author CL * @version V1.0.0 * @date 9-September-2015 * @brief Header file for Open PDM audio software decoding Library. * This Library is used to decode and reconstruct the audio signal * produced by ST MEMS microphone (MP45Dxxx, MP34Dxxx). ******************************************************************************* * @attention * *

© COPYRIGHT 2018 STMicroelectronics

* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************* */ /* Define to prevent recursive inclusion -------------------------------------*/ #ifndef __OPENPDMFILTER_H #define __OPENPDMFILTER_H #ifdef __cplusplus extern "C" { #endif /* Includes ------------------------------------------------------------------*/ #include /* Definitions ---------------------------------------------------------------*/ /* * Enable to use a Look-Up Table to improve performances while using more FLASH * and RAM memory. * Note: Without Look-Up Table up to stereo@16KHz configuration is supported. */ #define USE_LUT #define SINCN 3 #define DECIMATION_MAX 128 #ifdef PICO_BUILD #define FILTER_GAIN Param->Gain #else #define FILTER_GAIN 16 #endif #define HTONS(A) ((((uint16_t)(A) & 0xff00) >> 8) | \ (((uint16_t)(A) & 0x00ff) << 8)) #define RoundDiv(a, b) (((a)>0)?(((a)+(b)/2)/(b)):(((a)-(b)/2)/(b))) #define SaturaLH(N, L, H) (((N)<(L))?(L):(((N)>(H))?(H):(N))) /* Types ---------------------------------------------------------------------*/ typedef struct { /* Public */ float LP_HZ; float HP_HZ; uint16_t Fs; uint8_t In_MicChannels; uint8_t Out_MicChannels; uint8_t Decimation; uint8_t MaxVolume; #ifdef PICO_BUILD uint8_t Gain; #endif /* Private */ uint32_t Coef[SINCN]; uint16_t FilterLen; int64_t OldOut, OldIn, OldZ; uint16_t LP_ALFA; uint16_t HP_ALFA; uint16_t bit[5]; uint16_t byte; } TPDMFilter_InitStruct; /* Exported functions ------------------------------------------------------- */ void Open_PDM_Filter_Init(TPDMFilter_InitStruct *init_struct); void Open_PDM_Filter_64(uint8_t* data, uint16_t* data_out, uint16_t mic_gain, TPDMFilter_InitStruct *init_struct); void Open_PDM_Filter_128(uint8_t* data, uint16_t* data_out, uint16_t mic_gain, TPDMFilter_InitStruct *init_struct); #ifdef __cplusplus } #endif #endif // __OPENPDMFILTER_H /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ ================================================ FILE: src/analog_microphone.c ================================================ /* * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * */ #include #include #include "hardware/adc.h" #include "hardware/clocks.h" #include "hardware/dma.h" #include "hardware/irq.h" #include "pico/analog_microphone.h" #define ANALOG_RAW_BUFFER_COUNT 2 static struct { struct analog_microphone_config config; int dma_channel; uint16_t* raw_buffer[ANALOG_RAW_BUFFER_COUNT]; volatile int raw_buffer_write_index; volatile int raw_buffer_read_index; uint buffer_size; int16_t bias; uint dma_irq; analog_samples_ready_handler_t samples_ready_handler; } analog_mic; static void analog_dma_handler(); int analog_microphone_init(const struct analog_microphone_config* config) { memset(&analog_mic, 0x00, sizeof(analog_mic)); memcpy(&analog_mic.config, config, sizeof(analog_mic.config)); if (config->gpio < 26 || config->gpio > 29) { return -1; } size_t raw_buffer_size = config->sample_buffer_size * sizeof(analog_mic.raw_buffer[0][0]); analog_mic.buffer_size = config->sample_buffer_size; analog_mic.bias = ((int16_t)((config->bias_voltage * 4095) / 3.3)); for (int i = 0; i < ANALOG_RAW_BUFFER_COUNT; i++) { analog_mic.raw_buffer[i] = malloc(raw_buffer_size); if (analog_mic.raw_buffer[i] == NULL) { analog_microphone_deinit(); return -1; } } analog_mic.dma_channel = dma_claim_unused_channel(true); if (analog_mic.dma_channel < 0) { analog_microphone_deinit(); return -1; } float clk_div = (clock_get_hz(clk_adc) / (1.0 * config->sample_rate)) - 1; dma_channel_config dma_channel_cfg = dma_channel_get_default_config(analog_mic.dma_channel); channel_config_set_transfer_data_size(&dma_channel_cfg, DMA_SIZE_16); channel_config_set_read_increment(&dma_channel_cfg, false); channel_config_set_write_increment(&dma_channel_cfg, true); channel_config_set_dreq(&dma_channel_cfg, DREQ_ADC); analog_mic.dma_irq = DMA_IRQ_0; dma_channel_configure( analog_mic.dma_channel, &dma_channel_cfg, analog_mic.raw_buffer[0], &adc_hw->fifo, analog_mic.buffer_size, false ); adc_gpio_init(config->gpio); adc_init(); adc_select_input(config->gpio - 26); adc_fifo_setup( true, // Write each completed conversion to the sample FIFO true, // Enable DMA data request (DREQ) 1, // DREQ (and IRQ) asserted when at least 1 sample present false, // We won't see the ERR bit because of 8 bit reads; disable. false // Don't shift each sample to 8 bits when pushing to FIFO ); adc_set_clkdiv(clk_div); } void analog_microphone_deinit() { for (int i = 0; i < ANALOG_RAW_BUFFER_COUNT; i++) { if (analog_mic.raw_buffer[i]) { free(analog_mic.raw_buffer[i]); analog_mic.raw_buffer[i] = NULL; } } if (analog_mic.dma_channel > -1) { dma_channel_unclaim(analog_mic.dma_channel); analog_mic.dma_channel = -1; } } int analog_microphone_start() { irq_set_enabled(analog_mic.dma_irq, true); irq_set_exclusive_handler(analog_mic.dma_irq, analog_dma_handler); if (analog_mic.dma_irq == DMA_IRQ_0) { dma_channel_set_irq0_enabled(analog_mic.dma_channel, true); } else if (analog_mic.dma_irq == DMA_IRQ_1) { dma_channel_set_irq1_enabled(analog_mic.dma_channel, true); } else { return -1; } analog_mic.raw_buffer_write_index = 0; analog_mic.raw_buffer_read_index = 0; dma_channel_transfer_to_buffer_now( analog_mic.dma_channel, analog_mic.raw_buffer[0], analog_mic.buffer_size ); adc_run(true); // start running the adc } void analog_microphone_stop() { adc_run(false); // stop running the adc dma_channel_abort(analog_mic.dma_channel); if (analog_mic.dma_irq == DMA_IRQ_0) { dma_channel_set_irq0_enabled(analog_mic.dma_channel, false); } else if (analog_mic.dma_irq == DMA_IRQ_1) { dma_channel_set_irq1_enabled(analog_mic.dma_channel, false); } irq_set_enabled(analog_mic.dma_irq, false); } static void analog_dma_handler() { // clear IRQ if (analog_mic.dma_irq == DMA_IRQ_0) { dma_hw->ints0 = (1u << analog_mic.dma_channel); } else if (analog_mic.dma_irq == DMA_IRQ_1) { dma_hw->ints1 = (1u << analog_mic.dma_channel); } // get the current buffer index analog_mic.raw_buffer_read_index = analog_mic.raw_buffer_write_index; // get the next capture index to send the dma to start analog_mic.raw_buffer_write_index = (analog_mic.raw_buffer_write_index + 1) % ANALOG_RAW_BUFFER_COUNT; // give the channel a new buffer to write to and re-trigger it dma_channel_transfer_to_buffer_now( analog_mic.dma_channel, analog_mic.raw_buffer[analog_mic.raw_buffer_write_index], analog_mic.buffer_size ); if (analog_mic.samples_ready_handler) { analog_mic.samples_ready_handler(); } } void analog_microphone_set_samples_ready_handler(analog_samples_ready_handler_t handler) { analog_mic.samples_ready_handler = handler; } int analog_microphone_read(int16_t* buffer, size_t samples) { if (samples > analog_mic.config.sample_buffer_size) { samples = analog_mic.config.sample_buffer_size; } if (analog_mic.raw_buffer_write_index == analog_mic.raw_buffer_read_index) { return 0; } uint16_t* in = analog_mic.raw_buffer[analog_mic.raw_buffer_read_index]; int16_t* out = buffer; int16_t bias = analog_mic.bias; analog_mic.raw_buffer_read_index++; for (int i = 0; i < samples; i++) { *out++ = *in++ - bias; } return samples; } ================================================ FILE: src/include/pico/analog_microphone.h ================================================ /* * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * */ #ifndef _PICO_ANALOG_MICROPHONE_H_ #define _PICO_ANALOG_MICROPHONE_H_ typedef void (*analog_samples_ready_handler_t)(void); struct analog_microphone_config { uint gpio; float bias_voltage; uint sample_rate; uint sample_buffer_size; }; int analog_microphone_init(const struct analog_microphone_config* config); void analog_microphone_deinit(); int analog_microphone_start(); void analog_microphone_stop(); void analog_microphone_set_samples_ready_handler(analog_samples_ready_handler_t handler); int analog_microphone_read(int16_t* buffer, size_t samples); #endif ================================================ FILE: src/include/pico/pdm_microphone.h ================================================ /* * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * */ #ifndef _PICO_PDM_MICROPHONE_H_ #define _PICO_PDM_MICROPHONE_H_ #include "hardware/pio.h" typedef void (*pdm_samples_ready_handler_t)(void); struct pdm_microphone_config { uint gpio_data; uint gpio_clk; PIO pio; uint pio_sm; uint sample_rate; uint sample_buffer_size; }; int pdm_microphone_init(const struct pdm_microphone_config* config); void pdm_microphone_deinit(); int pdm_microphone_start(); void pdm_microphone_stop(); void pdm_microphone_set_samples_ready_handler(pdm_samples_ready_handler_t handler); void pdm_microphone_set_filter_max_volume(uint8_t max_volume); void pdm_microphone_set_filter_gain(uint8_t gain); void pdm_microphone_set_filter_volume(uint16_t volume); int pdm_microphone_read(int16_t* buffer, size_t samples); #endif ================================================ FILE: src/pdm_microphone.c ================================================ /* * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * */ #include #include #include "hardware/clocks.h" #include "hardware/dma.h" #include "hardware/irq.h" #include "OpenPDM2PCM/OpenPDMFilter.h" #include "pdm_microphone.pio.h" #include "pico/pdm_microphone.h" #define PDM_DECIMATION 64 #define PDM_RAW_BUFFER_COUNT 2 static struct { struct pdm_microphone_config config; int dma_channel; uint8_t* raw_buffer[PDM_RAW_BUFFER_COUNT]; volatile int raw_buffer_write_index; volatile int raw_buffer_read_index; uint raw_buffer_size; uint dma_irq; TPDMFilter_InitStruct filter; uint16_t filter_volume; pdm_samples_ready_handler_t samples_ready_handler; } pdm_mic; static void pdm_dma_handler(); int pdm_microphone_init(const struct pdm_microphone_config* config) { memset(&pdm_mic, 0x00, sizeof(pdm_mic)); memcpy(&pdm_mic.config, config, sizeof(pdm_mic.config)); if (config->sample_buffer_size % (config->sample_rate / 1000)) { return -1; } pdm_mic.raw_buffer_size = config->sample_buffer_size * (PDM_DECIMATION / 8); for (int i = 0; i < PDM_RAW_BUFFER_COUNT; i++) { pdm_mic.raw_buffer[i] = malloc(pdm_mic.raw_buffer_size); if (pdm_mic.raw_buffer[i] == NULL) { pdm_microphone_deinit(); return -1; } } pdm_mic.dma_channel = dma_claim_unused_channel(true); if (pdm_mic.dma_channel < 0) { pdm_microphone_deinit(); return -1; } uint pio_sm_offset = pio_add_program(config->pio, &pdm_microphone_data_program); float clk_div = clock_get_hz(clk_sys) / (config->sample_rate * PDM_DECIMATION * 4.0); pdm_microphone_data_init( config->pio, config->pio_sm, pio_sm_offset, clk_div, config->gpio_data, config->gpio_clk ); dma_channel_config dma_channel_cfg = dma_channel_get_default_config(pdm_mic.dma_channel); channel_config_set_transfer_data_size(&dma_channel_cfg, DMA_SIZE_8); channel_config_set_read_increment(&dma_channel_cfg, false); channel_config_set_write_increment(&dma_channel_cfg, true); channel_config_set_dreq(&dma_channel_cfg, pio_get_dreq(config->pio, config->pio_sm, false)); pdm_mic.dma_irq = DMA_IRQ_0; dma_channel_configure( pdm_mic.dma_channel, &dma_channel_cfg, pdm_mic.raw_buffer[0], &config->pio->rxf[config->pio_sm], pdm_mic.raw_buffer_size, false ); pdm_mic.filter.Fs = config->sample_rate; pdm_mic.filter.LP_HZ = config->sample_rate / 2; pdm_mic.filter.HP_HZ = 10; pdm_mic.filter.In_MicChannels = 1; pdm_mic.filter.Out_MicChannels = 1; pdm_mic.filter.Decimation = PDM_DECIMATION; pdm_mic.filter.MaxVolume = 64; pdm_mic.filter.Gain = 16; pdm_mic.filter_volume = pdm_mic.filter.MaxVolume; } void pdm_microphone_deinit() { for (int i = 0; i < PDM_RAW_BUFFER_COUNT; i++) { if (pdm_mic.raw_buffer[i]) { free(pdm_mic.raw_buffer[i]); pdm_mic.raw_buffer[i] = NULL; } } if (pdm_mic.dma_channel > -1) { dma_channel_unclaim(pdm_mic.dma_channel); pdm_mic.dma_channel = -1; } } int pdm_microphone_start() { irq_set_enabled(pdm_mic.dma_irq, true); irq_set_exclusive_handler(pdm_mic.dma_irq, pdm_dma_handler); if (pdm_mic.dma_irq == DMA_IRQ_0) { dma_channel_set_irq0_enabled(pdm_mic.dma_channel, true); } else if (pdm_mic.dma_irq == DMA_IRQ_1) { dma_channel_set_irq1_enabled(pdm_mic.dma_channel, true); } else { return -1; } Open_PDM_Filter_Init(&pdm_mic.filter); pio_sm_set_enabled( pdm_mic.config.pio, pdm_mic.config.pio_sm, true ); pdm_mic.raw_buffer_write_index = 0; pdm_mic.raw_buffer_read_index = 0; dma_channel_transfer_to_buffer_now( pdm_mic.dma_channel, pdm_mic.raw_buffer[0], pdm_mic.raw_buffer_size ); pio_sm_set_enabled( pdm_mic.config.pio, pdm_mic.config.pio_sm, true ); } void pdm_microphone_stop() { pio_sm_set_enabled( pdm_mic.config.pio, pdm_mic.config.pio_sm, false ); dma_channel_abort(pdm_mic.dma_channel); if (pdm_mic.dma_irq == DMA_IRQ_0) { dma_channel_set_irq0_enabled(pdm_mic.dma_channel, false); } else if (pdm_mic.dma_irq == DMA_IRQ_1) { dma_channel_set_irq1_enabled(pdm_mic.dma_channel, false); } irq_set_enabled(pdm_mic.dma_irq, false); } static void pdm_dma_handler() { // clear IRQ if (pdm_mic.dma_irq == DMA_IRQ_0) { dma_hw->ints0 = (1u << pdm_mic.dma_channel); } else if (pdm_mic.dma_irq == DMA_IRQ_1) { dma_hw->ints1 = (1u << pdm_mic.dma_channel); } // get the current buffer index pdm_mic.raw_buffer_read_index = pdm_mic.raw_buffer_write_index; // get the next capture index to send the dma to start pdm_mic.raw_buffer_write_index = (pdm_mic.raw_buffer_write_index + 1) % PDM_RAW_BUFFER_COUNT; // give the channel a new buffer to write to and re-trigger it dma_channel_transfer_to_buffer_now( pdm_mic.dma_channel, pdm_mic.raw_buffer[pdm_mic.raw_buffer_write_index], pdm_mic.raw_buffer_size ); if (pdm_mic.samples_ready_handler) { pdm_mic.samples_ready_handler(); } } void pdm_microphone_set_samples_ready_handler(pdm_samples_ready_handler_t handler) { pdm_mic.samples_ready_handler = handler; } void pdm_microphone_set_filter_max_volume(uint8_t max_volume) { pdm_mic.filter.MaxVolume = max_volume; } void pdm_microphone_set_filter_gain(uint8_t gain) { pdm_mic.filter.Gain = gain; } void pdm_microphone_set_filter_volume(uint16_t volume) { pdm_mic.filter_volume = volume; } int pdm_microphone_read(int16_t* buffer, size_t samples) { int filter_stride = (pdm_mic.filter.Fs / 1000); samples = (samples / filter_stride) * filter_stride; if (samples > pdm_mic.config.sample_buffer_size) { samples = pdm_mic.config.sample_buffer_size; } if (pdm_mic.raw_buffer_write_index == pdm_mic.raw_buffer_read_index) { return 0; } uint8_t* in = pdm_mic.raw_buffer[pdm_mic.raw_buffer_read_index]; int16_t* out = buffer; pdm_mic.raw_buffer_read_index++; for (int i = 0; i < samples; i += filter_stride) { #if PDM_DECIMATION == 64 Open_PDM_Filter_64(in, out, pdm_mic.filter_volume, &pdm_mic.filter); #elif PDM_DECIMATION == 128 Open_PDM_Filter_128(in, out, pdm_mic.filter_volume, &pdm_mic.filter); #else #error "Unsupported PDM_DECIMATION value!" #endif in += filter_stride * (PDM_DECIMATION / 8); out += filter_stride; } return samples; } ================================================ FILE: src/pdm_microphone.pio ================================================ /* * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * */ .program pdm_microphone_data .side_set 1 .wrap_target nop side 0 in pins, 1 side 0 push iffull noblock side 1 nop side 1 .wrap % c-sdk { static inline void pdm_microphone_data_init(PIO pio, uint sm, uint offset, float clk_div, uint data_pin, uint clk_pin) { pio_sm_set_consecutive_pindirs(pio, sm, data_pin, 1, false); pio_sm_set_consecutive_pindirs(pio, sm, clk_pin, 1, true); pio_sm_config c = pdm_microphone_data_program_get_default_config(offset); sm_config_set_sideset_pins(&c, clk_pin); sm_config_set_in_pins(&c, data_pin); pio_gpio_init(pio, clk_pin); pio_gpio_init(pio, data_pin); sm_config_set_in_shift(&c, false, false, 8); sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); sm_config_set_clkdiv(&c, clk_div); pio_sm_init(pio, sm, offset, &c); } %}