|
24 | 24 | * THE SOFTWARE.
|
25 | 25 | */
|
26 | 26 |
|
| 27 | +#include <math.h> |
| 28 | +#include <string.h> |
| 29 | + |
27 | 30 | #include "common-hal/microcontroller/Pin.h"
|
28 | 31 | #include "common-hal/audiobusio/I2SOut.h"
|
| 32 | +#include "shared-bindings/audiobusio/I2SOut.h" |
| 33 | +#include "shared-module/audiocore/__init__.h" |
29 | 34 |
|
30 | 35 | #include "py/obj.h"
|
31 | 36 | #include "py/runtime.h"
|
32 | 37 |
|
| 38 | +static audiobusio_i2sout_obj_t *instance; |
| 39 | + |
| 40 | +struct { int16_t l, r; } static_sample16 = {0x8000, 0x8000}; |
| 41 | +struct { uint8_t l1, r1, l2, r2; } static_sample8 = {0x80, 0x80, 0x80, 0x80}; |
| 42 | + |
| 43 | +struct frequency_info { uint32_t RATIO; uint32_t MCKFREQ; int sample_rate; float abserr; }; |
| 44 | +struct ratio_info { uint32_t RATIO; int16_t divisor; bool can_16bit; }; |
| 45 | +struct ratio_info ratios[] = { |
| 46 | + { I2S_CONFIG_RATIO_RATIO_32X, 32, true }, |
| 47 | + { I2S_CONFIG_RATIO_RATIO_48X, 48, false }, |
| 48 | + { I2S_CONFIG_RATIO_RATIO_64X, 64, true }, |
| 49 | + { I2S_CONFIG_RATIO_RATIO_96X, 96, true }, |
| 50 | + { I2S_CONFIG_RATIO_RATIO_128X, 128, true }, |
| 51 | + { I2S_CONFIG_RATIO_RATIO_192X, 192, true }, |
| 52 | + { I2S_CONFIG_RATIO_RATIO_256X, 256, true }, |
| 53 | + { I2S_CONFIG_RATIO_RATIO_384X, 384, true }, |
| 54 | + { I2S_CONFIG_RATIO_RATIO_512X, 512, true }, |
| 55 | +}; |
| 56 | + |
| 57 | +struct mclk_info { uint32_t MCKFREQ; int divisor; }; |
| 58 | +struct mclk_info mclks[] = { |
| 59 | + { I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV8, 8 }, |
| 60 | + { I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV10, 10 }, |
| 61 | + { I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV11, 11 }, |
| 62 | + { I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV15, 15 }, |
| 63 | + { I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV16, 16 }, |
| 64 | + { I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV21, 21 }, |
| 65 | + { I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV23, 23 }, |
| 66 | + { I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV31, 31 }, |
| 67 | + { I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV42, 42 }, |
| 68 | + { I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV63, 63 }, |
| 69 | + { I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV125, 125 }, |
| 70 | +}; |
| 71 | + |
| 72 | +static void calculate_ratio_info(uint32_t target_sample_rate, struct frequency_info *info, |
| 73 | + int ratio_index, int mclk_index) { |
| 74 | + info->RATIO = ratios[ratio_index].RATIO; |
| 75 | + info->MCKFREQ = mclks[mclk_index].MCKFREQ; |
| 76 | + info->sample_rate = 32000000 |
| 77 | + / ratios[ratio_index].divisor / mclks[mclk_index].divisor; |
| 78 | + info->abserr = fabsf(1.0f * target_sample_rate - info->sample_rate) |
| 79 | + / target_sample_rate; |
| 80 | +} |
| 81 | + |
| 82 | +void choose_i2s_clocking(audiobusio_i2sout_obj_t *self, uint32_t sample_rate) { |
| 83 | + struct frequency_info best = {0, 0, 0, 1.0}; |
| 84 | + for (size_t ri=0; ri<sizeof(ratios) / sizeof(ratios[0]); ri++) { |
| 85 | + if (NRF_I2S->CONFIG.SWIDTH == I2S_CONFIG_SWIDTH_SWIDTH_16Bit |
| 86 | + && !ratios[ri].can_16bit) { |
| 87 | + continue; |
| 88 | + } |
| 89 | + |
| 90 | + for (size_t mi=0; mi<sizeof(mclks) / sizeof(mclks[0]); mi++) { |
| 91 | + struct frequency_info info = {0, 0, 1.0}; |
| 92 | + calculate_ratio_info(sample_rate, &info, ri, mi); |
| 93 | + if (info.abserr < best.abserr) { |
| 94 | + best = info; |
| 95 | + } |
| 96 | +#ifdef DEBUG_CLOCKING |
| 97 | + mp_printf(&mp_plat_print, |
| 98 | + "RATIO=%3d MCKFREQ=%08x rate=%d abserr=%.4f\n", |
| 99 | + info.RATIO, info.MCKFREQ, info.sample_rate, |
| 100 | + (double)info.abserr); |
| 101 | +#endif |
| 102 | + } |
| 103 | + } |
| 104 | + NRF_I2S->CONFIG.RATIO = best.RATIO; |
| 105 | + NRF_I2S->CONFIG.MCKFREQ = best.MCKFREQ; |
| 106 | + self->sample_rate = best.sample_rate; |
| 107 | +} |
| 108 | + |
| 109 | +static void i2s_buffer_fill(audiobusio_i2sout_obj_t* self) { |
| 110 | + void *buffer = self->buffers[self->next_buffer]; |
| 111 | + NRF_I2S->TXD.PTR = (uintptr_t)buffer; |
| 112 | + self->next_buffer = !self->next_buffer; |
| 113 | + size_t bytesleft = self->buffer_length; |
| 114 | + |
| 115 | + if (self->paused || self->stopping) { |
| 116 | + if (self->stopping) { |
| 117 | + NRF_I2S->TASKS_STOP = 1; |
| 118 | + self->playing = false; |
| 119 | + } |
| 120 | +stopping: ; |
| 121 | + uint32_t *bp = (uint32_t*)buffer; |
| 122 | + uint32_t *be = (uint32_t*)(buffer + bytesleft); |
| 123 | + for (; bp != be; ) |
| 124 | + *bp++ = self->hold_value; |
| 125 | + return; |
| 126 | + } |
| 127 | + |
| 128 | + while (bytesleft) { |
| 129 | + if (self->sample_data == self->sample_end) { |
| 130 | + uint32_t sample_buffer_length; |
| 131 | + audioio_get_buffer_result_t get_buffer_result = |
| 132 | + audiosample_get_buffer(self->sample, false, 0, |
| 133 | + &self->sample_data, &sample_buffer_length); |
| 134 | + self->sample_end = self->sample_data + sample_buffer_length; |
| 135 | + if (get_buffer_result == GET_BUFFER_DONE) { |
| 136 | + if (self->loop) { |
| 137 | + audiosample_reset_buffer(self->sample, false, 0); |
| 138 | + } else { |
| 139 | + self->stopping = true; |
| 140 | + goto stopping; |
| 141 | + } |
| 142 | + } |
| 143 | + } |
| 144 | + uint16_t bytecount = MIN(bytesleft, (size_t)(self->sample_end - self->sample_data)); |
| 145 | + if (self->samples_signed) { |
| 146 | + memcpy(buffer, self->sample_data, bytecount); |
| 147 | + } else if (self->bytes_per_sample == 2) { |
| 148 | + uint16_t *bp = (uint16_t*)buffer; |
| 149 | + uint16_t *be = (uint16_t*)(buffer + bytecount); |
| 150 | + uint16_t *sp = (uint16_t*)self->sample_data; |
| 151 | + for (; bp != be; bp++) { |
| 152 | + *bp++ = *sp++ + 0x8000; |
| 153 | + } |
| 154 | + } else { |
| 155 | + uint8_t *bp = (uint8_t*)buffer; |
| 156 | + uint8_t *be = (uint8_t*)(buffer + bytecount); |
| 157 | + uint8_t *sp = (uint8_t*)self->sample_data; |
| 158 | + for (; bp != be; bp++) { |
| 159 | + *bp++ = *sp++ + 0x80; |
| 160 | + } |
| 161 | + } |
| 162 | + buffer += bytecount; |
| 163 | + self->sample_data += bytecount; |
| 164 | + bytesleft -= bytecount; |
| 165 | + } |
| 166 | + if (self->bytes_per_sample == 1 && self->channel_count == 1) { |
| 167 | + self->hold_value = 0x01010101 * *(uint8_t*)(buffer-1); |
| 168 | + } else if (self->bytes_per_sample == 2 && self->channel_count == 2) { |
| 169 | + self->hold_value = *(uint32_t*)(buffer-4); |
| 170 | + } else { |
| 171 | + self->hold_value = 0x00010001 * *(uint16_t*)(buffer-2); |
| 172 | + } |
| 173 | +} |
| 174 | + |
33 | 175 | void common_hal_audiobusio_i2sout_construct(audiobusio_i2sout_obj_t* self,
|
34 | 176 | const mcu_pin_obj_t* bit_clock, const mcu_pin_obj_t* word_select,
|
35 | 177 | const mcu_pin_obj_t* data, bool left_justified) {
|
36 |
| - mp_raise_NotImplementedError(NULL); |
| 178 | + if (instance) |
| 179 | + mp_raise_RuntimeError(translate("Device in use")); |
| 180 | + instance = self; |
| 181 | + |
| 182 | + claim_pin(bit_clock); |
| 183 | + claim_pin(word_select); |
| 184 | + claim_pin(data); |
| 185 | + |
| 186 | + NRF_I2S->PSEL.SCK = self->bit_clock_pin_number = bit_clock->number; |
| 187 | + NRF_I2S->PSEL.LRCK = self->word_select_pin_number = word_select->number; |
| 188 | + NRF_I2S->PSEL.SDOUT = self->data_pin_number = data->number; |
| 189 | + |
| 190 | + NRF_I2S->CONFIG.MODE = I2S_CONFIG_MODE_MODE_Master; |
| 191 | + NRF_I2S->CONFIG.RXEN = I2S_CONFIG_RXEN_RXEN_Disabled; |
| 192 | + NRF_I2S->CONFIG.TXEN = I2S_CONFIG_TXEN_TXEN_Enabled; |
| 193 | + NRF_I2S->CONFIG.MCKEN = I2S_CONFIG_MCKEN_MCKEN_Enabled; |
| 194 | + NRF_I2S->CONFIG.SWIDTH = I2S_CONFIG_SWIDTH_SWIDTH_16Bit; |
| 195 | + |
| 196 | + NRF_I2S->CONFIG.ALIGN = I2S_CONFIG_ALIGN_ALIGN_Left; |
| 197 | + NRF_I2S->CONFIG.FORMAT = left_justified ? I2S_CONFIG_FORMAT_FORMAT_Aligned |
| 198 | + : I2S_CONFIG_FORMAT_FORMAT_I2S; |
37 | 199 | }
|
38 | 200 |
|
39 | 201 | bool common_hal_audiobusio_i2sout_deinited(audiobusio_i2sout_obj_t* self) {
|
40 |
| - mp_raise_NotImplementedError(NULL); |
| 202 | + return self->data_pin_number == 0xff; |
41 | 203 | }
|
42 | 204 |
|
43 | 205 | void common_hal_audiobusio_i2sout_deinit(audiobusio_i2sout_obj_t* self) {
|
44 |
| - mp_raise_NotImplementedError(NULL); |
| 206 | + if (common_hal_audiobusio_i2sout_deinited(self)) { |
| 207 | + return; |
| 208 | + } |
| 209 | + reset_pin_number(self->bit_clock_pin_number); |
| 210 | + self->bit_clock_pin_number = 0xff; |
| 211 | + reset_pin_number(self->word_select_pin_number); |
| 212 | + self->word_select_pin_number = 0xff; |
| 213 | + reset_pin_number(self->data_pin_number); |
| 214 | + self->data_pin_number = 0xff; |
| 215 | + instance = NULL; |
45 | 216 | }
|
46 | 217 |
|
47 | 218 | void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t* self,
|
48 | 219 | mp_obj_t sample, bool loop) {
|
49 |
| - mp_raise_NotImplementedError(NULL); |
| 220 | + if (common_hal_audiobusio_i2sout_get_playing(self)) { |
| 221 | + common_hal_audiobusio_i2sout_stop(self); |
| 222 | + } |
| 223 | + |
| 224 | + self->sample = sample; |
| 225 | + self->loop = loop; |
| 226 | + uint32_t sample_rate = audiosample_sample_rate(sample); |
| 227 | + self->bytes_per_sample = audiosample_bits_per_sample(sample) / 8; |
| 228 | + |
| 229 | + uint32_t max_buffer_length; |
| 230 | + bool single_buffer, samples_signed; |
| 231 | + audiosample_get_buffer_structure(sample, /* single channel */ false, |
| 232 | + &single_buffer, &samples_signed, &max_buffer_length, |
| 233 | + &self->channel_count); |
| 234 | + self->single_buffer = single_buffer; |
| 235 | + self->samples_signed = samples_signed; |
| 236 | + |
| 237 | + choose_i2s_clocking(self, sample_rate); |
| 238 | + /* Allocate buffers based on a maximum duration */ |
| 239 | + enum { buffer_length_ms = 8 }; |
| 240 | + self->buffer_length = MAX( |
| 241 | + 2*max_buffer_length, |
| 242 | + sample_rate * buffer_length_ms * self->bytes_per_sample |
| 243 | + * self->channel_count / 1000); |
| 244 | + self->buffer_length = (self->buffer_length + 3) & ~3; |
| 245 | + self->buffers[0] = m_malloc(self->buffer_length, false); |
| 246 | + self->buffers[1] = m_malloc(self->buffer_length, false); |
| 247 | + |
| 248 | + |
| 249 | + audiosample_reset_buffer(self->sample, false, 0); |
| 250 | + |
| 251 | + self->next_buffer = 0; |
| 252 | + self->sample_data = self->sample_end = 0; |
| 253 | + self->playing = true; |
| 254 | + self->paused = false; |
| 255 | + self->stopping = false; |
| 256 | + i2s_buffer_fill(self); |
| 257 | + |
| 258 | + NRF_I2S->CONFIG.CHANNELS = self->channel_count == 1 ? I2S_CONFIG_CHANNELS_CHANNELS_Left : I2S_CONFIG_CHANNELS_CHANNELS_Stereo; |
| 259 | + |
| 260 | + |
| 261 | + NRF_I2S->RXTXD.MAXCNT = self->buffer_length / 4; |
| 262 | + NRF_I2S->ENABLE = I2S_ENABLE_ENABLE_Enabled; |
| 263 | + |
| 264 | + NRF_I2S->TASKS_START = 1; |
| 265 | + |
| 266 | + i2s_background(); |
50 | 267 | }
|
51 | 268 |
|
52 | 269 | void common_hal_audiobusio_i2sout_pause(audiobusio_i2sout_obj_t* self) {
|
53 |
| - mp_raise_NotImplementedError(NULL); |
| 270 | + self->paused = true; |
54 | 271 | }
|
55 | 272 |
|
56 | 273 | void common_hal_audiobusio_i2sout_resume(audiobusio_i2sout_obj_t* self) {
|
57 |
| - mp_raise_NotImplementedError(NULL); |
| 274 | + self->paused = false; |
58 | 275 | }
|
59 | 276 |
|
60 | 277 | bool common_hal_audiobusio_i2sout_get_paused(audiobusio_i2sout_obj_t* self) {
|
61 |
| - mp_raise_NotImplementedError(NULL); |
| 278 | + return self->paused; |
62 | 279 | }
|
63 | 280 |
|
64 | 281 | void common_hal_audiobusio_i2sout_stop(audiobusio_i2sout_obj_t* self) {
|
65 |
| - mp_raise_NotImplementedError(NULL); |
| 282 | + NRF_I2S->TASKS_STOP = 1; |
| 283 | + self->stopping = true; |
66 | 284 | }
|
67 | 285 |
|
68 | 286 | bool common_hal_audiobusio_i2sout_get_playing(audiobusio_i2sout_obj_t* self) {
|
69 |
| - mp_raise_NotImplementedError(NULL); |
| 287 | + if (NRF_I2S->EVENTS_STOPPED) { |
| 288 | + self->playing = false; |
| 289 | + NRF_I2S->EVENTS_STOPPED = 0; |
| 290 | + } |
| 291 | + return self->playing; |
| 292 | +} |
| 293 | + |
| 294 | +void i2s_background(void) { |
| 295 | + if (NRF_I2S->EVENTS_TXPTRUPD) { |
| 296 | + NRF_I2S->EVENTS_TXPTRUPD = 0; |
| 297 | + if (instance) { |
| 298 | + i2s_buffer_fill(instance); |
| 299 | + } else { |
| 300 | + NRF_I2S->TASKS_STOP = 1; |
| 301 | + } |
| 302 | + } |
| 303 | +} |
| 304 | + |
| 305 | +void i2s_reset(void) { |
| 306 | + NRF_I2S->TASKS_STOP = 1; |
| 307 | + NRF_I2S->ENABLE = I2S_ENABLE_ENABLE_Disabled; |
| 308 | + NRF_I2S->PSEL.MCK = 0xFFFFFFFF; |
| 309 | + NRF_I2S->PSEL.SCK = 0xFFFFFFFF; |
| 310 | + NRF_I2S->PSEL.LRCK = 0xFFFFFFFF; |
| 311 | + NRF_I2S->PSEL.SDOUT = 0xFFFFFFFF; |
| 312 | + NRF_I2S->PSEL.SDIN = 0xFFFFFFFF; |
| 313 | + instance = NULL; |
70 | 314 | }
|
0 commit comments