arduino-audio-tools
All Classes Namespaces Files Functions Variables Typedefs Enumerations Friends Modules Pages
AudioESP32ULP.h
Go to the documentation of this file.
1
10#pragma once
11
12#ifndef ESP32
13#error Only the ESP32 supports ULP audio output
14#endif
15#include "AudioLogger.h"
16#include "AudioTools/CoreAudio/AudioTypes.h"
17#include "AudioTools/CoreAudio/AudioOutput.h"
18#include <driver/dac.h>
19#include <driver/rtc_io.h>
20#include <esp32/ulp.h>
21#include <math.h>
22#include <soc/rtc.h>
23#include "soc/rtc_io_reg.h"
24
25namespace audio_tools {
26
27enum UlpDac { ULP_DAC1 = 1, ULP_DAC2 = 2 };
28
38class AudioESP32ULP : public AudioOutput {
39public:
40 AudioInfo defaultConfig() {
41 AudioInfo cfg(44100, 2, 16);
42 return cfg;
43 }
44
46 void setMonoDAC(UlpDac dac){
47 selected_mono_dac = dac;
48 }
49
52 min_write_bytes = bytes;
53 }
54
56 bool begin(AudioInfo info) {
57 TRACEI();
58 cfg = info;
59 stereoOutput = info.channels == 2;
60 activeDACs = stereoOutput ? 3 : selected_mono_dac;
61 hertz = cfg.sample_rate;
62
63 if (info.bits_per_sample != 16) {
64 LOGE("Unsupported bits_per_sample: %d", info.bits_per_sample);
65 return false;
66 }
67 return setup();
68 }
69
70 size_t write(const uint8_t *data, size_t len) {
71 TRACED();
72 int16_t *data_16 = (int16_t *)data;
73 size_t result = 0;
74 int16_t stereo[2];
75 int frameSize = cfg.channels * sizeof(int16_t);
76 int frames = len / frameSize;
77 for (int j = 0; j < frames; j++) {
78 int pos = j * cfg.channels;
79 stereo[0] = data_16[pos];
80 stereo[1] = stereoOutput ? data_16[pos + 1] : data_16[pos];
81 // blocking write
82 while (!writeFrame(stereo)) {
83 delay(20);
84 }
85 result += frameSize;
86 }
87 return result;
88 }
89
90 int availableForWrite() {
91 int result = totalSampleWords-lastFilledWord;
92 return result < min_write_bytes ? 0 : result;
93 }
94
95 void end() {
96 TRACEI();
97 const ulp_insn_t stopulp[] = {// stop the timer
98 I_END(),
99 // end the program
100 I_HALT()};
101
102 size_t load_addr = 0;
103 size_t size = sizeof(stopulp) / sizeof(ulp_insn_t);
104 ulp_process_macros_and_load(load_addr, stopulp, &size);
105
106 // start
107 ulp_run(0);
108
109 if (activeDACs & 1) {
110 dac_output_voltage(DAC_CHANNEL_1, 128);
111 }
112 if (activeDACs & 2) {
113 dac_output_voltage(DAC_CHANNEL_2, 128);
114 }
115 }
116
117
118protected:
119 int lastFilledWord = 0;
120 int hertz;
121 int min_write_bytes = 128;
122 UlpDac selected_mono_dac = ULP_DAC1;
123 uint8_t bufferedOddSample = 128;
124 bool waitingOddSample = true; // must be set to false for mono output
125 int activeDACs = 3; // 1:DAC1; 2:DAC2; 3:both;
126 bool stereoOutput = true;
127 const int opcodeCount = 20;
128 const uint32_t dacTableStart1 = 2048 - 512;
129 const uint32_t dacTableStart2 = dacTableStart1 - 512;
130 uint32_t totalSampleWords =
131 2048 - 512 - 512 - (opcodeCount + 1); // add 512 for mono
132 const int totalSamples = totalSampleWords * 2;
133 const uint32_t indexAddress = opcodeCount;
134 const uint32_t bufferStart = indexAddress + 1;
135
136 bool setup() {
137 TRACED();
138 if (!stereoOutput) {
139 waitingOddSample = false;
140 // totalSampleWords += 512;
141 // dacTableStart2 = dacTableStart1;
142 }
143
144 // calculate the actual ULP clock
145 unsigned long rtc_8md256_period = rtc_clk_cal(RTC_CAL_8MD256, 1000);
146 unsigned long rtc_fast_freq_hz =
147 1000000ULL * (1 << RTC_CLK_CAL_FRACT) * 256 / rtc_8md256_period;
148
149 // initialize DACs
150 if (activeDACs & 1) {
151 dac_output_enable(DAC_CHANNEL_1);
152 dac_output_voltage(DAC_CHANNEL_1, 128);
153 }
154 if (activeDACs & 2) {
155 dac_output_enable(DAC_CHANNEL_2);
156 dac_output_voltage(DAC_CHANNEL_2, 128);
157 }
158
159 int retAddress1 = 9;
160 int retAddress2 = 14;
161
162 int loopCycles = 134;
163 int loopHalfCycles1 = 90;
164 int loopHalfCycles2 = 44;
165
166 LOGI("Real RTC clock: %d", rtc_fast_freq_hz);
167
168 uint32_t dt = (rtc_fast_freq_hz / hertz) - loopCycles;
169 uint32_t dt2 = 0;
170 if (!stereoOutput) {
171 dt = (rtc_fast_freq_hz / hertz) - loopHalfCycles1;
172 dt2 = (rtc_fast_freq_hz / hertz) - loopHalfCycles2;
173 }
174
175 LOGI("dt: %d", dt);
176 LOGI("dt2: %d", dt2);
177
178 const ulp_insn_t stereo[] = {
179 // reset offset register
180 I_MOVI(R3, 0),
181 // delay to get the right sampling rate
182 I_DELAY(dt), // 6 + dt
183 // reset sample index
184 I_MOVI(R0, 0), // 6
185 // write the index back to memory for the main cpu
186 I_ST(R0, R3, indexAddress), // 8
187 // load the samples
188 I_LD(R1, R0, bufferStart), // 8
189 // mask the lower 8 bits
190 I_ANDI(R2, R1, 0x00ff), // 6
191 // multiply by 2
192 I_LSHI(R2, R2, 1), // 6
193 // add start position
194 I_ADDI(R2, R2, dacTableStart1), // 6
195 // jump to the dac opcode
196 I_BXR(R2), // 4
197 // back from first dac
198 // delay between the two samples in mono rendering
199 I_DELAY(dt2), // 6 + dt2
200 // mask the upper 8 bits
201 I_ANDI(R2, R1, 0xff00), // 6
202 // shift the upper bits to right and multiply by 2
203 I_RSHI(R2, R2, 8 - 1), // 6
204 // add start position of second dac table
205 I_ADDI(R2, R2, dacTableStart2), // 6
206 // jump to the dac opcode
207 I_BXR(R2), // 4
208 // here we get back from writing the second sample
209 // load 0x8080 as sample
210 I_MOVI(R1, 0x8080), // 6
211 // write 0x8080 in the sample buffer
212 I_ST(R1, R0, indexAddress), // 8
213 // increment the sample index
214 I_ADDI(R0, R0, 1), // 6
215 // if reached end of the buffer, jump relative to index reset
216 I_BGE(-16, totalSampleWords), // 4
217 // wait to get the right sample rate (2 cycles more to compensate the
218 // index reset)
219 I_DELAY((unsigned int)dt + 2), // 8 + dt
220 // if not, jump absolute to where index is written to memory
221 I_BXI(3) // 4
222 };
223 // write io and jump back another 12 + 4 + 12 + 4
224
225 size_t load_addr = 0;
226 size_t size = sizeof(stereo) / sizeof(ulp_insn_t);
227 ulp_process_macros_and_load(load_addr, stereo, &size);
228 // this is how to get the opcodes
229 // for(int i = 0; i < size; i++)
230 // Serial.println(RTC_SLOW_MEM[i], HEX);
231
232 // create DAC opcode tables
233 switch (activeDACs) {
234 case 1:
235 for (int i = 0; i < 256; i++) {
236 RTC_SLOW_MEM[dacTableStart1 + i * 2] = create_I_WR_REG(
237 RTC_IO_PAD_DAC1_REG, 19, 26, i); // dac1: 0x1D4C0121 | (i << 10)
238 RTC_SLOW_MEM[dacTableStart1 + 1 + i * 2] =
239 create_I_BXI(retAddress1); // 0x80000000 + retAddress1 * 4
240 RTC_SLOW_MEM[dacTableStart2 + i * 2] = create_I_WR_REG(
241 RTC_IO_PAD_DAC1_REG, 19, 26, i); // dac2: 0x1D4C0122 | (i << 10)
242 RTC_SLOW_MEM[dacTableStart2 + 1 + i * 2] =
243 create_I_BXI(retAddress2); // 0x80000000 + retAddress2 * 4
244 }
245 break;
246 case 2:
247 for (int i = 0; i < 256; i++) {
248 RTC_SLOW_MEM[dacTableStart1 + i * 2] = create_I_WR_REG(
249 RTC_IO_PAD_DAC2_REG, 19, 26, i); // dac1: 0x1D4C0121 | (i << 10)
250 RTC_SLOW_MEM[dacTableStart1 + 1 + i * 2] =
251 create_I_BXI(retAddress1); // 0x80000000 + retAddress1 * 4
252 RTC_SLOW_MEM[dacTableStart2 + i * 2] = create_I_WR_REG(
253 RTC_IO_PAD_DAC2_REG, 19, 26, i); // dac2: 0x1D4C0122 | (i << 10)
254 RTC_SLOW_MEM[dacTableStart2 + 1 + i * 2] =
255 create_I_BXI(retAddress2); // 0x80000000 + retAddress2 * 4
256 }
257 break;
258 case 3:
259 for (int i = 0; i < 256; i++) {
260 RTC_SLOW_MEM[dacTableStart1 + i * 2] = create_I_WR_REG(
261 RTC_IO_PAD_DAC1_REG, 19, 26, i); // dac1: 0x1D4C0121 | (i << 10)
262 RTC_SLOW_MEM[dacTableStart1 + 1 + i * 2] =
263 create_I_BXI(retAddress1); // 0x80000000 + retAddress1 * 4
264 RTC_SLOW_MEM[dacTableStart2 + i * 2] = create_I_WR_REG(
265 RTC_IO_PAD_DAC1_REG, 19, 26, i); // dac2: 0x1D4C0122 | (i << 10)
266 RTC_SLOW_MEM[dacTableStart2 + 1 + i * 2] =
267 create_I_BXI(retAddress2); // 0x80000000 + retAddress2 * 4
268 }
269 break;
270 }
271
272 // set all samples to 128 (silence)
273 for (int i = 0; i < totalSampleWords; i++)
274 RTC_SLOW_MEM[bufferStart + i] = 0x8080;
275
276 // start
277 RTC_SLOW_MEM[indexAddress] = 0;
278 ulp_run(0);
279
280 // wait until ULP starts using samples and the index of output sample
281 // advances
282 while (RTC_SLOW_MEM[indexAddress] == 0)
283 delay(1);
284
285 return true;
286 }
287
288 bool writeFrame(int16_t sample[2]) {
289 TRACED();
290 int16_t ms[2];
291 ms[0] = sample[0];
292 ms[1] = sample[1];
293
294 // TODO: needs improvement (counting is different here with respect to ULP
295 // code)
296 int currentSample = RTC_SLOW_MEM[indexAddress] & 0xffff;
297 int currentWord = currentSample >> 1;
298
299 for (int i = 0; i < 2; i++) {
300 ms[i] = ((ms[i] >> 8) + 128) & 0xff;
301 }
302 if (!stereoOutput) // mix both channels
303 ms[0] =
304 (uint16_t)(((uint32_t)((int32_t)(ms[0]) + (int32_t)(ms[1])) >> 1) &
305 0xff);
306
307 if (waitingOddSample) { // always true for stereo because samples are
308 // consumed in pairs
309 if (lastFilledWord !=
310 currentWord) // accept sample if writing index lastFilledWord has not
311 // reached index of output sample
312 {
313 unsigned int w;
314 if (stereoOutput) {
315 w = ms[0];
316 w |= ms[1] << 8;
317 } else {
318 w = bufferedOddSample;
319 w |= ms[0] << 8;
320 bufferedOddSample = 128;
321 waitingOddSample = false;
322 }
323 RTC_SLOW_MEM[bufferStart + lastFilledWord] = w;
324 lastFilledWord++;
325 if (lastFilledWord == totalSampleWords)
326 lastFilledWord = 0;
327 return true;
328 } else {
329 return false;
330 }
331 } else {
332 bufferedOddSample = ms[0];
333 waitingOddSample = true;
334 return true;
335 }
336 }
337
338 uint32_t create_I_WR_REG(uint32_t reg, uint32_t low_bit, uint32_t high_bit,
339 uint32_t val) {
340 typedef union {
341 ulp_insn_t ulp_ins;
342 uint32_t ulp_bin;
343 } ulp_union;
344 const ulp_insn_t singleinstruction[] = {
345 I_WR_REG(reg, low_bit, high_bit, val)};
346 ulp_union recover_ins;
347 recover_ins.ulp_ins = singleinstruction[0];
348 return (uint32_t)(recover_ins.ulp_bin);
349 }
350
351 uint32_t create_I_BXI(uint32_t imm_pc) {
352 typedef union {
353 ulp_insn_t ulp_ins;
354 uint32_t ulp_bin;
355 } ulp_union;
356 const ulp_insn_t singleinstruction[] = {I_BXI(imm_pc)};
357 ulp_union recover_ins;
358 recover_ins.ulp_ins = singleinstruction[0];
359 return (uint32_t)(recover_ins.ulp_bin);
360 }
361};
362
363}
Outputs to ESP32 DAC through the ULP (Ultra> Low Power coprocessor), freeing I2S for other uses....
Definition AudioESP32ULP.h:38
void setMonoDAC(UlpDac dac)
Selects the DAC when we have a mono signal.
Definition AudioESP32ULP.h:46
void setMinWriteBytes(int bytes)
Selects the limit for the availableForWrite to report the data.
Definition AudioESP32ULP.h:51
bool begin(AudioInfo info)
Starts the processing. I the output is mono, we can determine the output pin by selecting DAC1 (gpio2...
Definition AudioESP32ULP.h:56
Abstract Audio Ouptut class.
Definition AudioOutput.h:22
Vector implementation which provides the most important methods as defined by std::vector....
Definition Vector.h:21
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioConfig.h:885
Basic Audio information which drives e.g. I2S.
Definition AudioTypes.h:52
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition AudioTypes.h:55
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition AudioTypes.h:57
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition AudioTypes.h:59