1/*
2 * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 *
6 */
7
8#include <stdlib.h>
9#include <string.h>
10
11#include "hardware/adc.h"
12#include "hardware/clocks.h"
13#include "hardware/dma.h"
14#include "hardware/irq.h"
15
16#include "pico/analog_microphone.h"
17
18#define ANALOG_RAW_BUFFER_COUNT 2
19
20static struct {
21 struct analog_microphone_config config;
22 int dma_channel;
23 uint16_t* raw_buffer[ANALOG_RAW_BUFFER_COUNT];
24 volatile int raw_buffer_write_index;
25 volatile int raw_buffer_read_index;
26 uint buffer_size;
27 int16_t bias;
28 uint dma_irq;
29 analog_samples_ready_handler_t samples_ready_handler;
30} analog_mic;
31
32static void analog_dma_handler();
33
34int analog_microphone_init(const struct analog_microphone_config* config) {
35 memset(&analog_mic, 0x00, sizeof(analog_mic));
36 memcpy(&analog_mic.config, config, sizeof(analog_mic.config));
37
38 if (config->gpio < 26 || config->gpio > 29) {
39 return -1;
40 }
41
42 size_t raw_buffer_size = config->sample_buffer_size * sizeof(analog_mic.raw_buffer[0][0]);
43
44 analog_mic.buffer_size = config->sample_buffer_size;
45 analog_mic.bias = ((int16_t)((config->bias_voltage * 4095) / 3.3));
46
47 for (int i = 0; i < ANALOG_RAW_BUFFER_COUNT; i++) {
48 analog_mic.raw_buffer[i] = malloc(raw_buffer_size);
49 if (analog_mic.raw_buffer[i] == NULL) {
50 analog_microphone_deinit();
51
52 return -1;
53 }
54 }
55
56 analog_mic.dma_channel = dma_claim_unused_channel(true);
57 if (analog_mic.dma_channel < 0) {
58 analog_microphone_deinit();
59
60 return -1;
61 }
62
63 float clk_div = (clock_get_hz(clk_adc) / (1.0 * config->sample_rate)) - 1;
64
65 dma_channel_config dma_channel_cfg = dma_channel_get_default_config(analog_mic.dma_channel);
66
67 channel_config_set_transfer_data_size(&dma_channel_cfg, DMA_SIZE_16);
68 channel_config_set_read_increment(&dma_channel_cfg, false);
69 channel_config_set_write_increment(&dma_channel_cfg, true);
70 channel_config_set_dreq(&dma_channel_cfg, DREQ_ADC);
71
72 analog_mic.dma_irq = DMA_IRQ_0;
73
74 dma_channel_configure(
75 analog_mic.dma_channel,
76 &dma_channel_cfg,
77 analog_mic.raw_buffer[0],
78 &adc_hw->fifo,
79 analog_mic.buffer_size,
80 false
81 );
82
83 adc_gpio_init(config->gpio);
84
85 adc_init();
86 adc_select_input(config->gpio - 26);
87 adc_fifo_setup(
88 true, // Write each completed conversion to the sample FIFO
89 true, // Enable DMA data request (DREQ)
90 1, // DREQ (and IRQ) asserted when at least 1 sample present
91 false, // We won't see the ERR bit because of 8 bit reads; disable.
92 false // Don't shift each sample to 8 bits when pushing to FIFO
93 );
94
95 adc_set_clkdiv(clk_div);
96}
97
98void analog_microphone_deinit() {
99 for (int i = 0; i < ANALOG_RAW_BUFFER_COUNT; i++) {
100 if (analog_mic.raw_buffer[i]) {
101 free(analog_mic.raw_buffer[i]);
102
103 analog_mic.raw_buffer[i] = NULL;
104 }
105 }
106
107 if (analog_mic.dma_channel > -1) {
108 dma_channel_unclaim(analog_mic.dma_channel);
109
110 analog_mic.dma_channel = -1;
111 }
112}
113
114int analog_microphone_start() {
115 irq_set_enabled(analog_mic.dma_irq, true);
116 irq_set_exclusive_handler(analog_mic.dma_irq, analog_dma_handler);
117
118 if (analog_mic.dma_irq == DMA_IRQ_0) {
119 dma_channel_set_irq0_enabled(analog_mic.dma_channel, true);
120 } else if (analog_mic.dma_irq == DMA_IRQ_1) {
121 dma_channel_set_irq1_enabled(analog_mic.dma_channel, true);
122 } else {
123 return -1;
124 }
125
126 analog_mic.raw_buffer_write_index = 0;
127 analog_mic.raw_buffer_read_index = 0;
128
129 dma_channel_transfer_to_buffer_now(
130 analog_mic.dma_channel,
131 analog_mic.raw_buffer[0],
132 analog_mic.buffer_size
133 );
134
135 adc_run(true); // start running the adc
136}
137
138void analog_microphone_stop() {
139 adc_run(false); // stop running the adc
140
141 dma_channel_abort(analog_mic.dma_channel);
142
143 if (analog_mic.dma_irq == DMA_IRQ_0) {
144 dma_channel_set_irq0_enabled(analog_mic.dma_channel, false);
145 } else if (analog_mic.dma_irq == DMA_IRQ_1) {
146 dma_channel_set_irq1_enabled(analog_mic.dma_channel, false);
147 }
148
149 irq_set_enabled(analog_mic.dma_irq, false);
150}
151
152static void analog_dma_handler() {
153 // clear IRQ
154 if (analog_mic.dma_irq == DMA_IRQ_0) {
155 dma_hw->ints0 = (1u << analog_mic.dma_channel);
156 } else if (analog_mic.dma_irq == DMA_IRQ_1) {
157 dma_hw->ints1 = (1u << analog_mic.dma_channel);
158 }
159
160 // get the current buffer index
161 analog_mic.raw_buffer_read_index = analog_mic.raw_buffer_write_index;
162
163 // get the next capture index to send the dma to start
164 analog_mic.raw_buffer_write_index = (analog_mic.raw_buffer_write_index + 1) % ANALOG_RAW_BUFFER_COUNT;
165
166 // give the channel a new buffer to write to and re-trigger it
167 dma_channel_transfer_to_buffer_now(
168 analog_mic.dma_channel,
169 analog_mic.raw_buffer[analog_mic.raw_buffer_write_index],
170 analog_mic.buffer_size
171 );
172
173 if (analog_mic.samples_ready_handler) {
174 analog_mic.samples_ready_handler();
175 }
176}
177
178void analog_microphone_set_samples_ready_handler(analog_samples_ready_handler_t handler) {
179 analog_mic.samples_ready_handler = handler;
180}
181
182int analog_microphone_read(int16_t* buffer, size_t samples) {
183 if (samples > analog_mic.config.sample_buffer_size) {
184 samples = analog_mic.config.sample_buffer_size;
185 }
186
187 if (analog_mic.raw_buffer_write_index == analog_mic.raw_buffer_read_index) {
188 return 0;
189 }
190
191 uint16_t* in = analog_mic.raw_buffer[analog_mic.raw_buffer_read_index];
192 int16_t* out = buffer;
193 int16_t bias = analog_mic.bias;
194
195 analog_mic.raw_buffer_read_index++;
196
197 for (int i = 0; i < samples; i++) {
198 *out++ = *in++ - bias;
199 }
200
201 return samples;
202}