audio.c


1
#include "Audio.h"
2
#include "stm32f4xx_conf.h"
3
#include "stm32f4xx.h"
4
5
#include <stdlib.h>
6
7
static void WriteRegister(uint8_t address, uint8_t value);
8
static void StartAudioDMAAndRequestBuffers();
9
static void StopAudioDMA();
10
11
static AudioCallbackFunction *CallbackFunction;
12
static void *CallbackContext;
13
static int16_t * volatile NextBufferSamples;
14
static volatile int NextBufferLength;
15
static volatile int BufferNumber;
16
static volatile bool DMARunning;
17
18
void InitializeAudio(int plln, int pllr, int i2sdiv, int i2sodd) {
19
  GPIO_InitTypeDef  GPIO_InitStructure;
20
21
  // Intitialize state.
22
  CallbackFunction = NULL;
23
  CallbackContext = NULL;
24
  NextBufferSamples = NULL;
25
  NextBufferLength = 0;
26
  BufferNumber = 0;
27
  DMARunning = false;
28
29
  // Turn on peripherals.
30
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
31
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
32
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
33
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
34
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
35
36
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
37
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE);
38
39
  // Configure reset pin.
40
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;;
41
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
42
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
43
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
44
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
45
  GPIO_Init(GPIOD, &GPIO_InitStructure);
46
47
  // Configure I2C SCL and SDA pins.
48
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_9;
49
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
50
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
51
  GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
52
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
53
  GPIO_Init(GPIOB, &GPIO_InitStructure);
54
55
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_I2C1);
56
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_I2C1);
57
58
  // Configure I2S MCK, SCK, SD pins.
59
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_10 | GPIO_Pin_12;
60
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
61
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
62
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
63
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
64
  GPIO_Init(GPIOC, &GPIO_InitStructure);
65
66
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource7, GPIO_AF_SPI3);
67
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_SPI3);
68
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource12, GPIO_AF_SPI3);
69
70
  // Configure I2S WS pin.
71
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
72
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
73
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
74
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
75
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
76
  GPIO_Init(GPIOA, &GPIO_InitStructure);
77
78
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource4, GPIO_AF_SPI3);
79
80
  // Reset the codec.
81
  GPIOD ->BSRRH = 1 << 4;
82
  for (volatile int i = 0; i < 0x4fff; i++) {
83
    __asm__ volatile("nop");
84
  }
85
  GPIOD ->BSRRL = 1 << 4;
86
87
  // Reset I2C.
88
  RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1, ENABLE);
89
  RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1, DISABLE);
90
91
  // Configure I2C.
92
  uint32_t pclk1 = 42000000;
93
94
  I2C1 ->CR2 = pclk1 / 1000000; // Configure frequency and disable interrupts and DMA.
95
  I2C1 ->OAR1 = I2C_OAR1_ADDMODE | 0x33;
96
97
  // Configure I2C speed in standard mode.
98
  const uint32_t i2c_speed = 100000;
99
  int ccrspeed = pclk1 / (i2c_speed * 2);
100
  if (ccrspeed < 4) {
101
    ccrspeed = 4;
102
  }
103
  I2C1 ->CCR = ccrspeed;
104
  I2C1 ->TRISE = pclk1 / 1000000 + 1;
105
106
  I2C1 ->CR1 = I2C_CR1_ACK | I2C_CR1_PE; // Enable and configure the I2C peripheral.
107
108
  // Configure codec.
109
  WriteRegister(0x02, 0x01); // Keep codec powered off.
110
  WriteRegister(0x04, 0xaf); // SPK always off and HP always on.
111
112
  WriteRegister(0x05, 0x81); // Clock configuration: Auto detection.
113
  WriteRegister(0x06, 0x04); // Set slave mode and Philips audio standard.
114
115
  //SetAudioVolume(0xff);
116
  SetAudioVolume(0x00);
117
118
  // Power on the codec.
119
  WriteRegister(0x02, 0x9e);
120
121
  // Configure codec for fast shutdown.
122
  WriteRegister(0x0a, 0x00); // Disable the analog soft ramp.
123
  WriteRegister(0x0e, 0x04); // Disable the digital soft ramp.
124
125
  WriteRegister(0x27, 0x00); // Disable the limiter attack level.
126
  WriteRegister(0x1f, 0x0f); // Adjust bass and treble levels.
127
128
  WriteRegister(0x1a, 0x0a); // Adjust PCM volume level.
129
  WriteRegister(0x1b, 0x0a);
130
131
  // Disable I2S.
132
  SPI3 ->I2SCFGR = 0;
133
134
  // I2S clock configuration
135
  RCC ->CFGR &= ~RCC_CFGR_I2SSRC; // PLLI2S clock used as I2S clock source.
136
  RCC ->PLLI2SCFGR = (pllr << 28) | (plln << 6);
137
138
  // Enable PLLI2S and wait until it is ready.
139
  RCC ->CR |= RCC_CR_PLLI2SON;
140
  while (!(RCC ->CR & RCC_CR_PLLI2SRDY ))
141
    ;
142
143
  // Configure I2S.
144
  SPI3 ->I2SPR = i2sdiv | (i2sodd << 8) | SPI_I2SPR_MCKOE;
145
  SPI3 ->I2SCFGR = SPI_I2SCFGR_I2SMOD | SPI_I2SCFGR_I2SCFG_1
146
      | SPI_I2SCFGR_I2SE; // Master transmitter, Phillips mode, 16 bit values, clock polarity low, enable.
