| 
									
										
										
										
											2022-10-21 19:35:15 +02:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * The MIT License (MIT) | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright (c) 2020 Jerzy Kasenberg | 
					
						
							|  |  |  |  * Copyright (c) 2022 Angel Molina (angelmolinu@gmail.com) | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * 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 <stdio.h>
 | 
					
						
							|  |  |  | #include <string.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-03 15:44:05 +07:00
										 |  |  | #include "bsp/board_api.h"
 | 
					
						
							| 
									
										
										
										
											2022-10-21 19:35:15 +02:00
										 |  |  | #include "tusb.h"
 | 
					
						
							|  |  |  | #include "usb_descriptors.h"
 | 
					
						
							|  |  |  | #include "common.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //--------------------------------------------------------------------+
 | 
					
						
							|  |  |  | // MACRO CONSTANT TYPEDEF PROTOTYPES
 | 
					
						
							|  |  |  | //--------------------------------------------------------------------+
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // List of supported sample rates
 | 
					
						
							|  |  |  | const uint32_t sample_rates[] = {44100, 48000}; | 
					
						
							|  |  |  | uint32_t current_sample_rate  = 44100; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define N_SAMPLE_RATES  TU_ARRAY_SIZE(sample_rates)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | uint32_t blink_interval_ms = BLINK_NOT_MOUNTED; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Audio controls
 | 
					
						
							|  |  |  | // Current states
 | 
					
						
							|  |  |  | int8_t mute[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1];       // +1 for master channel 0
 | 
					
						
							|  |  |  | int16_t volume[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1];    // +1 for master channel 0
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Buffer for microphone data
 | 
					
						
							|  |  |  | int32_t mic_buf[CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ / 4]; | 
					
						
							|  |  |  | // Buffer for speaker data
 | 
					
						
							|  |  |  | int32_t spk_buf[CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ / 4]; | 
					
						
							|  |  |  | // Speaker data size received in the last frame
 | 
					
						
							|  |  |  | int spk_data_size; | 
					
						
							|  |  |  | // Resolution per format
 | 
					
						
							|  |  |  | const uint8_t resolutions_per_format[CFG_TUD_AUDIO_FUNC_1_N_FORMATS] = {CFG_TUD_AUDIO_FUNC_1_FORMAT_1_RESOLUTION_RX}; | 
					
						
							|  |  |  | // Current resolution, update on format change
 | 
					
						
							|  |  |  | uint8_t current_resolution; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Helper for clock get requests
 | 
					
						
							|  |  |  | static bool tud_audio_clock_get_request(uint8_t rhport, audio_control_request_t const *request) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   TU_ASSERT(request->bEntityID == UAC2_ENTITY_CLOCK); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (request->bControlSelector == AUDIO_CS_CTRL_SAM_FREQ) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     if (request->bRequest == AUDIO_CS_REQ_CUR) | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2024-04-22 16:17:22 +07:00
										 |  |  |       TU_LOG1("Clock get current freq %" PRIu32 "\r\n", current_sample_rate); | 
					
						
							| 
									
										
										
										
											2022-10-21 19:35:15 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |       audio_control_cur_4_t curf = { (int32_t) tu_htole32(current_sample_rate) }; | 
					
						
							|  |  |  |       return tud_audio_buffer_and_schedule_control_xfer(rhport, (tusb_control_request_t const *)request, &curf, sizeof(curf)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else if (request->bRequest == AUDIO_CS_REQ_RANGE) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       audio_control_range_4_n_t(N_SAMPLE_RATES) rangef = | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         .wNumSubRanges = tu_htole16(N_SAMPLE_RATES) | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |       TU_LOG1("Clock get %d freq ranges\r\n", N_SAMPLE_RATES); | 
					
						
							|  |  |  |       for(uint8_t i = 0; i < N_SAMPLE_RATES; i++) | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         rangef.subrange[i].bMin = (int32_t) sample_rates[i]; | 
					
						
							|  |  |  |         rangef.subrange[i].bMax = (int32_t) sample_rates[i]; | 
					
						
							|  |  |  |         rangef.subrange[i].bRes = 0; | 
					
						
							|  |  |  |         TU_LOG1("Range %d (%d, %d, %d)\r\n", i, (int)rangef.subrange[i].bMin, (int)rangef.subrange[i].bMax, (int)rangef.subrange[i].bRes); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return tud_audio_buffer_and_schedule_control_xfer(rhport, (tusb_control_request_t const *)request, &rangef, sizeof(rangef)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   else if (request->bControlSelector == AUDIO_CS_CTRL_CLK_VALID && | 
					
						
							|  |  |  |            request->bRequest == AUDIO_CS_REQ_CUR) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     audio_control_cur_1_t cur_valid = { .bCur = 1 }; | 
					
						
							|  |  |  |     TU_LOG1("Clock get is valid %u\r\n", cur_valid.bCur); | 
					
						
							|  |  |  |     return tud_audio_buffer_and_schedule_control_xfer(rhport, (tusb_control_request_t const *)request, &cur_valid, sizeof(cur_valid)); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   TU_LOG1("Clock get request not supported, entity = %u, selector = %u, request = %u\r\n", | 
					
						
							|  |  |  |           request->bEntityID, request->bControlSelector, request->bRequest); | 
					
						
							|  |  |  |   return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Helper for clock set requests
 | 
					
						
							|  |  |  | static bool tud_audio_clock_set_request(uint8_t rhport, audio_control_request_t const *request, uint8_t const *buf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   (void)rhport; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   TU_ASSERT(request->bEntityID == UAC2_ENTITY_CLOCK); | 
					
						
							|  |  |  |   TU_VERIFY(request->bRequest == AUDIO_CS_REQ_CUR); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (request->bControlSelector == AUDIO_CS_CTRL_SAM_FREQ) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     TU_VERIFY(request->wLength == sizeof(audio_control_cur_4_t)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     current_sample_rate = (uint32_t) ((audio_control_cur_4_t const *)buf)->bCur; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-22 16:17:22 +07:00
										 |  |  |     TU_LOG1("Clock set current freq: %" PRIu32 "\r\n", current_sample_rate); | 
					
						
							| 
									
										
										
										
											2022-10-21 19:35:15 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   else | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     TU_LOG1("Clock set request not supported, entity = %u, selector = %u, request = %u\r\n", | 
					
						
							|  |  |  |             request->bEntityID, request->bControlSelector, request->bRequest); | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Helper for feature unit get requests
 | 
					
						
							|  |  |  | static bool tud_audio_feature_unit_get_request(uint8_t rhport, audio_control_request_t const *request) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   TU_ASSERT(request->bEntityID == UAC2_ENTITY_SPK_FEATURE_UNIT); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (request->bControlSelector == AUDIO_FU_CTRL_MUTE && request->bRequest == AUDIO_CS_REQ_CUR) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     audio_control_cur_1_t mute1 = { .bCur = mute[request->bChannelNumber] }; | 
					
						
							|  |  |  |     TU_LOG1("Get channel %u mute %d\r\n", request->bChannelNumber, mute1.bCur); | 
					
						
							|  |  |  |     return tud_audio_buffer_and_schedule_control_xfer(rhport, (tusb_control_request_t const *)request, &mute1, sizeof(mute1)); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-12-01 14:03:43 +01:00
										 |  |  |   else if (request->bControlSelector == AUDIO_FU_CTRL_VOLUME) | 
					
						
							| 
									
										
										
										
											2022-10-21 19:35:15 +02:00
										 |  |  |   { | 
					
						
							|  |  |  |     if (request->bRequest == AUDIO_CS_REQ_RANGE) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       audio_control_range_2_n_t(1) range_vol = { | 
					
						
							|  |  |  |         .wNumSubRanges = tu_htole16(1), | 
					
						
							|  |  |  |         .subrange[0] = { .bMin = tu_htole16(-VOLUME_CTRL_50_DB), tu_htole16(VOLUME_CTRL_0_DB), tu_htole16(256) } | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |       TU_LOG1("Get channel %u volume range (%d, %d, %u) dB\r\n", request->bChannelNumber, | 
					
						
							|  |  |  |               range_vol.subrange[0].bMin / 256, range_vol.subrange[0].bMax / 256, range_vol.subrange[0].bRes / 256); | 
					
						
							|  |  |  |       return tud_audio_buffer_and_schedule_control_xfer(rhport, (tusb_control_request_t const *)request, &range_vol, sizeof(range_vol)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else if (request->bRequest == AUDIO_CS_REQ_CUR) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       audio_control_cur_2_t cur_vol = { .bCur = tu_htole16(volume[request->bChannelNumber]) }; | 
					
						
							|  |  |  |       TU_LOG1("Get channel %u volume %d dB\r\n", request->bChannelNumber, cur_vol.bCur / 256); | 
					
						
							|  |  |  |       return tud_audio_buffer_and_schedule_control_xfer(rhport, (tusb_control_request_t const *)request, &cur_vol, sizeof(cur_vol)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   TU_LOG1("Feature unit get request not supported, entity = %u, selector = %u, request = %u\r\n", | 
					
						
							|  |  |  |           request->bEntityID, request->bControlSelector, request->bRequest); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Helper for feature unit set requests
 | 
					
						
							|  |  |  | static bool tud_audio_feature_unit_set_request(uint8_t rhport, audio_control_request_t const *request, uint8_t const *buf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   (void)rhport; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   TU_ASSERT(request->bEntityID == UAC2_ENTITY_SPK_FEATURE_UNIT); | 
					
						
							|  |  |  |   TU_VERIFY(request->bRequest == AUDIO_CS_REQ_CUR); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (request->bControlSelector == AUDIO_FU_CTRL_MUTE) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     TU_VERIFY(request->wLength == sizeof(audio_control_cur_1_t)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     mute[request->bChannelNumber] = ((audio_control_cur_1_t const *)buf)->bCur; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     TU_LOG1("Set channel %d Mute: %d\r\n", request->bChannelNumber, mute[request->bChannelNumber]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   else if (request->bControlSelector == AUDIO_FU_CTRL_VOLUME) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     TU_VERIFY(request->wLength == sizeof(audio_control_cur_2_t)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     volume[request->bChannelNumber] = ((audio_control_cur_2_t const *)buf)->bCur; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     TU_LOG1("Set channel %d volume: %d dB\r\n", request->bChannelNumber, volume[request->bChannelNumber] / 256); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   else | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     TU_LOG1("Feature unit set request not supported, entity = %u, selector = %u, request = %u\r\n", | 
					
						
							|  |  |  |             request->bEntityID, request->bControlSelector, request->bRequest); | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //--------------------------------------------------------------------+
 | 
					
						
							|  |  |  | // Application Callback API Implementations
 | 
					
						
							|  |  |  | //--------------------------------------------------------------------+
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // 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) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   audio_control_request_t const *request = (audio_control_request_t const *)p_request; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (request->bEntityID == UAC2_ENTITY_CLOCK) | 
					
						
							|  |  |  |     return tud_audio_clock_get_request(rhport, request); | 
					
						
							|  |  |  |   if (request->bEntityID == UAC2_ENTITY_SPK_FEATURE_UNIT) | 
					
						
							|  |  |  |     return tud_audio_feature_unit_get_request(rhport, request); | 
					
						
							|  |  |  |   else | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     TU_LOG1("Get request not handled, entity = %d, selector = %d, request = %d\r\n", | 
					
						
							|  |  |  |             request->bEntityID, request->bControlSelector, request->bRequest); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // 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 *buf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   audio_control_request_t const *request = (audio_control_request_t const *)p_request; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (request->bEntityID == UAC2_ENTITY_SPK_FEATURE_UNIT) | 
					
						
							|  |  |  |     return tud_audio_feature_unit_set_request(rhport, request, buf); | 
					
						
							|  |  |  |   if (request->bEntityID == UAC2_ENTITY_CLOCK) | 
					
						
							|  |  |  |     return tud_audio_clock_set_request(rhport, request, buf); | 
					
						
							|  |  |  |   TU_LOG1("Set request not handled, entity = %d, selector = %d, request = %d\r\n", | 
					
						
							|  |  |  |           request->bEntityID, request->bControlSelector, request->bRequest); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool tud_audio_set_itf_close_EP_cb(uint8_t rhport, tusb_control_request_t const * p_request) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   (void)rhport; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   uint8_t const itf = tu_u16_low(tu_le16toh(p_request->wIndex)); | 
					
						
							|  |  |  |   uint8_t const alt = tu_u16_low(tu_le16toh(p_request->wValue)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (ITF_NUM_AUDIO_STREAMING_SPK == itf && alt == 0) { | 
					
						
							|  |  |  |     // Audio streaming stop
 | 
					
						
							|  |  |  |     blink_interval_ms = BLINK_MOUNTED; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool tud_audio_set_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   (void)rhport; | 
					
						
							|  |  |  |   uint8_t const itf = tu_u16_low(tu_le16toh(p_request->wIndex)); | 
					
						
							|  |  |  |   uint8_t const alt = tu_u16_low(tu_le16toh(p_request->wValue)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   TU_LOG2("Set interface %d alt %d\r\n", itf, alt); | 
					
						
							|  |  |  |   if (ITF_NUM_AUDIO_STREAMING_SPK == itf && alt != 0) { | 
					
						
							|  |  |  |     // Audio streaming start
 | 
					
						
							|  |  |  |     blink_interval_ms = BLINK_STREAMING; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Clear buffer when streaming format is changed
 | 
					
						
							|  |  |  |   spk_data_size = 0; | 
					
						
							|  |  |  |   if(alt != 0) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     current_resolution = resolutions_per_format[alt-1]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool tud_audio_rx_done_pre_read_cb(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   (void)rhport; | 
					
						
							|  |  |  |   (void)func_id; | 
					
						
							|  |  |  |   (void)ep_out; | 
					
						
							|  |  |  |   (void)cur_alt_setting; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   spk_data_size = tud_audio_read(spk_buf, n_bytes_received); | 
					
						
							|  |  |  |   tud_audio_write(spk_buf, n_bytes_received); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // This callback could be used to fill microphone data separately
 | 
					
						
							|  |  |  |   return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //--------------------------------------------------------------------+
 | 
					
						
							|  |  |  | // BLINKING TASK
 | 
					
						
							|  |  |  | //--------------------------------------------------------------------+
 | 
					
						
							|  |  |  | void led_blinking_task(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   static uint32_t start_ms = 0; | 
					
						
							|  |  |  |   static bool led_state = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Blink every interval ms
 | 
					
						
							|  |  |  |   if (board_millis() - start_ms < blink_interval_ms) return; | 
					
						
							|  |  |  |   start_ms += blink_interval_ms; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   board_led_write(led_state); | 
					
						
							|  |  |  |   led_state = 1 - led_state; | 
					
						
							|  |  |  | } |