|
| 1 | +# The MIT License (MIT) |
| 2 | +# |
| 3 | +# Copyright (c) 2017 Tony DiCola for Adafruit Industries |
| 4 | +# |
| 5 | +# Permission is hereby granted, free of charge, to any person obtaining a copy |
| 6 | +# of this software and associated documentation files (the "Software"), to deal |
| 7 | +# in the Software without restriction, including without limitation the rights |
| 8 | +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 9 | +# copies of the Software, and to permit persons to whom the Software is |
| 10 | +# furnished to do so, subject to the following conditions: |
| 11 | +# |
| 12 | +# The above copyright notice and this permission notice shall be included in |
| 13 | +# all copies or substantial portions of the Software. |
| 14 | +# |
| 15 | +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 16 | +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 17 | +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 18 | +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 19 | +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 20 | +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| 21 | +# THE SOFTWARE. |
| 22 | +""" |
| 23 | +`adafruit_vs1053` |
| 24 | +==================================================== |
| 25 | +
|
| 26 | +Driver for interacting and playing media files with the VS1053 audio codec over |
| 27 | +a SPI connection. |
| 28 | +
|
| 29 | +NOTE: This is not currently working for audio playback of files. Only sine |
| 30 | +wave test currently works. The problem is that pure Python code is currently |
| 31 | +too slow to keep up with feeding data to the VS1053 fast enough. There's no |
| 32 | +interrupt support so Python code has to monitor the DREQ line and provide a |
| 33 | +small buffer of data when ready, but the overhead of the interpretor means we |
| 34 | +can't keep up. Optimizing SPI to use DMA transfers could help but ultimately |
| 35 | +an interrupt-based approach is likely what can make this work better (or C |
| 36 | +functions built in to custom builds that monitor the DREQ line and feed a |
| 37 | +buffer of data). |
| 38 | +
|
| 39 | +* Author(s): Tony DiCola |
| 40 | +""" |
| 41 | +import digitalio |
| 42 | +import time |
| 43 | + |
| 44 | +from adafruit_bus_device.spi_device import SPIDevice |
| 45 | + |
| 46 | + |
| 47 | +_COMMAND_BAUDRATE = const(250000) # Speed for command transfers (MUST be slow) |
| 48 | +_DATA_BAUDRATE = const(8000000) # Speed for data transfers (fast!) |
| 49 | + |
| 50 | +_VS1053_SCI_READ = const(0x03) |
| 51 | +_VS1053_SCI_WRITE = const(0x02) |
| 52 | + |
| 53 | +_VS1053_REG_MODE = const(0x00) |
| 54 | +_VS1053_REG_STATUS = const(0x01) |
| 55 | +_VS1053_REG_BASS = const(0x02) |
| 56 | +_VS1053_REG_CLOCKF = const(0x03) |
| 57 | +_VS1053_REG_DECODETIME = const(0x04) |
| 58 | +_VS1053_REG_AUDATA = const(0x05) |
| 59 | +_VS1053_REG_WRAM = const(0x06) |
| 60 | +_VS1053_REG_WRAMADDR = const(0x07) |
| 61 | +_VS1053_REG_HDAT0 = const(0x08) |
| 62 | +_VS1053_REG_HDAT1 = const(0x09) |
| 63 | +_VS1053_REG_VOLUME = const(0x0B) |
| 64 | + |
| 65 | +_VS1053_GPIO_DDR = const(0xC017) |
| 66 | +_VS1053_GPIO_IDATA = const(0xC018) |
| 67 | +_VS1053_GPIO_ODATA = const(0xC019) |
| 68 | + |
| 69 | +_VS1053_INT_ENABLE = const(0xC01A) |
| 70 | + |
| 71 | +_VS1053_MODE_SM_DIFF = const(0x0001) |
| 72 | +_VS1053_MODE_SM_LAYER12 = const(0x0002) |
| 73 | +_VS1053_MODE_SM_RESET = const(0x0004) |
| 74 | +_VS1053_MODE_SM_CANCEL = const(0x0008) |
| 75 | +_VS1053_MODE_SM_EARSPKLO = const(0x0010) |
| 76 | +_VS1053_MODE_SM_TESTS = const(0x0020) |
| 77 | +_VS1053_MODE_SM_STREAM = const(0x0040) |
| 78 | +_VS1053_MODE_SM_SDINEW = const(0x0800) |
| 79 | +_VS1053_MODE_SM_ADPCM = const(0x1000) |
| 80 | +_VS1053_MODE_SM_LINE1 = const(0x4000) |
| 81 | +_VS1053_MODE_SM_CLKRANGE = const(0x8000) |
| 82 | + |
| 83 | + |
| 84 | +class VS1053: |
| 85 | + |
| 86 | + # Class-level buffer for read and write commands. |
| 87 | + # This is NOT thread/re-entrant safe (by design, for less memory hit). |
| 88 | + _SCI_SPI_BUFFER = bytearray(4) |
| 89 | + |
| 90 | + def __init__(self, spi, cs, xdcs, dreq): |
| 91 | + # Create SPI device for VS1053 |
| 92 | + self._cs = digitalio.DigitalInOut(cs) |
| 93 | + self._vs1053_spi = SPIDevice(spi, self._cs, baudrate=_COMMAND_BAUDRATE, polarity=0, phase=0) |
| 94 | + # Setup control lines. |
| 95 | + self._xdcs = digitalio.DigitalInOut(xdcs) |
| 96 | + self._xdcs.switch_to_output(value=True) |
| 97 | + self._dreq = digitalio.DigitalInOut(dreq) |
| 98 | + self._dreq.switch_to_input() |
| 99 | + # Reset chip. |
| 100 | + self.reset() |
| 101 | + # Check version is 4 (VS1053 ID). |
| 102 | + if self.version != 4: |
| 103 | + raise RuntimeError('Expected version 4 (VS1053) but got: {} Check wiring!'.format(self.version)) |
| 104 | + |
| 105 | + def _sci_write(self, address, value): |
| 106 | + # Write a 16-bit big-endian value to the provided 8-bit address. |
| 107 | + self._SCI_SPI_BUFFER[0] = _VS1053_SCI_WRITE |
| 108 | + self._SCI_SPI_BUFFER[1] = address & 0xFF |
| 109 | + self._SCI_SPI_BUFFER[2] = (value >> 8) & 0xFF |
| 110 | + self._SCI_SPI_BUFFER[3] = value & 0xFF |
| 111 | + with self._vs1053_spi as spi: |
| 112 | + spi.configure(baudrate=_COMMAND_BAUDRATE) |
| 113 | + spi.write(self._SCI_SPI_BUFFER) |
| 114 | + |
| 115 | + def _sci_read(self, address): |
| 116 | + # Read a 16-bit big-endian value from the provided 8-bit address. |
| 117 | + # Write a 16-bit big-endian value to the provided 8-bit address. |
| 118 | + self._SCI_SPI_BUFFER[0] = _VS1053_SCI_READ |
| 119 | + self._SCI_SPI_BUFFER[1] = address & 0xFF |
| 120 | + with self._vs1053_spi as spi: |
| 121 | + spi.configure(baudrate=_COMMAND_BAUDRATE) |
| 122 | + spi.write(self._SCI_SPI_BUFFER, end=2) |
| 123 | + time.sleep(0.00001) # Delay 10 microseconds (at least) |
| 124 | + spi.readinto(self._SCI_SPI_BUFFER, end=2) |
| 125 | + return (self._SCI_SPI_BUFFER[0] << 8) | self._SCI_SPI_BUFFER[1] |
| 126 | + |
| 127 | + def soft_reset(self): |
| 128 | + """Perform a quick soft reset of the VS1053.""" |
| 129 | + self._sci_write(_VS1053_REG_MODE, _VS1053_MODE_SM_SDINEW | _VS1053_MODE_SM_RESET) |
| 130 | + time.sleep(0.1) |
| 131 | + |
| 132 | + def reset(self): |
| 133 | + """Perform a longer full reset with clock and volume reset too.""" |
| 134 | + self._xdcs.value = True |
| 135 | + self.soft_reset() |
| 136 | + time.sleep(0.1) |
| 137 | + self._sci_write(_VS1053_REG_CLOCKF, 0x6000) |
| 138 | + self.set_volume(40, 40) |
| 139 | + |
| 140 | + def set_volume(self, left, right): |
| 141 | + """Set the volume of the left and right channels to the provided byte |
| 142 | + value (0-255), the lower the louder. |
| 143 | + """ |
| 144 | + volume = ((left & 0xFF) << 8) | (right & 0xFF) |
| 145 | + self._sci_write(_VS1053_REG_VOLUME, volume) |
| 146 | + |
| 147 | + @property |
| 148 | + def ready_for_data(self): |
| 149 | + """Return True if the VS1053 is ready to accept data, false otherwise. |
| 150 | + """ |
| 151 | + return self._dreq.value |
| 152 | + |
| 153 | + @property |
| 154 | + def version(self): |
| 155 | + """Return the status register version value.""" |
| 156 | + return (self._sci_read(_VS1053_REG_STATUS) >> 4) & 0x0F |
| 157 | + |
| 158 | + @property |
| 159 | + def decode_time(self): |
| 160 | + """Return the decode time register value. This is the amount of time |
| 161 | + the current file has been played back in seconds.""" |
| 162 | + return self._sci_read(_VS1053_REG_DECODETIME) |
| 163 | + |
| 164 | + @decode_time.setter |
| 165 | + def decode_time(self, value): |
| 166 | + """Set the decode time register value.""" |
| 167 | + # From datasheet, set twice to ensure it is correctly set (pg. 43) |
| 168 | + self._sci_write(_VS1053_REG_DECODETIME, value) |
| 169 | + |
| 170 | + @property |
| 171 | + def byte_rate(self): |
| 172 | + """Return the bit rate in bytes per second (computed each second). |
| 173 | + Useful to know if a song is being played and how fast it's happening. |
| 174 | + """ |
| 175 | + self._sci_write(_VS1053_REG_WRAMADDR, 0x1e05) |
| 176 | + return self._sci_read(_VS1053_REG_WRAM) |
| 177 | + |
| 178 | + def start_playback(self): |
| 179 | + """Prepare for playback of a file. After calling this check the |
| 180 | + ready_for_data property continually until true and then send in |
| 181 | + buffers of music data to the play_data function. |
| 182 | + """ |
| 183 | + # Reset playback. |
| 184 | + self._sci_write(_VS1053_REG_MODE, _VS1053_MODE_SM_LINE1 | _VS1053_MODE_SM_SDINEW) |
| 185 | + # Resync. |
| 186 | + self._sci_write(_VS1053_REG_WRAMADDR, 0x1e29) |
| 187 | + self._sci_write(_VS1053_REG_WRAM, 0) |
| 188 | + # Set time to zero. |
| 189 | + self.decode_time = 0 |
| 190 | + |
| 191 | + def stop_playback(self): |
| 192 | + """Stop any playback of audio.""" |
| 193 | + self._sci_write(_VS1053_REG_MODE, _VS1053_MODE_SM_LINE1 | _VS1053_MODE_SM_SDINEW | _VS1053_MODE_SM_CANCEL) |
| 194 | + |
| 195 | + def play_data(self, data_buffer, start=0, end=None): |
| 196 | + """Send a buffer of file data to the VS1053 for playback. Make sure |
| 197 | + the ready_for_data property is True before calling! |
| 198 | + """ |
| 199 | + try: |
| 200 | + if end is None: |
| 201 | + end = len(data_buffer) |
| 202 | + self._xdcs.value = False |
| 203 | + with self._vs1053_spi as spi: |
| 204 | + spi.configure(baudrate=_DATA_BAUDRATE) |
| 205 | + spi.write(data_buffer, start=start, end=end) |
| 206 | + finally: |
| 207 | + self._xdcs.value = True |
| 208 | + |
| 209 | + def sine_test(self, n, seconds): |
| 210 | + """Play a sine wave for the specified number of seconds. Useful to |
| 211 | + test the VS1053 is working. |
| 212 | + """ |
| 213 | + self.reset() |
| 214 | + mode = self._sci_read(_VS1053_REG_MODE) |
| 215 | + mode |= 0x0020 |
| 216 | + self._sci_write(_VS1053_REG_MODE, mode) |
| 217 | + while not self.ready_for_data: |
| 218 | + pass |
| 219 | + try: |
| 220 | + self._xdcs.value = False |
| 221 | + with self._vs1053_spi as spi: |
| 222 | + spi.configure(baudrate=_DATA_BAUDRATE) |
| 223 | + spi.write(bytes([0x53, 0xEF, 0x6E, n & 0xFF, 0x00, 0x00, |
| 224 | + 0x00, 0x00])) |
| 225 | + finally: |
| 226 | + self._xdcs.value = True |
| 227 | + time.sleep(seconds) |
| 228 | + try: |
| 229 | + self._xdcs.value = False |
| 230 | + with self._vs1053_spi as spi: |
| 231 | + spi.configure(baudrate=_DATA_BAUDRATE) |
| 232 | + spi.write(bytes([0x45, 0x78, 0x69, 0x74, 0x00, 0x00, 0x00, |
| 233 | + 0x00])) |
| 234 | + finally: |
| 235 | + self._xdcs.value = True |
0 commit comments