147
148
}
149
150
void AudioOn() {
151
  WriteRegister(0x02, 0x9e);
152
  SPI3 ->I2SCFGR = SPI_I2SCFGR_I2SMOD | SPI_I2SCFGR_I2SCFG_1
153
      | SPI_I2SCFGR_I2SE; // Master transmitter, Phillips mode, 16 bit values, clock polarity low, enable.
154
}
155
156
void AudioOff() {
157
  WriteRegister(0x02, 0x01);
158
  SPI3 ->I2SCFGR = 0;
159
}
160
161
void SetAudioVolume(int volume) {
162
  WriteRegister(0x20, (volume + 0x19) & 0xff);
163
  WriteRegister(0x21, (volume + 0x19) & 0xff);
164
}
165
166
void OutputAudioSample(int16_t sample) {
167
  while (!(SPI3 ->SR & SPI_SR_TXE ))
168
    ;
169
  SPI3 ->DR = sample;
170
}
171
172
void OutputAudioSampleWithoutBlocking(int16_t sample) {
173
  SPI3 ->DR = sample;
174
}
175
176
void PlayAudioWithCallback(AudioCallbackFunction *callback, void *context) {
177
  StopAudioDMA();
178
179
  NVIC_EnableIRQ(DMA1_Stream7_IRQn);
180
  NVIC_SetPriority(DMA1_Stream7_IRQn, 4);
181
182
  SPI3 ->CR2 |= SPI_CR2_TXDMAEN; // Enable I2S TX DMA request.
183
184
  CallbackFunction = callback;
185
  CallbackContext = context;
186
  BufferNumber = 0;
187
188
  if (CallbackFunction)
189
    CallbackFunction(CallbackContext, BufferNumber);
190
}
191
192
void StopAudio() {
193
  StopAudioDMA();
194
  SPI3 ->CR2 &= ~SPI_CR2_TXDMAEN; // Disable I2S TX DMA request.
195
  NVIC_DisableIRQ(DMA1_Stream7_IRQn);
196
  CallbackFunction = NULL;
197
}
198
199
void ProvideAudioBuffer(void *samples, int numsamples) {
200
  while (!ProvideAudioBufferWithoutBlocking(samples, numsamples))
201
    __asm__ volatile ("wfi");
202
}
203
204
bool ProvideAudioBufferWithoutBlocking(void *samples, int numsamples) {
205
  if (NextBufferSamples)
206
    return false;
207
208
  NVIC_DisableIRQ(DMA1_Stream7_IRQn);
209
210
  NextBufferSamples = samples;
211
  NextBufferLength = numsamples;
212
213
  if (!DMARunning)
214
    StartAudioDMAAndRequestBuffers();
215
216
  NVIC_EnableIRQ(DMA1_Stream7_IRQn);
217
218
  return true;
219
}
220
221
static void WriteRegister(uint8_t address, uint8_t value) {
222
  while (I2C1 ->SR2 & I2C_SR2_BUSY )
223
    ;
224
225
  I2C1 ->CR1 |= I2C_CR1_START; // Start the transfer sequence.
226
  while (!(I2C1 ->SR1 & I2C_SR1_SB ))
227
    ; // Wait for start bit.
228
229
  I2C1 ->DR = 0x94;
230
  while (!(I2C1 ->SR1 & I2C_SR1_ADDR ))
231
    ; // Wait for master transmitter mode.
232
  I2C1 ->SR2;
233
234
  I2C1 ->DR = address; // Transmit the address to write to.
235
  while (!(I2C1 ->SR1 & I2C_SR1_TXE ))
236
    ; // Wait for byte to move to shift register.
237
238
  I2C1 ->DR = value; // Transmit the value.
239
240
  while (!(I2C1 ->SR1 & I2C_SR1_BTF ))
241
    ; // Wait for all bytes to finish.
242
  I2C1 ->CR1 |= I2C_CR1_STOP; // End the transfer sequence.
243
}
244
245
static void StartAudioDMAAndRequestBuffers() {
246
  // Configure DMA stream.
247
  DMA1_Stream7 ->CR = (0 * DMA_SxCR_CHSEL_0 ) | // Channel 0
248
      (1 * DMA_SxCR_PL_0 ) | // Priority 1
249
      (1 * DMA_SxCR_PSIZE_0 ) | // PSIZE = 16 bit
250
      (1 * DMA_SxCR_MSIZE_0 ) | // MSIZE = 16 bit
251
      DMA_SxCR_MINC | // Increase memory address
252
      (1 * DMA_SxCR_DIR_0 ) | // Memory to peripheral
253
      DMA_SxCR_TCIE; // Transfer complete interrupt
254
  DMA1_Stream7 ->NDTR = NextBufferLength;
255
  DMA1_Stream7 ->PAR = (uint32_t) &SPI3 ->DR;
256
  DMA1_Stream7 ->M0AR = (uint32_t) NextBufferSamples;
257
  DMA1_Stream7 ->FCR = DMA_SxFCR_DMDIS;
258
  DMA1_Stream7 ->CR |= DMA_SxCR_EN;
259
260
  // Update state.
261
  NextBufferSamples = NULL;
262
  BufferNumber ^= 1;
263
  DMARunning = true;
264
265
  // Invoke callback if it exists to queue up another buffer.
266
  if (CallbackFunction)
267
    CallbackFunction(CallbackContext, BufferNumber);
268
}
269
270
static void StopAudioDMA() {
271
  DMA1_Stream7 ->CR &= ~DMA_SxCR_EN; // Disable DMA stream.
272
  while (DMA1_Stream7 ->CR & DMA_SxCR_EN )
273
    ; // Wait for DMA stream to stop.
274
275
  DMARunning = false;
276
}
277
278
void DMA1_Stream7_IRQHandler() {
279
  DMA1 ->HIFCR |= DMA_HIFCR_CTCIF7; // Clear interrupt flag.
280
281
  if (NextBufferSamples) {
282
    StartAudioDMAAndRequestBuffers();
283
  } else {
284
    DMARunning = false;
285
  }
286
}