-
Notifications
You must be signed in to change notification settings - Fork 16
/
i2s.pio
253 lines (224 loc) · 10.6 KB
/
i2s.pio
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
; i2s.pio
;
; Author: Daniel Collins
; Date: 2022-02-25
;
; Copyright (c) 2022 Daniel Collins
;
; This file is part of rp2040_i2s_example.
;
; rp2040_i2s_example is free software: you can redistribute it and/or modify it under
; the terms of the GNU General Public License, version 3 as published by the
; Free Software Foundation.
;
; rp2040_i2s_example 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 General Public License for more details.
;
; You should have received a copy of the GNU General Public License along with
; rp2040_i2s_example. If not, see <https://www.gnu.org/licenses/>.
; An I2S bi-directional peripheral with master clock (SCK) output.
.program i2s_sck
; Transmit an I2S system / master clock.
;
; Usually this needs to be 256 * fs (the word clock rate).
; (e.g. for 48kHz audio, this should be precisely 12.288000 MHz.)
; Since all of these transmit at 1 I2S-clock per 2 sys-clock cycles,
; you need to set the clock divider at 2x the desired rate
; (for the 48kHz example, this should be 2 * 12,288,000 = 24.5760000 MHz).
;
; Use this as one state machine of the I2S PIO unit as a whole for perfect
; synchronization, e.g.:
; state machine 0 = clock (divide to 48000 * 256 * 2 = 24.576000 MHz for 48kHz stereo audio)
; state machine 1 = out (divide to 48000 * 64 * 2 = 6.144000 MHz for 48kHz stereo audio)
; state machine 2 = in (divide to 48000 * 256 * 2 = 24.576000 MHz for 48kHz stereo audio)
;
; One output pin is used for the clock output.
.wrap_target
set pins 1
set pins 0
.wrap
.program i2s_out_master
; I2S audio output block. Synchronous with clock and input.
; Must run at BCK * 2.
;
; This block also outputs the word clock (also called frame or LR clock) and
; the bit clock.
;
; Set register x to (bit depth - 2) (e.g. for 24 bit audio, set to 22).
; Note that if this is needed to be synchronous with the SCK module,
; it is not possible to run 24-bit frames with an SCK of 256x fs. You must either
; run SCK at 384x fs (if your codec permits this) or use 32-bit frames, which
; work fine with 24-bit codecs.
.side_set 2
public entry_point:
; /--- LRCLK
; |/-- BCLK
frameL: ; ||
set x, 30 side 0b00 ; start of Left frame
pull noblock side 0b01 ; One clock after edge change with no data
dataL:
out pins, 1 side 0b00
jmp x-- dataL side 0b01
frameR:
set x, 30 side 0b10
pull noblock side 0b11 ; One clock after edge change with no data
dataR:
out pins, 1 side 0b10
jmp x-- dataR side 0b11
.program i2s_bidi_slave
; I2S audio bidirectional (input/output) block, both in slave mode.
; Requires external BCK and LRCK, usually from the codec directly.
; This block provides both the output and input components together,
; so should not be used in combination with either of the other output or
; input units on the same I2S bus.
;
; Input pin order: DIN, BCK, LRCK
; Set JMP pin to LRCK.
;
; Clock synchronously with the system clock, or *at least* 4x the usual
; bit clock for a given fs (e.g. for 48kHz 24-bit, clock at at least
; (48000 * 24 * 2 (stereo)) * 4 = 9.216 MHz. Ideally 8x or more.
start_l:
wait 1 pin 1 ; DIN should be sampled on rising transition of BCK
in pins, 1 ; first "bit" of new frame is actually LSB of last frame, per I2S
pull noblock ;
push noblock ;
wait 0 pin 1 ; ignore first BCK transition after edge
public_entry_point:
wait 0 pin 2 ; wait for L frame to come around before we start
out pins 1 ; update DOUT on falling BCK edge
loop_l:
wait 1 pin 1 ; DIN should be sampled on rising transition of BCK
in pins, 1 ; read DIN
wait 0 pin 1 ; DOUT should be updated on falling transition of BCK
out pins 1 ; update DOUT
jmp pin start_r ; if LRCK has gone high, we're "done" with this word (1 bit left to read)
jmp loop_l ;
start_r:
wait 1 pin 1 ; wait for the last bit of the previous frame
in pins, 1 ; first "bit" of new frame is actually LSB of last frame, per I2S
pull noblock ; pull next output word from FIFO
push noblock ; push the completed word to the FIFO
wait 0 pin 1 ; wait for next clock cycle
out pins 1 ; update DOUT on falling edge
loop_r:
wait 1 pin 1 ;
in pins 1 ;
wait 0 pin 1 ;
out pins 1 ; update DOUT
jmp pin loop_r ; if LRCK is still high, we're still sampling this word
; implicit jmp to start_l: otherwise, start the loop over
; I2S Audio Input - Slave or Synchronous with Output Master
; Inputs must be sequential in order: DIN, BCK, LRCK
; Must run at same speed of SCK block, or at least 4x BCK.
;
; NOTE: Set JMP pin to LRCK pin.
; NOTE: The very first word read is potentially corrupt, since there is a
; chance to start in the middle of an L frame and only read part of it.
; Nevertheless, the frame order should be synchronized (first word is L,
; second is R, etc.)
.program i2s_in_slave
start_sample_l:
wait 1 pin 1 ; DIN should be sampled on rising transition of BCK
in pins, 1 ; first "bit" of new frame is actually LSB of last frame, per I2S
push noblock ; push however many bits we read into the FIFO
wait 0 pin 1 ; ignore first BCK transition after edge
public_entry_point:
wait 0 pin 2 ; wait for L frame to come around before we start
sample_l:
wait 1 pin 1 ; DIN should be sampled on rising transition of BCK
in pins, 1 ; read DIN
wait 0 pin 1 ; don't sample more than once per rising edge, wait for next clock
jmp pin start_sample_r ; if LRCK has gone high, we're "done" with this word (1 bit left to read)
jmp sample_l
start_sample_r:
wait 1 pin 1 ; wait for the last bit of the previous frame
in pins, 1 ; first "bit" of new frame is actually LSB of last frame, per I2S
push noblock ; push the completed word to the FIFO
wait 0 pin 1 ; wait for next clock cycle
sample_r:
wait 1 pin 1
in pins, 1
wait 0 pin 1
jmp pin sample_r ; if LRCK is still high, we're still sampling this word
; implicit jmp to start_sample_l: otherwise, start the loop over
% c-sdk {
// These constants are the I2S clock to pio clock ratio
const int i2s_sck_program_pio_mult = 2;
const int i2s_out_master_program_pio_mult = 2;
/*
* System ClocK (SCK) is only required by some I2S peripherals.
* This outputs it at 1 SCK per 2 PIO clocks, so scale the dividers correctly
* first.
* NOTE: Most peripherals require that this is *perfectly* aligned in ratio,
* if not phase, to the bit and word clocks of any master peripherals.
* It is up to you to ensure that the divider config is set up for a
* precise (not approximate) ratio between the BCK, LRCK, and SCK outputs.
*/
static void i2s_sck_program_init(PIO pio, uint8_t sm, uint8_t offset, uint8_t sck_pin) {
pio_gpio_init(pio, sck_pin);
pio_sm_config sm_config = i2s_sck_program_get_default_config(offset);
sm_config_set_set_pins(&sm_config, sck_pin, 1);
uint pin_mask = (1u << sck_pin);
pio_sm_set_pins_with_mask(pio, sm, 0, pin_mask); // zero output
pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
pio_sm_init(pio, sm, offset, &sm_config);
}
static inline void i2s_out_master_program_init(PIO pio, uint8_t sm, uint8_t offset, uint8_t bit_depth, uint8_t dout_pin, uint8_t clock_pin_base) {
pio_gpio_init(pio, dout_pin);
pio_gpio_init(pio, clock_pin_base);
pio_gpio_init(pio, clock_pin_base + 1);
pio_sm_config sm_config = i2s_out_master_program_get_default_config(offset);
sm_config_set_out_pins(&sm_config, dout_pin, 1);
sm_config_set_sideset_pins(&sm_config, clock_pin_base);
sm_config_set_out_shift(&sm_config, false, false, bit_depth);
sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX);
pio_sm_init(pio, sm, offset, &sm_config);
uint32_t pin_mask = (1u << dout_pin) | (3u << clock_pin_base);
pio_sm_set_pins_with_mask(pio, sm, 0, pin_mask); // zero output
pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
}
static inline void i2s_bidi_slave_program_init(PIO pio, uint8_t sm, uint8_t offset, uint8_t dout_pin, uint8_t in_pin_base) {
pio_gpio_init(pio, dout_pin);
pio_gpio_init(pio, in_pin_base);
pio_gpio_init(pio, in_pin_base + 1);
pio_gpio_init(pio, in_pin_base + 2);
pio_sm_config sm_config = i2s_bidi_slave_program_get_default_config(offset);
sm_config_set_out_pins(&sm_config, dout_pin, 1);
sm_config_set_in_pins(&sm_config, in_pin_base);
sm_config_set_jmp_pin(&sm_config, in_pin_base + 2);
sm_config_set_out_shift(&sm_config, false, false, 0);
sm_config_set_in_shift(&sm_config, false, false, 0);
pio_sm_init(pio, sm, offset, &sm_config);
// Setup output pins
uint32_t pin_mask = (1u << dout_pin);
pio_sm_set_pins_with_mask(pio, sm, 0, pin_mask); // zero output
pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
// Setup input pins
pin_mask = (7u << in_pin_base); // Three input pins
pio_sm_set_pindirs_with_mask(pio, sm, 0, pin_mask);
}
/*
* Designed to be used with output master module, requiring overlapping pins:
* din_pin_base + 0 = input pin
* din_pin_base + 1 = out_master clock_pin_base
* din_pin_base + 2 = out_master clock_pin_base + 1
*
* Intended to be run at SCK rate (4x BCK), so clock same as SCK module if using
* it, or 4x the BCK frequency (BCK is 64x fs, so 256x fs).
*/
static inline void i2s_in_slave_program_init(PIO pio, uint8_t sm, uint8_t offset, uint8_t din_pin_base) {
pio_gpio_init(pio, din_pin_base);
gpio_set_pulls(din_pin_base, false, false);
gpio_set_dir(din_pin_base, GPIO_IN);
pio_sm_config sm_config = i2s_in_slave_program_get_default_config(offset);
sm_config_set_in_pins(&sm_config, din_pin_base);
sm_config_set_in_shift(&sm_config, false, false, 0);
sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_RX);
sm_config_set_jmp_pin(&sm_config, din_pin_base + 2);
pio_sm_init(pio, sm, offset, &sm_config);
uint32_t pin_mask = (7u << din_pin_base); // Three input pins
pio_sm_set_pindirs_with_mask(pio, sm, 0, pin_mask);
}
%}