|
| 1 | +# SPDX-FileCopyrightText: 2024 Tim Chinowsky |
| 2 | +# SPDX-License-Identifier: MIT |
| 3 | + |
| 4 | +import array |
| 5 | +import board |
| 6 | +import rp2pio |
| 7 | + |
| 8 | +import adafruit_pioasm |
| 9 | + |
| 10 | +# Implement extended multichannel I2S interface like that used by audio codecs |
| 11 | +# such as the TAC5212. In extended I2S, "Left" and "Right" can each contain |
| 12 | +# multiple channels, so for instance 8 channels of audio can be sent as a "left" |
| 13 | +# containing 4 channels and a "right" containing 4 channels. |
| 14 | + |
| 15 | +# In this implementation the number of bits per sample, sample rate, and |
| 16 | +# number of channels can be independently specified. The number of channels must |
| 17 | +# be even, to divide evenly between left and right. |
| 18 | + |
| 19 | +# Ramped test data containing the requested number of sample sets (one set = one |
| 20 | +# sample for each channel) and spanning the specified number of bits will be generated |
| 21 | +# and sent out over I2S on the specified pins. |
| 22 | + |
| 23 | +# Data will be preceded and followed by a set of zeros for convenience. |
| 24 | +# (Some protocol analyzers have trouble analyzing serial data at the the beginning |
| 25 | +# and end of a data set) |
| 26 | + |
| 27 | +# At the same time that I2S data is sent out the out_pin, I2S data will be received |
| 28 | +# on the in_pin. If the output is looped back (connected) to the input, the data |
| 29 | +# received should be the same as the data sent. |
| 30 | + |
| 31 | +# Some samples run in loopback configuration: |
| 32 | + |
| 33 | +# bits per sample: 16 |
| 34 | +# sample rate: 48000 |
| 35 | +# channels: 4 |
| 36 | +# sample sets: 4 |
| 37 | + |
| 38 | +# actual sample frequency 47984.6 Hz |
| 39 | +# bit clock 3071017.0 Hz |
| 40 | + |
| 41 | +# write: 00000000 00000000 00000000 00000000 |
| 42 | +# read: 00000000 00000000 00000000 00000000 |
| 43 | + |
| 44 | +# write: 00000000 00001111 00002222 00003333 |
| 45 | +# read: 00000000 00001111 00002222 00003333 |
| 46 | + |
| 47 | +# write: 00004444 00005555 00006666 00007777 |
| 48 | +# read: 00004444 00005555 00006666 00007777 |
| 49 | + |
| 50 | +# write: 00008888 00009999 0000aaaa 0000bbbb |
| 51 | +# read: 00008888 00009999 0000aaaa 0000bbbb |
| 52 | + |
| 53 | +# write: 0000cccc 0000dddd 0000eeee 0000ffff |
| 54 | +# read: 0000cccc 0000dddd 0000eeee 0000ffff |
| 55 | + |
| 56 | +# write: 00000000 00000000 00000000 00000000 |
| 57 | +# read: 00000000 00000000 00000000 00000000 |
| 58 | + |
| 59 | +# bits per sample: 24 |
| 60 | +# sample rate: 24000 |
| 61 | +# channels: 8 |
| 62 | +# sample sets: 5 |
| 63 | + |
| 64 | +# actual sample frequency 23987.7 Hz |
| 65 | +# bit clock 4605642.0 Hz |
| 66 | + |
| 67 | +# write: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 |
| 68 | +# read: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 |
| 69 | + |
| 70 | +# write: 00000000 00069069 000d20d2 0013b13b 001a41a4 0020d20d 00276276 002df2df |
| 71 | +# read: 00000000 00069069 000d20d2 0013b13b 001a41a4 0020d20d 00276276 002df2df |
| 72 | + |
| 73 | +# write: 00348348 003b13b1 0041a41a 00483482 004ec4ec 00555554 005be5be 00627626 |
| 74 | +# read: 00348348 003b13b1 0041a41a 00483482 004ec4ec 00555554 005be5be 00627626 |
| 75 | + |
| 76 | +# write: 00690690 006f96f8 00762762 007cb7ca 00834834 0089d89c 00906904 0096f96c |
| 77 | +# read: 00690690 006f96f8 00762762 007cb7ca 00834834 0089d89c 00906904 0096f96c |
| 78 | + |
| 79 | +# write: 009d89d8 00a41a40 00aaaaa8 00b13b10 00b7cb7c 00be5be4 00c4ec4c 00cb7cb4 |
| 80 | +# read: 009d89d8 00a41a40 00aaaaa8 00b13b10 00b7cb7c 00be5be4 00c4ec4c 00cb7cb4 |
| 81 | + |
| 82 | +# write: 00d20d20 00d89d88 00df2df0 00e5be58 00ec4ec4 00f2df2c 00f96f94 00ffffff |
| 83 | +# read: 00d20d20 00d89d88 00df2df0 00e5be58 00ec4ec4 00f2df2c 00f96f94 00ffffff |
| 84 | + |
| 85 | +# write: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 |
| 86 | +# read: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 |
| 87 | + |
| 88 | + |
| 89 | +def i2s_codec( # pylint: disable=too-many-arguments |
| 90 | + channels=2, |
| 91 | + sample_rate=48000, |
| 92 | + bits=16, |
| 93 | + bclk_pin=None, |
| 94 | + out_pin=None, |
| 95 | + in_pin=None, |
| 96 | +): |
| 97 | + i2s_clock = sample_rate * channels * bits |
| 98 | + pio_clock = 4 * i2s_clock |
| 99 | + pio_code = """ |
| 100 | + .program i2s_codec |
| 101 | + .side_set 2 |
| 102 | + ; at program start we initialize the bit count top |
| 103 | + ; (which may be >32) with data |
| 104 | + ; pulled from the input fifo |
| 105 | + pull noblock ; first empty the input fifo |
| 106 | + pull noblock |
| 107 | + pull noblock |
| 108 | + pull noblock |
| 109 | + out null, 32 ; then clear OSR so we can get a new value |
| 110 | + pull block ; then get the bit count top value from the fifo |
| 111 | + ; /--- LRCLK |
| 112 | + ; |/-- BCLK |
| 113 | + ; || |
| 114 | + mov x, osr; side 0b01 [1] ; save it in x |
| 115 | + out null, 32 side 0b00 [1] |
| 116 | + mov y, x side 0b01 [1] ; start of main loop (wrap target=8) |
| 117 | + bitloop1: |
| 118 | + out pins 1 side 0b00 |
| 119 | + in pins 1 side 0b00 |
| 120 | + jmp y-- bitloop1 side 0b01 [1] |
| 121 | + out pins 1 side 0b10 |
| 122 | + in pins 1 side 0b10 |
| 123 | + mov y, x side 0b11 [1] |
| 124 | + bitloop0: |
| 125 | + out pins 1 side 0b10 |
| 126 | + in pins 1 side 0b10 |
| 127 | + jmp y-- bitloop0 side 0b11 [1] |
| 128 | + out pins 1 side 0b00 |
| 129 | + in pins 1 side 0b00 |
| 130 | + """ |
| 131 | + pio_params = { |
| 132 | + "frequency": pio_clock, |
| 133 | + "first_out_pin": out_pin, |
| 134 | + "first_in_pin": in_pin, |
| 135 | + "first_sideset_pin": bclk_pin, |
| 136 | + "sideset_pin_count": 2, |
| 137 | + "auto_pull": True, |
| 138 | + "auto_push": True, |
| 139 | + "out_shift_right": False, |
| 140 | + "in_shift_right": False, |
| 141 | + "pull_threshold": bits, |
| 142 | + "push_threshold": bits, |
| 143 | + "wait_for_txstall": False, |
| 144 | + "wrap_target": 8, |
| 145 | + } |
| 146 | + pio_instructions = adafruit_pioasm.assemble(pio_code) |
| 147 | + i2s_clock = sample_rate * channels * bits |
| 148 | + pio_clock = 4 * i2s_clock |
| 149 | + pio = rp2pio.StateMachine(pio_instructions, **pio_params) |
| 150 | + return pio |
| 151 | + |
| 152 | + |
| 153 | +def spaced_samples(length, bits): |
| 154 | + max_int = (1 << bits) - 1 |
| 155 | + if length == 1: |
| 156 | + return [0] |
| 157 | + step = max_int / (length - 1) |
| 158 | + result = [round(i * step) for i in range(length)] |
| 159 | + result[0] = 0 |
| 160 | + result[-1] = max_int |
| 161 | + return result |
| 162 | + |
| 163 | + |
| 164 | +while True: |
| 165 | + print() |
| 166 | + BITS = int(input("# bits per sample: ")) |
| 167 | + SAMPLE_RATE = int(input("# sample rate: ")) |
| 168 | + CHANNELS = int(input("# channels: ")) |
| 169 | + SAMPLE_SETS = int(input("# sample sets: ")) |
| 170 | + |
| 171 | + n_samples = CHANNELS * SAMPLE_SETS |
| 172 | + buffer_type = "L" |
| 173 | + buffer_width = 32 |
| 174 | + data = [0] * CHANNELS + spaced_samples(n_samples, BITS) + [0] * CHANNELS |
| 175 | + # initialize pio bit count top value by sending it at the start of output data |
| 176 | + bit_count_top = BITS * (CHANNELS // 2) - 2 |
| 177 | + buffer_out = array.array( |
| 178 | + buffer_type, [bit_count_top] + [d << (buffer_width - BITS) for d in data] |
| 179 | + ) |
| 180 | + buffer_in = array.array(buffer_type, [0] * len(data)) |
| 181 | + |
| 182 | + PIO = i2s_codec( |
| 183 | + channels=CHANNELS, |
| 184 | + bits=BITS, |
| 185 | + sample_rate=SAMPLE_RATE, |
| 186 | + out_pin=board.D9, |
| 187 | + in_pin=board.D10, |
| 188 | + bclk_pin=board.D5, # L/R signal will be one pin higher, i.e. D6 |
| 189 | + ) |
| 190 | + print() |
| 191 | + print(f"# actual sample frequency {PIO.frequency/4/CHANNELS/BITS:9.1f} Hz") |
| 192 | + print(f"# bit clock {PIO.frequency/4:9.1f} Hz") |
| 193 | + print() |
| 194 | + PIO.write_readinto(buffer_out, buffer_in) |
| 195 | + start = 0 |
| 196 | + line_length = CHANNELS |
| 197 | + |
| 198 | + while start < len(buffer_in): |
| 199 | + print("# write: ", end="") |
| 200 | + for i in range(start, min(len(data), start + line_length)): |
| 201 | + print(f"{data[i]:0{buffer_width/4}x} ", end=" ") |
| 202 | + print() |
| 203 | + print("# read: ", end="") |
| 204 | + for i in range(start, min(len(buffer_in), start + line_length)): |
| 205 | + print(f"{buffer_in[i]:0{buffer_width/4}x} ", end=" ") |
| 206 | + print() |
| 207 | + print() |
| 208 | + start += line_length |
| 209 | + PIO.deinit() |
| 210 | + del PIO |
0 commit comments