''' SL-MarineMuseumPiano.py Brought to you by your friends at The Squirrel's Lair! Uses a Raspberry Pi Pico (RP2040) and DFPlayer Mini *****Pinout***** Pico VBUS (40) DFPlayer Mini VCC GND (38) DFPlayer Mini GND GP0 (1) DFPlayer Mini UART RX GP1 (2) DFPlayer Mini UART TX GP17 (22) RCWL-0516 SIGNAL OUT GP25 (internal) Pico onboard LED *****How it works***** 1) Idle state. Checks for sensor activation on GP17. 2) If sensor is high, check to see if sound is playing. If not, play a random track. 3) If sensor is low, wait for 1s then go back to step 1 (DFPlayer Mini will stop when track is done). *****DFPlayer Mini info***** UART config: 9600 8N1 *Instruction format* $S VERSION Length CMD Feedback para1 para2 checksumMSB checksumLSB $O $S (start_byte) is the start byte, always 0x7e (126) VERSION (ver) is the version of the command that's being sent, we will always use 0xff (as per the datasheet) Length (instruction_length) is how many bytes there are in the instruction between VERSION and para2 inclusive (always 0x06 for our uses) CMD (cmd) is the command that's being sent, see below for list of commands Feedback (feedback_request) is whether the DFPlayer Mini will respond with acknowledgement to commands (set to 0x0 for no) para1 (cmd_opt_MSB) is the MSB of the options that are being sent with the command (i.e. which track to play) Almost always set to 0x0 unless requesting track or folder number that's >255 para2 (cmd_opt_LSB) is the LSB of the parameters that are being sent with the command (i.e. which track to play) ChecksumMSB, see below for calculating this ChecksumLSB, see below for calculating this $O (end_byte) is the end byte, always 0xef (239) *Checksum calculation* checksum = 0 - version - length - command - feedback - para1 - para2 + (1 << 16) Then the checksum MSB = checksum >> 8 and the checksum LSB = checksum & 0xff *Commands* 0x03: Specify track (0-2999) 0x04: Increase volume 0x05: Decrease volume 0x06: Specify volume (0-30) 0x07: Specify EQ (0-5, Normal/Pop/Rock/Jazz/Classic/Bass) 0x08: Specify playback mode (0-3, Repeat, folder repeat, single repeat, random) 0x09: Specify playback source (0-4, U/TF/AUX/SLEEP/FLASH) 0x0A: Enter standby 0x0B: Enter normal mode 0x0C: Reset module 0x0D: Playback 0x0E: Pause 0x3F: Query initialization parameters 0x42: Query current status 0x43: Query current volume 0x44: Query current EQ 0x45: Query current playback mode 0x47: Query the total number of TF card files 0x4B: Query the current track of TF card (skipped a bunch because they're not useful here) * Relevant links* https://wiki.dfrobot.com/DFPlayer_Mini_SKU_DFR0299 https://stackoverflow.com/questions/69890233/how-to-calculate-checksum-for-dfplayer-mini https://github.com/PowerBroker2/DFPlayerMini_Fast/blob/master/src/DFPlayerMini_Fast.cpp https://wiki.keyestudio.com/KS0387_keyestudio_YX5200-24SS_MP3_Module ''' from machine import Pin, UART, ADC import time import struct import random ##### VARIABLES AND PERIPHERALS START ##### # Set up onboard LED led = Pin(25, Pin.OUT) # Set up pushbutton switch (GP17) as input pbutton = Pin(17, Pin.IN) # Set up UART uart = UART(0, baudrate=9600, tx=Pin(0), rx=Pin(1), timeout=100) start_byte = 0x7e end_byte = 0xef instruction_length = 0x06 ver = 0xff feedback_request = 0x00 # Set volume for speaker volume = 17 ##### VARIABLES AND PERIPHERALS END ##### ##### FUNCTIONS START ##### def show_error(error_type): if error_type == "DEV_COMM": # Can't talk with the DFPlayer Mini. # Blink 1x while True: print("Device communication error") for t in range(0, 1, 1): led.value(1) time.sleep(0.1) led.value(0) time.sleep(0.25) time.sleep(3) elif error_type == "SD_CARD": # Problem reading the SD card or files on the SD card # Blink 3x while True: print("SD card or filesystem error") for t in range(0, 3, 1): led.value(1) time.sleep(0.1) led.value(0) time.sleep(0.25) time.sleep(3) elif error_type == "NO_FILES": # Can read SD card but it doesn't show any files # Blink 5x while True: print("No files found on SD card") for t in range(0, 5, 1): led.value(1) time.sleep(0.1) led.value(0) time.sleep(0.25) time.sleep(3) elif error_type == "VOL_BAD": # Attempting to set volume outside range # Blink 7x while True: print("Volume outside range") for t in range(0, 7, 1): led.value(1) time.sleep(0.1) led.value(0) time.sleep(0.25) time.sleep(3) elif error_type == "TRACK_BAD": # Attempting to play a track that's outside the number on the SD card # Blink 9x while True: print("Track outside range") for t in range(0, 9, 1): led.value(1) time.sleep(0.1) led.value(0) time.sleep(0.25) time.sleep(3) else: # Shouldn't be here because there's no error that matches. # Blink 15x while True: print("Unknown error") for t in range(0, 15, 1): led.value(1) time.sleep(0.1) led.value(0) time.sleep(0.25) time.sleep(3) def send_cmd(cmd, cmd_opt_MSB, cmd_opt_LSB): # Send a block of data to the DFPlayer Mini. Calculate the checksum as required checksum = 0 - ver - instruction_length - cmd - feedback_request - cmd_opt_MSB - cmd_opt_LSB + (1 << 16) checksumMSB = checksum >> 8 checksumLSB = checksum & 0xff led.value(1) time.sleep(0.5) led.value(0) time.sleep(0.1) data_block = bytes([start_byte, ver, instruction_length, cmd, feedback_request, cmd_opt_MSB, cmd_opt_LSB, checksumMSB, checksumLSB, end_byte]) uart.write(data_block) def query_tracks(): # Queries the device to find how many tracks are on the SD card cmd = 0x48 cmd_opt_MSB = 0x00 cmd_opt_LSB = 0x00 send_cmd(cmd, cmd_opt_MSB, cmd_opt_LSB) reply = uart.read(100) # Wait 0.1s and do it again to compensate for power-on jitter time.sleep(0.1) send_cmd(cmd, cmd_opt_MSB, cmd_opt_LSB) reply = uart.read(100) reply_unpacked = struct.unpack('{}B'.format(len(reply)), reply) print(reply_unpacked) # Check the sanity of the received data and whether the device is returning an error if((reply_unpacked[0] != 0x7e) or (reply_unpacked[9] != 0xef)): # Bad data show_error("DEV_COMM") else: # If we're here then the received data looks OK. Check to see what it says. if(reply_unpacked[3] == 0x40): # If we're here then there's an SD card error show_error("SD_CARD") else: # If we're here then we can read the filesystem on the SD card number_tracks = reply_unpacked[6] if(number_tracks == 0): show_error("NO_FILES") return(number_tracks) def query_volume(): # Queries the device to find the current volume (0-30) cmd = 0x43 cmd_opt_MSB = 0x00 cmd_opt_LSB = 0x00 send_cmd(cmd, cmd_opt_MSB, cmd_opt_LSB) reply = uart.read(100) reply_unpacked = struct.unpack('{}B'.format(len(reply)), reply) # Check the sanity of the received data and whether the device is returning an error if((reply_unpacked[0] != 0x7e) or (reply_unpacked[9] != 0xef)): # Bad data show_error("DEV_COMM") else: print("OK") if(reply_unpacked[3] == 0x40): show_error("SD_CARD") else: print("OK") volume = reply_unpacked[6] print("Volume set to:", volume) def query_status(): # Queries the device to find the current status (0 = not playing, 1 = playing) cmd = 0x42 cmd_opt_MSB = 0x00 cmd_opt_LSB = 0x00 send_cmd(cmd, cmd_opt_MSB, cmd_opt_LSB) reply = uart.read(50) reply_unpacked = struct.unpack('{}B'.format(len(reply)), reply) if((reply_unpacked[0] != 0x7e) or (reply_unpacked[9] != 0xef)): # Bad data show_error("DEV_COMM") else: print("OK") if(reply_unpacked[3] == 0x40): show_error("SD_CARD") else: status = reply_unpacked[6] if(status == 1): print("Playing") return("PLAYING") elif(status == 0): print("Not playing") return("!PLAYING") else: print("Unknown status") def set_volume(volume): # Sets volume (0-30). Returns exception if <0 or >30 cmd = 0x06 cmd_opt_MSB = 0x00 if((volume > 30) or (volume < 0)): show_error("VOL_BAD") cmd_opt_LSB = volume send_cmd(cmd, cmd_opt_MSB, cmd_opt_LSB) def set_track(track): # Selects a particular track. Returns exception if outside the number of # tracks on the SD card cmd = 0x03 cmd_opt_MSB = 0x00 if ((track < 1) or (track > query_tracks())): show_error("TRACK_BAD") cmd_opt_LSB = track send_cmd(cmd, cmd_opt_MSB, cmd_opt_LSB) def sound_play(): # Play the currently selected track cmd = 0x0d cmd_opt_MSB = 0x00 cmd_opt_LSB = 0x00 send_cmd(cmd, cmd_opt_MSB, cmd_opt_LSB) def sound_stop(): # Stop playing (stop, not pause) cmd = 0x16 cmd_opt_MSB = 0x00 cmd_opt_LSB = 0x00 send_cmd(cmd, cmd_opt_MSB, cmd_opt_LSB) def reset_player(): # Software reset the DFPlayer Mini cmd = 0x0c cmd_opt_MSB = 0x00 cmd_opt_LSB = 0x00 send_cmd(cmd, cmd_opt_MSB, cmd_opt_LSB) def startup(): # Need to give DFPlayer Mini some time (documentation varies) after power-on or reset time.sleep(5) # Query the number of tracks. This should always be done at startup because # it will show an error if there are zero tracks, there is no SD card, or # if there is a problem communicating with the DFPlayer Mini board print("Number of tracks:", query_tracks()) time.sleep(0.5) # Set the volume (0-30) set_volume(volume) # Turn off any track that may be playing: sound_stop() def main_loop(): number_of_tracks = query_tracks() while True: if(pbutton.value() == 1): #print("Button press detected") led.value(1) #time.sleep(1) #led.value(0) #time.sleep(3) if (query_status() == "PLAYING"): # If we're here, there's a track playing. Don't do anything. time.sleep(1) else: # If we're here, grab a random song and play it. set_track(random.randint(1, number_of_tracks)) sound_play() #time.sleep(10) #sound_stop() led.value(0) else: #print("No button") led.value(0) time.sleep(1) #time.sleep(10) ##### FUNCTIONS END ##### startup() main_loop()