基于RP2的MP3播发器
MP3在90年代非常流行,准备用RP2做个简易MP3,架构如下所示:
实物搭建照片
数据操作流程框图如下所示:
播放音乐数据流框图:
通过上述三个框图,不用谢文字就可以明白MP3的操作过程
主要代码如下:
-
-
-
-
- import os
- import time
- import ufont
- from ssd1306 import *
-
- from machine import SoftI2C,SoftSPI,Pin,Timer,RTC
- from ssd1306 import SSD1306_I2C
- from sdcard import SDCard
-
- i2c = SoftI2C(sda=Pin(4), scl=Pin(5))
- oled = SSD1306_I2C(128, 64, i2c, addr=0x3c)
-
- KEY1=Pin(14,Pin.IN,Pin.PULL_UP)
- KEY2=Pin(15,Pin.IN,Pin.PULL_UP)
- KEY3=Pin(13,Pin.IN,Pin.PULL_UP)
- KEY4=Pin(12,Pin.IN,Pin.PULL_UP)
-
- spisd = SoftSPI(-1, miso=Pin(11), mosi=Pin(10), sck=Pin(9))
- sd = SDCard(spisd, Pin(8))
-
-
-
- vfs=os.VfsFat(sd)
- os.mount(vfs,'/sd')
-
-
-
- os.chdir('sd')
-
-
-
- sd_list = os.listdir()
- sd_row = 0
- sd_data = []
- for i in sd_list:
- if(i[-3:] == 'wav'):
- sd_data.append(i)
- print(sd_data)
- sd_len = len(sd_data)
- print(sd_len)
-
- class KEY_INST:
-
- def __init__(self,key_pin=[]):
- self.KEY = []
- for i in range(len(key_pin)):
- self.KEY.append(Pin(key_pin[i],Pin.IN,Pin.PULL_UP))
-
- def scan(self):
- if self.KEY[0].value() == 0:
- time.sleep_ms(10)
- if self.KEY[0].value() == 0:
- print("key1 is press : Left")
- while not self.KEY[0].value():
- pass
- print("key1 is unpress : Left")
- return 1
- if self.KEY[1].value() == 0:
- time.sleep_ms(10)
- if self.KEY[1].value() == 0:
- print("key2 is press : Right")
- while not self.KEY[1].value():
- pass
- print("key2 is unpress : Right")
- return 2
- if self.KEY[2].value() == 0:
- time.sleep_ms(10)
- if self.KEY[2].value() == 0:
- print("key3 is press : Up")
- while not self.KEY[2].value():
- pass
- print("key1 is unpress : Up")
- return 3
- if self.KEY[3].value() == 0:
- time.sleep_ms(10)
- if self.KEY[3].value() == 0:
- print("key4 is press : Down")
- while not self.KEY[3].value():
- pass
- print("key4 is unpress : Down")
- return 4
- return 0
-
- list_of_menus = [20,30,40,50,60,70,70,70,70,70,70,70,70,70,70,70,70,70,70,70]
- list_of_word =["1.AllLight","2.Cyclle","3.Flicker","4.AloneLight","5.Arbitrarily","6.EXT","7.XXX"]
-
- class MENU_SHOW():
-
- def __init__(self,handle,name,word):
- self.menu_name = name
- self.oled = handle
- self.menus = word
- self.len = len(word)
-
- def txt(self):
- self.oled.text(self.menu_name,50,4)
- self.oled.line(0,15,128,15,1)
- self.oled.line(0,63,128,63,1)
- self.oled.line(0,16,128,16,1)
- self.oled.rect(115,20,7,7,1)
- self.oled.rect(115,30,7,7,1)
- self.oled.rect(115,40,7,7,1)
- self.oled.rect(115,50,7,7,1)
- self.oled.rect(0,0,128,64,1)
-
- def word_fixed(self,i):
- if self.len <= 5:
- for i in range(self.len):
- self.oled.text(self.menus[i], 4, list_of_menus[i])
- else:
- if i<=4:
- for j in range(5):
- self.oled.text(self.menus[j], 4, list_of_menus[j])
- else:
- for j in range(self.len):
- self.oled.text(self.menus[j], 4, list_of_menus[j + 4 -i])
- self.oled.show()
-
- def word_allo(self,word,j):
- self.oled.fill(0)
- self.txt()
- for i in range(len(word)):
- self.oled.text(word[i], 4, list_of_menus[i])
- self.choose_word(j)
-
-
- def choose_word(self,j):
- if j<=4:
- self.oled.fill_rect(115,list_of_menus[j-1],7,7,1)
- self.oled.rect(2,list_of_menus[j-1]-2,124,12,1)
- else:
- self.oled.fill_rect(115,list_of_menus[3],7,7,1)
- self.oled.rect(2,list_of_menus[3]-2,124,12,1)
- self.oled.show()
-
- def up_down(self,a):
- self.oled.fill(0)
- self.txt()
- self.word_fixed(a)
- self.choose_word(a)
-
- def ext_show(self,a):
- self.word(a)
- self.choose_word(a)
-
- from machine import Pin, SoftSPI
- import os
- from machine import I2S
-
- font = ufont.BMFont("/sd/unifont-14-12917-16.v3.bmf")
-
-
- SCK_PIN = 16
- WS_PIN = 17
- SD_PIN = 18
- I2S_ID = 0
- BUFFER_LENGTH_IN_BYTES = 40000
-
-
-
- WAV_SAMPLE_SIZE_IN_BITS = 16
- FORMAT = I2S.STEREO
- SAMPLE_RATE_IN_HZ = 16000
-
-
- class MP3_INST(KEY_INST):
- def __init__(self,lcd,key=[],sd_data=[]):
- self.audio_out = I2S(
- I2S_ID,
- sck=Pin(SCK_PIN),
- ws=Pin(WS_PIN),
- sd=Pin(SD_PIN),
- mode=I2S.TX,
- bits=WAV_SAMPLE_SIZE_IN_BITS,
- format=FORMAT,
- rate=SAMPLE_RATE_IN_HZ,
- ibuf=BUFFER_LENGTH_IN_BYTES,
- )
- self.display = lcd
- self.switch = False
- self.playt = False
- self.data = sd_data
- self.num = 0
- super().__init__(key)
-
- def cal_time(self,t):
- t = t.split(':')
- min = int(t[0])
- sec = float(t[1])
-
- return min * 60 + sec
-
- def unpack_lrc(self,name):
- print(name)
- file = open("/sd/{}.lrc".format(name), "r", encoding="utf-8")
- lrc_list = file.readlines()
- file.close()
- author = lrc_list[0][4:-3] + '-' + lrc_list[1][4:-3]
- lrc_time = []
- lrc_word = []
- lrc_dict = {}
- for i in lrc_list[4:]:
- lrc_sub = i.replace("[", "]").strip().split("]")
- for j in range(len(lrc_sub) - 1):
- if lrc_sub[j] and lrc_sub[j].startswith('0'):
- lrc_dict[lrc_sub[j]] = lrc_sub[-1]
- for key in sorted(lrc_dict.keys()):
- lrc_time.append(self.cal_time(key))
- lrc_word.append(lrc_dict[key])
- return author, lrc_time, lrc_word
-
- def play(self,i):
- self.num = i
- wav = open("/sd/{}".format(self.data[self.num]), "rb")
- _ = wav.seek(44)
-
- wav_samples_mv = memoryview(bytearray(11025))
- print("========== START PLAYBACK ==========")
- self.playt = True
- self.switch = True
- lrc_num = 0
- try:
- while True:
- key_state = self.scan()
- if key_state == 1:
- self.num = self.num - 1
- if self.num <= 0:
- self.num = 1
- self.switch = True
- wav.close()
- elif key_state == 2:
- self.num = self.num + 1
- if self.num >= sd_len:
- self.num = 1
- self.switch = True
- wav.close()
- elif key_state == 3:
- self.playt = not self.playt
- elif key_state == 4:
- return True
-
- if self.playt:
- if self.switch:
- self.switch = False
- print(self.data[self.num])
- wav = open("/sd/{}".format(self.data[self.num]), "rb")
-
- _ = wav.seek(44)
- author, lrc_time, lrc_word = self.unpack_lrc(self.data[self.num][:-4])
- print(author)
- font.text(self.display, author, 0, 8, show=True, clear=True, auto_wrap=False)
- now = time.time()
- lrc_num = 0
-
- if time.time() - now >= lrc_time[lrc_num]:
- lrc_num = lrc_num + 1
- if lrc_num >= len(lrc_time):
- lrc_num = len(lrc_time) - 1
- font.text(self.display, author, 0, 8, show=True, clear=True, auto_wrap=False)
- font.text(self.display, lrc_word[lrc_num], 0, 24, show=True, clear=False, auto_wrap=True)
-
- num_read = wav.readinto(wav_samples_mv)
- if num_read == 0:
- self.num = self.num + 1
- if self.num > sd_len-1:
- self.num = 0
- wav = open("/sd/{}".format(self.data[self.num]), "rb")
- _ = wav.seek(44)
-
- author, lrc_time, lrc_word = self.unpack_lrc(self.data[self.num])
- font.text(self.display, author, 0, 8, show=True, clear=True, auto_wrap=False)
- else:
- _ = self.audio_out.write(wav_samples_mv[:num_read])
- except (KeyboardInterrupt, Exception) as e:
- print("caught exception {} {}".format(type(e).__name__, e))
-
- wav.close()
-
- def donghua():
- for i in range(2,269):
- file = open("/sd/text_img_{}.dat".format(i), "rb")
- buffer = file.read(128*8)
- file.close()
- fb_buffer = framebuf.FrameBuffer(bytearray(buffer), 128, 64, framebuf.MONO_HMSB)
-
- oled.blit(fb_buffer, 0, 0)
- oled.show()
-
- if __name__ == "__main__":
- menu_show = MENU_SHOW(oled,"SD",sd_data[0:4])
- key_press = KEY_INST([14,15,13,12])
- menu_num = 1
- donghua()
- menu_show.word_allo(sd_data[0:4],1)
- while True:
- key_state = key_press.scan()
- if key_state == 1:
- menu_num = menu_num - 1
- if menu_num <= 0:
- menu_num = 1
- if menu_num > 4:
- menu_show.word_allo(sd_data[menu_num-4:menu_num],menu_num)
- else:
- menu_show.word_allo(sd_data[0:4],menu_num)
- elif key_state == 2:
- menu_num = menu_num + 1
- if menu_num > sd_len:
- menu_num = 1
- if menu_num > 4:
- menu_show.word_allo(sd_data[menu_num-4:menu_num],menu_num)
- else:
- menu_show.word_allo(sd_data[0:4],menu_num)
- elif key_state == 3:
- pass
-
- print(sd_data[menu_num - 1])
- audio_wav = MP3_INST(oled,[14,15,13,12],sd_data)
- audio_wav.play(menu_num - 1)
-
- elif key_state == 4:
-
- menu_show.word_allo(sd_data[0:4],1)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
MP3功能演示操作:链接如下所示:
https://training.eeworld.com.cn/uploadcourse/68027/lesson
播放器加载失败: 未检测到Flash Player,请到 安装
MP3
源码如下:
项目总结:
非常感谢得捷电子与EEWORLD提供设备与平台,本次开发非常愉快,开发MP3功能的过程中,熟悉了音频的处理相关知识与工具,提升了自身知识
另外项目缺陷是EP9023的晶振是8M,因此只能支持22.05k以下、24bit的音频文件,另外就是打开双核处理的时候,即写音频文件与刷新OLED同时工作,RP2就奔溃了,但是如何简单的led、oled刷新开双核,RP2可以正常运行,这一点没有解决,所以项目任然采用的是单核运行,这就导致播发音乐与刷新OLED的歌词不连贯,这点可惜了,等后续用c实现试试。
欢迎大家交流。
其他:
任务1:熟悉micropython的基本语法
最简单的莫过于电灯了
- from machine import Pin
- import time
-
- led = Pin(25,Pin.OUT)
- led.value(1)
-
- def led_work():
- led.value(1)
- time.sleep(1)
- led.value(0)
- time.sleep(1)
-
- while True:
-
- time.sleep(1)
- print("led work...")
-
任务2:驱动外设
驱动蜂鸣器,源码如下:
- from mic import *
- from machine import Pin,PWM
- import time
-
- buzzer = 16
- scale=[G,A,EE,A,G,A,G,A,EE,A,G,
- A,EE,A,G,A,E,
- G,D,E,G,A,B,
- A,EE,A,G,A,G,
- A,EE,B,CC,B,CC,B,A,E,
- D,E,G,A,B,A,EE,A,G,A,
- G,A,EE,A,G,A,EE,A,G,A,
- E,G,D,E,G,A,B,A,EE,A,G,A,
- G,A,EE,B,CC,B,CC,DD,EE,AA]
-
- duration=[2,1,1,1,1,7,1,1,1,1,1,
- 1,1,1,1,3,1,
- 3,1,1,1,1,1,
- 1,1,1,1,7,1,
- 1,1,1,1,1,1,1,1,6,
- 1,1,1,1,1,1,1,1,1,7,
- 1,1,1,1,1,1,1,1,1,3,
- 1,3,1,1,1,1,1,1,1,1,1,7,
- 1,1,1,1,1,1,1,1,1,4]
-
- scale_len=len(scale)
- buzzer_phy = Pin(buzzer,Pin.OUT)
- pwm_phy = PWM(buzzer_phy)
- interval = 100
-
- while True:
- for i in range(len(scale)):
- pwm_phy.freq(scale[i])
- pwm_phy.duty_u16(duration[i]*250)
- time.sleep_ms(400)
- pwm_phy.duty_u16(0)
- time.sleep_ms(interval)
- time.sleep(2)
驱动OLED,利用OLED 实现简易示波器
- from ssd1306 import SSD1306_I2C
- from machine import Pin,I2C
- import math
-
- i2c = I2C(1)
- print(i2c)
- print(i2c.scan())
- display = SSD1306_I2C(128,64,i2c)
- display.fill(0)
- display.text("abcdef",0,0)
- display.text("1234",0,8)
- display.text("123456789",0,16)
- display.text("123456789",0,24)
- display.text("abcdef",10,40)
- display.text("1234",20,54)
-
- display.show()
- osci_hight = 50
- osci_width = 100
- freq = 5
- amp = 25
- freq_div = 1
- amp_div = 1
- display.show()
- display.fill(0)
- display.hline(0,31,128,1)
- display.vline(64,0,64,1)
- y = []
- a_last = 0
- for i in range(osci_width):
- a = 25+amp*math.sin(2*math.pi*freq*i/osci_width)
- y.append(int(a))
-
- if i == 0:
- display.line(i,int(a),i,int(a),1)
- else:
- display.line(i-1,int(a_last),i,int(a),1)
- a_last = a
- display.text("5v/div",8,56)
- display.show()
-
显示如下所示:
任务3:同步网络时间
-
- import network
- import urequests
- import ujson
- import utime
- import time
- import ssd1306
- import machine,gc
- from machine import RTC, I2C, Pin
- from ssd1306 import SSD1306_I2C
-
- ssid = "HAHA"
- pw = "4r8,52L8"
-
-
- print("Connecting to wifi...")
-
- i2c = I2C(1)
- oled = SSD1306_I2C(128, 64, i2c, addr=0x3c)
- oled.fill(0)
- oled.text("Connecting", 0, 5)
- oled.text(" to wifi...", 0, 16)
- oled.show()
- time.sleep(1)
-
- wifi = network.WLAN(network.STA_IF)
- wifi.active(True)
- if wifi.isconnected():
- print("wifi connected")
- else:
- wifi.connect(ssid, pw)
-
- i = 0
- while not wifi.isconnected():
- time.sleep_ms(1000)
- oled.text("wait {}s".format(int(i)), 0, 24)
- i = i + 1
- if i % 20 == 0:
- wifi.connect(ssid, pw)
- if i == 60:
- print("wifi connect failed..")
- oled.text("wifi connect failed..",0,32)
- oled.show()
-
-
-
- print("IP: " + str(wifi.ifconfig()[0]) + "\n")
- oled.text("Cd. IP: ", 0, 35)
- oled.text(" " + str(wifi.ifconfig()[0]), 0, 45)
- oled.show()
- time.sleep(1)
- url = 'https://api.seniverse.com/v3/weather/daily.json?key=Sm2zhT9bLvDz_Hfzb&location=wuhan&language=en&unit=c&start=0&days=5'
- oled.fill(0)
- oled.show()
-
-
-
- def weather_():
- result = urequests.get(url)
- if not wifi.isconnected():
- machine.reset()
- j = ujson.loads(result.text)
- for i in range(3):
- oled.fill(0)
- city=j['results'][0]['location']['name']
- oled.text(city+" weather", 0, 0)
-
- date=j['results'][0]['daily'][i]['date']
- oled.text("Date "+date, 0, 9)
-
- tianqi='D/N:'+j['results'][0]['daily'][i]['text_day']+'/'+j['results'][0]['daily'][i]['text_night']
- oled.text(tianqi, 0, 20)
-
- high=j['results'][0]['daily'][i]['high']+' deg'
- low=j['results'][0]['daily'][i]['low']+' deg'
-
- oled.text('Low:'+low, 0, 29)
- oled.text('High:'+high, 0, 37)
-
- shidu=j['results'][0]['daily'][i]["humidity"]
- oled.text('Humidity:'+shidu+'%', 0, 50)
- oled.show()
- utime.sleep(3)
- oled.show()
-
- def weather_icon():
- result = urequests.get(url)
- if not wifi.isconnected():
- machine.reset()
- j = ujson.loads(result.text)
- weatherday=j['results'][0]['daily'][0]['text_day']
- return weatherday
-
- web_query_delay = 60000
- retry_delay = 5000
- url2 = 'http://worldtimeapi.org/api/timezone/Asia/Hong_Kong'
- rtc = RTC()
- def webtime_():
-
- response = urequests.get(url2)
-
- if response.status_code == 200:
-
- parsed = ujson.loads(response.text)
-
- datetime_str = str(parsed["datetime"])
- year = int(datetime_str[0:4])
- month = int(datetime_str[5:7])
- day = int(datetime_str[8:10])
- hour = int(datetime_str[11:13])
- minute = int(datetime_str[14:16])
- second = int(datetime_str[17:19])
- subsecond = int(round(int(datetime_str[20:26]) / 10000))
-
-
- rtc.datetime((year, month, day, 0, hour, minute, second, subsecond))
- update_time = utime.ticks_ms()
- print("wifi of RTC updated success\n")
-
- else:
- update_time = utime.ticks_ms() - web_query_delay + retry_delay
-
- rtc = RTC()
- oled.text('wifiLinkTo RTC', 0, 4)
- oled.show()
- webtime_()
- oled.fill(0)
- oled.text('updated', 35, 20)
- oled.text('success', 35, 30)
- oled.show()
- time.sleep(1)
-
- weather_()
- time.sleep(10)
- webtime_()
-
-
-
-
任务4:实现定位功能
- import time
- from machine import Pin, UART
- from micropyGPS import MicropyGPS
-
- if __name__ == '__main__':
-
- uart_gps = UART(1, baudrate=9600, tx=Pin(4), rx=Pin(5))
- gps = MicropyGPS()
-
- while True:
- if uart_gps.any() > 0:
- status = gps.update(uart_gps.read(1).decode("ascii"))
- if status:
- print("latitude:", gps.latitude_string())
- print("Longitude:", gps.longitude_string())
- print("Speed:",gps.speed_string("kph"),"or",gps.speed_string("mph"),"or", gps.speed_string("knot"),)
- print("Date (Long Format):", gps.date_string("long"))
- print("Date (Short D/M/Y Format):", gps.date_string("s_dmy"))
- print("timestamp (Short [H,M,S] Format):", gps.timestamp)
|