基于RP2的MP3播发器
MP3在90年代非常流行,准备用RP2做个简易MP3,架构如下所示:
实物搭建照片
数据操作流程框图如下所示:
播放音乐数据流框图:
通过上述三个框图,不用谢文字就可以明白MP3的操作过程
主要代码如下:
# import math
# import machine,gc
import os
import time
import ufont
from ssd1306 import *
from machine import SoftI2C,SoftSPI,Pin,Timer,RTC #从machine模块导入I2C、Pin子模块
from ssd1306 import SSD1306_I2C#从ssd1306模块中导入SSD1306_I2C子模块
from sdcard import SDCard
i2c = SoftI2C(sda=Pin(4), scl=Pin(5)) #I2C初始化:sda--22, scl -->23
oled = SSD1306_I2C(128, 64, i2c, addr=0x3c) #OLED显示屏初始化:128*64分辨率,OLED的I2C地址是0x3c
KEY1=Pin(14,Pin.IN,Pin.PULL_UP) #L构建KEY对象
KEY2=Pin(15,Pin.IN,Pin.PULL_UP) #R构建KEY对象
KEY3=Pin(13,Pin.IN,Pin.PULL_UP) #UP构建KEY对象
KEY4=Pin(12,Pin.IN,Pin.PULL_UP) #UP构建KEY对象
spisd = SoftSPI(-1, miso=Pin(11), mosi=Pin(10), sck=Pin(9))
sd = SDCard(spisd, Pin(8))
# print('未挂载SD之前:{}'.format(os.listdir()))
vfs=os.VfsFat(sd)
os.mount(vfs,'/sd')
# print('挂载SD开之后:{}'.format(os.listdir()))
os.chdir('sd')
# print('SD卡中的文件:{}'.format(os.listdir()))
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) #写入第1行内容
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)
# self.oled.show()
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")
# ======= I2S CONFIGURATION =======
SCK_PIN = 16
WS_PIN = 17
SD_PIN = 18
I2S_ID = 0
BUFFER_LENGTH_IN_BYTES = 40000
# ======= I2S CONFIGURATION =======
# ======= AUDIO CONFIGURATION =======
WAV_SAMPLE_SIZE_IN_BITS = 16
FORMAT = I2S.STEREO
SAMPLE_RATE_IN_HZ = 16000#16000
# ======= AUDIO CONFIGURATION =======
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])
# print("{}:{}".format(min, sec))
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(10000))
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 = open("/sd/linjj.wav"), "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)
# print(self.data[self.num])
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))
# cleanup
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)
# menu_show.choose_word(1)
elif key_state == 4:
#返回上一级目录
menu_show.word_allo(sd_data[0:4],1)
MP3功能演示操作:链接如下所示:
https://training.eeworld.com.cn/uploadcourse/68027/lesson
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:
# led.toggle()
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))
# display.pixel(i,int(a),1)
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" # wifi路由器名称
pw = "4r8,52L8" # wifi路由器密码
print("Connecting to wifi...")
# i2c = SoftI2C(sda=Pin(4), scl=Pin(5)) #I2C初始化:sda--22, scl -->23
i2c = I2C(1)
oled = SSD1306_I2C(128, 64, i2c, addr=0x3c) #OLED显示屏初始化:128*64分辨率,OLED的I2C地址是0x3c
oled.fill(0)
oled.text("Connecting", 0, 5)
oled.text(" to wifi...", 0, 16)
oled.show()
time.sleep(1)
# wifi connection#链接WiFi
wifi = network.WLAN(network.STA_IF) # station mode
wifi.active(True)
if wifi.isconnected():
print("wifi connected")
else:
wifi.connect(ssid, pw)
# wait for connection#等待WiFi链接上
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()
# wifi connected#WiFi链接上以后显示的内容
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)#根据网址更换密钥和城市(key=)(location=)
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 # interval time of web JSON query
retry_delay = 5000 # interval time of retry after a failed Web query
url2 = 'http://worldtimeapi.org/api/timezone/Asia/Hong_Kong'
rtc = RTC()
def webtime_():
response = urequests.get(url2)
if response.status_code == 200: # query success
# parse JSON
parsed = ujson.loads(response.text)
# you can also use parsed = response.json()
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))
# update internal RTC
rtc.datetime((year, month, day, 0, hour, minute, second, subsecond))
update_time = utime.ticks_ms()
print("wifi of RTC updated success\n")
else: # query failed, retry retry_delay ms later
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)) # uart1
gps = MicropyGPS() #GPS
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)
|