-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathHiFi.cpp
More file actions
396 lines (343 loc) · 13.9 KB
/
HiFi.cpp
File metadata and controls
396 lines (343 loc) · 13.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
/*
HiFi.cpp
This library implements a driver for the SSC peripheral on the Arduino
DUE board (ARM Cortex-M3 based). This can be used to interface with external
high quality ADCs, DACs, or CODECs instead of the limited bit-depth
converters that are on the SAM3X.
Currently, only I2S mode is supported, although the SSC peripheral can
support left-justified, right justified and TDM (>2 channels) modes.
These modes could be added, but were left out for simplicity. Most codecs
support I2S mode and it doesn't really have many options which reduces
overall complexity. This seems like a good compromise for the Arduino
platform.
You can configure the library to transmit, receive, or both, but only in slave
mode. The SAM3X doesn't appear to have the capability of driving an MCLK
signal that is synchronous with the TK/RK and TF/RF signals, which is needed
by many of the audio converters. Additionally, it doesn't seem to be able to
generate clocks that are multiples of standard audio sampling rates (44.1k,
48k, etc.). It's simpler to use an external clock master and just set the
SSC to slave to that. That allows the playback/recording of wave files that
can be generated/rendered on other systems easily.
For transmit and receive, there are 2 clock signals needed - frame clock and
bit clock. As mentioned above, these are inputs to the SSC. If both
transmit and receive are desired, one side can be synchronized to the other
to reduce the overall pin count (i.e. don't need separate receive and
transmit clocks). Although the SSC does support independent transmit and
receive rates if needed (e.g. separate input and output sample rates).
To use this library, place the files in a folder called 'HiFi' under the
libraries directory in your sketches folder.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "HiFi.h"
// Make sure that data is first pin in the list.
const PinDescription SSCTXPins[]=
{
{ PIOA, PIO_PA16B_TD, ID_PIOA, PIO_PERIPH_B, PIO_DEFAULT, PIN_ATTR_DIGITAL, NO_ADC, NO_ADC, NOT_ON_PWM, NOT_ON_TIMER }, // A0
{ PIOA, PIO_PA15B_TF, ID_PIOA, PIO_PERIPH_B, PIO_DEFAULT, PIN_ATTR_DIGITAL, NO_ADC, NO_ADC, NOT_ON_PWM, NOT_ON_TIMER }, // PIN 24
{ PIOA, PIO_PA14B_TK, ID_PIOA, PIO_PERIPH_B, PIO_DEFAULT, PIN_ATTR_DIGITAL, NO_ADC, NO_ADC, NOT_ON_PWM, NOT_ON_TIMER }, // PIN 23
};
// Make sure that data is first pin in the list.
const PinDescription SSCRXPins[]=
{
{ PIOB, PIO_PB18A_RD, ID_PIOB, PIO_PERIPH_A, PIO_DEFAULT, PIN_ATTR_DIGITAL, NO_ADC, NO_ADC, NOT_ON_PWM, NOT_ON_TIMER }, // A9
{ PIOB, PIO_PB17A_RF, ID_PIOB, PIO_PERIPH_A, PIO_DEFAULT, PIN_ATTR_DIGITAL, NO_ADC, NO_ADC, NOT_ON_PWM, NOT_ON_TIMER }, // A8
{ PIOB, PIO_PB19A_RK, ID_PIOB, PIO_PERIPH_A, PIO_DEFAULT, PIN_ATTR_DIGITAL, NO_ADC, NO_ADC, NOT_ON_PWM, NOT_ON_TIMER }, // A10
};
void HiFiClass::begin(void)
{
// Enable module
pmc_enable_periph_clk(ID_SSC);
ssc_reset(SSC);
_dataOutAddr = (uint32_t *)ssc_get_tx_access(SSC);
_dataInAddr = (uint32_t *)ssc_get_rx_access(SSC);
// Enable SSC interrupt line from the core
NVIC_DisableIRQ(SSC_IRQn);
NVIC_ClearPendingIRQ(SSC_IRQn);
NVIC_SetPriority(SSC_IRQn, 0); // most arduino interrupts are set to priority 0.
NVIC_EnableIRQ(SSC_IRQn);
}
void HiFiClass::configureTx( HiFiAudioMode_t audioMode,
HiFiClockMode_t clkMode,
uint8_t bitsPerChannel )
{
clock_opt_t tx_clk_option;
data_frame_opt_t tx_data_frame_option;
memset((uint8_t *)&tx_clk_option, 0, sizeof(clock_opt_t));
memset((uint8_t *)&tx_data_frame_option, 0, sizeof(data_frame_opt_t));
///////////////////////////////////////////////////////////////////////////
/// Transmitter IO Pin configuration
///////////////////////////////////////////////////////////////////////////
uint8_t endpin = sizeof(SSCTXPins)/sizeof(SSCTXPins[0]);
if (clkMode == HIFI_CLK_MODE_USE_TK_RK_CLK)
{
endpin = 1;
}
for (int i=0; i < endpin; i++)
{
PIO_Configure(SSCTXPins[i].pPort,
SSCTXPins[i].ulPinType,
SSCTXPins[i].ulPin,
SSCTXPins[i].ulPinConfiguration);
}
// Note: there is a function in the Atmel ssc driver for configuration of
// the peripheral in I2S mode, but it is incomplete and buggy. This library
// will configure the SSC directly which will also shed some light on the
// various configuration parameters should a user need something slightly
// different.
///////////////////////////////////////////////////////////////////////////
/// Transmitter clock mode configuration
///////////////////////////////////////////////////////////////////////////
switch (clkMode)
{
case HIFI_CLK_MODE_USE_EXT_CLKS:
// Use clocks on the TK/TF pins
// Despite what this looks like, it actually means use the TK clock
// pin. There is both an error in the documentation and the macro
// definition supplied by Atmel in the ssc.h file. The document author
// just cut and paste and the Atmel engineer decided to keep the
// macro consistent with the data sheet (or didn't pay attention...).
// I imagine that this will get fixed at some point, so this may need
// to be revisited.
tx_clk_option.ul_cks = SSC_TCMR_CKS_RK;
if (audioMode == HIFI_AUDIO_MODE_MONO_RIGHT)
{
// high level on the frame clock is right channel in
// I2S. If we're only using the right channel, then
// we can set start condition for right-only
tx_clk_option.ul_start_sel = SSC_TCMR_START_RF_RISING;
}
else
{
// stereo or mono-left will start in the left channel slot
tx_clk_option.ul_start_sel = SSC_TCMR_START_RF_FALLING;
}
break;
case HIFI_CLK_MODE_USE_TK_RK_CLK:
// Despite what this looks like, it actually means use the RK clock
// pin. There is both an error in the documentation and the macro
// definition supplied by Atmel in the ssc.h file. The document author
// just cut and paste and the Atmel engineer decided to keep the
// macro consistent with the data sheet (or didn't pay attention...).
// I imagine that this will get fixed at some point, so this may need
// to be revisited. See the external clock case above.
tx_clk_option.ul_cks = SSC_TCMR_CKS_TK;
// Use the receiver's configuration as the start trigger for
// transmit. The receiver must be configured and running in
// order for this to work.
tx_clk_option.ul_start_sel = SSC_TCMR_START_RECEIVE;
break;
}
// No output clocks
tx_clk_option.ul_ckg = SSC_TCMR_CKG_NONE;
tx_clk_option.ul_period = 0; // we're not master -- set to 0
tx_clk_option.ul_cko = SSC_TCMR_CKO_NONE;
tx_clk_option.ul_cki = 0;
// I2S has a one bit delay on the data.
tx_clk_option.ul_sttdly = 1;
///////////////////////////////////////////////////////////////////////////
/// Transmitter frame mode configuration.
///////////////////////////////////////////////////////////////////////////
tx_data_frame_option.ul_datlen = bitsPerChannel - 1;
tx_data_frame_option.ul_msbf = SSC_TFMR_MSBF;
// number of channels
if (audioMode == HIFI_AUDIO_MODE_STEREO)
{
tx_data_frame_option.ul_datnb = 1;
}
else
{
tx_data_frame_option.ul_datnb = 0;
}
// No frame clock output
tx_data_frame_option.ul_fsos = SSC_TFMR_FSOS_NONE;
///////////////////////////////////////////////////////////////////////////
/// Load configuration and enable TX interrupt
///////////////////////////////////////////////////////////////////////////
ssc_set_transmitter(SSC, &tx_clk_option, &tx_data_frame_option);
ssc_enable_interrupt(SSC, SSC_IER_TXRDY);
}
void HiFiClass::enableTx(bool enable)
{
if (enable)
{
ssc_enable_tx(SSC);
}
else
{
ssc_disable_tx(SSC);
}
}
void HiFiClass::onTxReady(void(*function)(HiFiChannelID_t)) {
onTxReadyCallback = function;
}
void HiFiClass::configureRx( HiFiAudioMode_t audioMode,
HiFiClockMode_t clkMode,
uint8_t bitsPerChannel )
{
clock_opt_t rx_clk_option;
data_frame_opt_t rx_data_frame_option;
memset((uint8_t *)&rx_clk_option, 0, sizeof(clock_opt_t));
memset((uint8_t *)&rx_data_frame_option, 0, sizeof(data_frame_opt_t));
///////////////////////////////////////////////////////////////////////////
/// Receiver IO Pin configuration
///////////////////////////////////////////////////////////////////////////
uint8_t endpin = sizeof(SSCRXPins)/sizeof(SSCRXPins[0]);
if (clkMode == HIFI_CLK_MODE_USE_TK_RK_CLK)
{
endpin = 1;
}
for (int i=0; i < (sizeof(SSCRXPins)/sizeof(SSCRXPins[0])); i++)
{
PIO_Configure(SSCRXPins[i].pPort,
SSCRXPins[i].ulPinType,
SSCRXPins[i].ulPin,
SSCRXPins[i].ulPinConfiguration);
}
// Note: there is a function in the Atmel ssc driver for configuration of
// the peripheral in I2S mode, but it is incomplete and buggy. This library
// will configure the SSC directly which will also shed some light on the
// various configuration parameters should a user need something slightly
// different.
///////////////////////////////////////////////////////////////////////////
/// Receiver clock mode configuration
///////////////////////////////////////////////////////////////////////////
switch (clkMode)
{
case HIFI_CLK_MODE_USE_EXT_CLKS:
// Use clocks on the RK/RF pins
rx_clk_option.ul_cks = SSC_RCMR_CKS_RK;
if (audioMode == HIFI_AUDIO_MODE_MONO_RIGHT)
{
// high level on the frame clock is right channel in
// I2S. If we're only using the right channel, then
// we can set start condition for right-only
rx_clk_option.ul_start_sel = SSC_RCMR_START_RF_RISING;
}
else
{
// stereo or mono-left will start in the left channel slot
rx_clk_option.ul_start_sel = SSC_RCMR_START_RF_FALLING;
}
break;
case HIFI_CLK_MODE_USE_TK_RK_CLK:
// Use the clock selected by the transmitter config
// (i.e. sync receiver to transmitter).
rx_clk_option.ul_cks = SSC_RCMR_CKS_TK;
// Use the transmitter's configuration as the start trigger
// The transmitter must be configured and running in
// order for this to work.
rx_clk_option.ul_start_sel = SSC_RCMR_START_TRANSMIT;
break;
}
// No output clocks
rx_clk_option.ul_ckg = SSC_RCMR_CKG_NONE;
rx_clk_option.ul_period = 0; // we're not master -- set to 0
rx_clk_option.ul_cko = SSC_RCMR_CKO_NONE;
// I2S latches data on the rising edge of the clock.
rx_clk_option.ul_cki = SSC_RCMR_CKI;
// I2S has a one bit delay on the data.
rx_clk_option.ul_sttdly = 1;
///////////////////////////////////////////////////////////////////////////
/// Receiver frame mode configuration.
///////////////////////////////////////////////////////////////////////////
rx_data_frame_option.ul_datlen = bitsPerChannel - 1;
rx_data_frame_option.ul_msbf = SSC_RFMR_MSBF;
// number of channels
if (audioMode == HIFI_AUDIO_MODE_STEREO)
{
rx_data_frame_option.ul_datnb = 1;
}
else
{
rx_data_frame_option.ul_datnb = 0;
}
// No frame clock output
rx_data_frame_option.ul_fsos = SSC_TFMR_FSOS_NONE;
///////////////////////////////////////////////////////////////////////////
/// Load configuration and enable RX interrupt
///////////////////////////////////////////////////////////////////////////
ssc_set_receiver(SSC, &rx_clk_option, &rx_data_frame_option);
ssc_enable_interrupt(SSC, SSC_IER_RXRDY);
}
void HiFiClass::enableRx(bool enable)
{
if (enable)
{
ssc_enable_rx(SSC);
}
else
{
ssc_disable_rx(SSC);
}
}
void HiFiClass::onRxReady(void(*function)(HiFiChannelID_t)) {
onRxReadyCallback = function;
}
void HiFiClass::onService(void)
{
HiFiChannelID_t channel;
// read and save status -- some bits are cleared on a read
uint32_t status = ssc_get_status(SSC);
if (ssc_is_tx_ready(SSC) == SSC_RC_YES)
{
if (HiFi.onTxReadyCallback)
{
// The TXSYN event is triggered based on what the start
// condition was set to during configuration. This
// is usually the left channel, except in the case of the
// mono right setup, in which case it's the right. This
// may need to change if support for other formats
// (e.g. TDM) is added.
if (status & SSC_IER_TXSYN)
{
channel = HIFI_CHANNEL_ID_1;
}
else
{
channel = HIFI_CHANNEL_ID_2;
}
HiFi.onTxReadyCallback(channel);
}
}
if (ssc_is_rx_ready(SSC) == SSC_RC_YES)
{
if (HiFi.onRxReadyCallback)
{
// The RXSYN event is triggered based on what the start
// condition was set to during configuration. This
// is usually the left channel, except in the case of the
// mono right setup, in which case it's the right. This
// may need to change if support for other formats
// (e.g. TDM) is added.
if (status & SSC_IER_RXSYN)
{
channel = HIFI_CHANNEL_ID_1;
}
else
{
channel = HIFI_CHANNEL_ID_2;
}
HiFi.onRxReadyCallback(channel);
}
}
}
/**
* \brief Synchronous Serial Controller Handler.
*
*/
void SSC_Handler(void)
{
HiFi.onService();
}
// Create our object
HiFiClass HiFi = HiFiClass();