├── LICENSE ├── README.md ├── dfplayer.jpg └── lib └── DFPlayer.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 J-Christophe Bos (Micropython version) 4 | Copyright (c) 2019 Bernhard Bablok (CircuitPython port) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | This is a library for the mini-dfplayer, a minimalistic MP3-player 5 | available for a few bucks from Ebay, Amazon and other distributors. 6 | 7 | ![](dfplayer.jpg) 8 | 9 | The chip has a number of pins for direct control, but also an 10 | UART-interface and this library controls the chip using the documented 11 | commands. 12 | 13 | The core of the code is from a micropython implementation of the interface 14 | [https://github.com/jczic/KT403A-MP3](https://github.com/jczic/KT403A-MP3). 15 | 16 | 17 | Installation 18 | ------------ 19 | 20 | Just copy the file `lib/DFPlayer.py` to your `lib`-directory on your board. 21 | 22 | 23 | Usage 24 | ----- 25 | 26 | Import the library and create a `DFPlayer`-object. If you don't pass an 27 | uart, the constructor uses `board.RX` and `board.TX` to create a default. 28 | Use the created object to control the player. For the API just have a 29 | look at the source-code. 30 | 31 | A real-world example can be found in the project: 32 | [https://github.com/bablokb/xmas-music-box](https://github.com/bablokb/xmas-music-box). -------------------------------------------------------------------------------- /dfplayer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bablokb/circuitpython-dfplayer/46e7fa2d4266a309d199d18eb36e2ea5f1cc40b3/dfplayer.jpg -------------------------------------------------------------------------------- /lib/DFPlayer.py: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # CircuitPython driver library for the DFPlayer-Mini. 3 | # 4 | # The core of the code is from: https://github.com/jczic/KT403A-MP3 5 | # (adapted to CircuitPython, changed naming, stripped down API) 6 | # 7 | # Author: Bernhard Bablok 8 | # License: MIT 9 | # 10 | # Website: https://github.com/bablokb/circuitpython-dfplayer 11 | # 12 | # ---------------------------------------------------------------------------- 13 | 14 | import board 15 | import time 16 | import busio 17 | import struct 18 | 19 | class DFPlayer(object): 20 | 21 | # --- constants ---------------------------------------------------------- 22 | 23 | MEDIA_U_DISK = 1 24 | MEDIA_SD = 2 25 | MEDIA_AUX = 3 26 | MEDIA_SLEEP = 4 27 | MEDIA_FLASH = 5 28 | 29 | EQ_NORMAL = 0 30 | EQ_POP = 1 31 | EQ_ROCK = 2 32 | EQ_JAZZ = 3 33 | EQ_CLASSIC = 4 34 | EQ_BASS = 5 35 | 36 | STATUS_STOPPED = 0x0200 37 | STATUS_BUSY = 0x0201 38 | STATUS_PAUSED = 0x0202 39 | 40 | # --- constructor -------------------------------------------------------- 41 | 42 | def __init__(self,uart=None,media=None,volume=50,eq=None,latency=0.100): 43 | if uart is None: 44 | self._uart = busio.UART(board.TX,board.RX,baudrate=9600) 45 | else: 46 | self._uart = uart 47 | self._latency = latency 48 | self.set_media(media if media else DFPlayer.MEDIA_SD) 49 | if not self.get_status(): 50 | raise Exception('DFPlayer could not be initialized.') 51 | self.set_volume(volume) 52 | self.set_eq(eq if eq else DFPlayer.EQ_NORMAL) 53 | 54 | # --- transfer data to device --------------------------------------------- 55 | 56 | def _write_data(self, cmd, dataL=0, dataH=0): 57 | self._uart.write(b'\x7E') # Start 58 | self._uart.write(b'\xFF') # Firmware version 59 | self._uart.write(b'\x06') # Command length 60 | self._uart.write(bytes([cmd])) # Command word 61 | self._uart.write(b'\x00') # Feedback flag 62 | self._uart.write(bytes([dataH])) # DataH 63 | self._uart.write(bytes([dataL])) # DataL 64 | self._uart.write(b'\xEF') # Stop 65 | 66 | # give device some time 67 | if cmd == 0x09: # set_media 68 | time.sleep(0.200) 69 | elif cmd == 0x0C: # reset 70 | time.sleep(1.000) 71 | elif cmd in [0x47,0x48,0x49,0x4E]: # query files 72 | time.sleep(0.500) 73 | else: 74 | time.sleep(self._latency) # other commands 75 | 76 | # --- read data from device ------------------------------------------------ 77 | 78 | def _read_data(self): 79 | if self._uart.in_waiting: 80 | buf = self._uart.read(10) 81 | if buf is not None and \ 82 | len(buf) == 10 and \ 83 | buf[0] == 0x7E and \ 84 | buf[1] == 0xFF and \ 85 | buf[2] == 0x06 and \ 86 | buf[9] == 0xEF: 87 | cmd = buf[3] 88 | data = struct.unpack('>H', buf[5:7])[0] 89 | return (cmd, data) 90 | return None 91 | 92 | # --- get response --------------------------------------------------------- 93 | 94 | def _read_response(self): 95 | res = None 96 | while True: 97 | r = self._read_data() 98 | if not r: 99 | return res 100 | res = r 101 | 102 | # --- play ----------------------------------------------------------------- 103 | 104 | def play(self,folder=None,track=None): 105 | if folder is None and track is None: 106 | self._write_data(0x0D) 107 | elif folder is None: 108 | self._write_data(0x03,int(track%256),int(track/256)) 109 | elif track is None: 110 | self._write_data(0x12,folder) 111 | else: 112 | self._write_data(0x0F,track,folder) 113 | 114 | # --- play random song ------------------------------------------------------ 115 | 116 | def random(self): 117 | self._write_data(0x18) 118 | 119 | # --- pause ---------------------------------------------------------------- 120 | 121 | def pause(self): 122 | self._write_data(0x0E) 123 | 124 | # --- stop ----------------------------------------------------------------- 125 | 126 | def stop(self): 127 | self._write_data(0x16) 128 | 129 | # --- play next song ------------------------------------------------------- 130 | 131 | def next(self): 132 | self._write_data(0x01) 133 | 134 | # --- play previous song --------------------------------------------------- 135 | 136 | def previous(self): 137 | self._write_data(0x02) 138 | 139 | # ---volume up ------------------------------------------------------------- 140 | 141 | def volume_up(self): 142 | self._write_data(0x04) 143 | 144 | # --- volume down ---------------------------------------------------------- 145 | 146 | def volume_down(self): 147 | self._write_data(0x05) 148 | 149 | # --- set volume ----------------------------------------------------------- 150 | 151 | def set_volume(self,percent): 152 | if percent < 0: 153 | percent = 0 154 | elif percent > 100: 155 | percent = 100 156 | self._write_data(0x06, int(percent*0x1E/100)) 157 | 158 | # --- read volume ---------------------------------------------------------- 159 | 160 | def get_volume(self): 161 | self._write_data(0x43) 162 | r = self._read_response() 163 | return int(r[1] / 0x1E *100) if r and r[0] == 0x43 else 0 164 | 165 | # --- set equalizer -------------------------------------------------------- 166 | 167 | def set_eq(self,eq): 168 | if eq < 0 or eq > 5: 169 | eq = 0 170 | self._write_data(0x07,eq) 171 | 172 | # --- read equalizer ------------------------------------------------------- 173 | 174 | def get_eq(self): 175 | self._write_data(0x44) 176 | r = self._read_response() 177 | return r[1] if r and r[0] == 0x44 else 0 178 | 179 | # --- loop current song ---------------------------------------------------- 180 | 181 | def loop(self): 182 | self._write_data(0x08) 183 | 184 | # --- set media device ----------------------------------------------------- 185 | 186 | def set_media(self,media): 187 | self._media = media 188 | self._write_data(0x09, media) 189 | 190 | # --- switch to low power state -------------------------------------------- 191 | 192 | def set_standby(self,on=True): 193 | if on: 194 | self._write_data(0x0A) 195 | else: 196 | self._write_data(0x0B) 197 | 198 | # --- reset chip ----------------------------------------------------------- 199 | 200 | def reset(self): 201 | self._write_data(0x0C) 202 | 203 | # --- loop over all files -------------------------------------------------- 204 | 205 | def loop_all(self,on=True): 206 | self._write_data(0x11,1 if on else 0) 207 | 208 | # --- read busy state ------------------------------------------------------ 209 | 210 | def get_status(self): 211 | self._write_data(0x42) 212 | r = self._read_response() 213 | return r[1] if r and r[0] == 0x42 else None 214 | 215 | # --- query number of files ------------------------------------------------ 216 | 217 | def num_files(self,folder=None,media=None): 218 | if folder is not None: 219 | self._write_data(0x4E,folder) 220 | r = self._read_response() 221 | return r[1] if r and r[0] == 0x4E else 0 222 | 223 | if media is None: 224 | media = self._media 225 | if media == DFPlayer.MEDIA_U_DISK: 226 | self._write_data(0x47) 227 | elif media == DFPlayer.MEDIA_SD: 228 | self._write_data(0x48) 229 | elif media == DFPlayer.MEDIA_FLASH: 230 | self._write_data(0x49) 231 | else: 232 | return 0 233 | r = self._read_response() 234 | if r and r[0] >= 0x47 and r[0] <= 0x49: 235 | return r[1] 236 | else: 237 | return 0 238 | --------------------------------------------------------------------------------