如何基于ESP32和MicroPython构建一个RFID控制的MP3播放器
扫描二维码
随时随地手机看文章
有一段时间了,我有一个圣诞节抽奖与射频识别标签为ESP32编程。贡献用语音输出的测量结果回来。我用手机记录下了这些零碎的语言,并将其传输到SD卡上。现在你当然也可以把音乐保存在SD卡上,并通过射频识别标签调用——一个具有扩展潜力的MP3播放器已经准备好了。我尝试了各种编程方法。最初看起来很有希望和有趣的线程方法,最终不得不放弃,转而支持传统编程,因为性能不符合我的想法。我现在用这个系列的新文章向你介绍这个解决方案
ESP32和ESP8266上的Micropython
今天:
带RFID的MP3播放器
严格来说,我是通过关于ESP32/ESP8266 dem Raspberry Pi Pico和那台PC上的串行接口的文章产生了这篇文章的想法。我想展示RS232端口上的其他一些应用程序。这里使用的DFPlayer Mini是通过串行接口控制的。由于使用了双向数据交换,因此只需一个ESP32或Raspberry Pi Pico作为控制器。PC也可以通过RS232 TTL转换器直接与DFPlayer Mini进行对话,但我将其省略。
两个迷你断路器上的组件通过许多跳线电缆查看鸟类游戏。毕竟,使用了四个接口,SPI (RFID读取器),I2C (OLED显示器),RS232 (DFPlayer Mini)和ADC (Poti)。
图1:MP3播放器的结构与SH1106
有几件事需要考虑。DFPlayer Mini必须提供5V。因此,具有5V电平的脉冲也在他的PIN TX上,因为是忙碌引脚。由于ESP32只能承受3.3V到其gpio,电平必须通过电压分压器降低。我选择了1kΩ和2.2kΩ。您还可以采用其他比例为1:2的值。相反,RX输入端的DFPlayer可以很好地处理ESP32的3.3V信号。
如果扬声器用黑线放在GND上,则5V圈的电流约为。450马!这是因为连接SP1和SP2引线为5V电平。因此,只将两个扬声器连接到SP1和SP2,不要连接到GND。即使您只使用一个扬声器,它的线路也属于SP1和SP2(图2中黄色部分)。否则你必须通过ELKO连接扬声器。
图2:带有4gb SD卡的DFPlayer Mini
ESP32上的ADC输入可以处理高达2.4V的张力。然而,电位器是3.3V。因此,我通过计算2.2kΩ的电阻来降低失调电压。因此,电位器可以放在面包板上,我提供了一个5针笔带的连接。
图3:面包板蒙太奇的波蒂与笔条
你必须特别小心上面提到的显示。SDA和SCK的连接是一致的,但是+VCC和GND的引脚与96“模块(上面的笔条)在1.3”模块(下面的笔条)中相互交换。我在图4的电路中使用了一个1.3“模块。
图4:运行中的SH1106
图5:MP3播放器-电路
为了自主操作,我为16850的锂离子电池提供了一个电池所有者。它可以通过封闭的电缆直接连接到ESP32,然后接管其他模块的供应。
图6:电池所有者18650
连接图可能有助于将电路放在一起。
图7:连接计划
一架ESP32接管了主要位置。为了使用它,需要两个面包板,它们连接到中间的电源导轨上,这样你就可以用引脚排的间隔到达那里,也可以在边缘插入电缆。
ESP32的小兄弟,ESP8266,不适合我们的目的,因为它不能提供第二个,完整的接口,我们需要它来控制DFPlayer。
Uart是通用异步接收器/发射器的缩写。我们还从tony的终端与控制器讨论了这样一个接口。ESP32 /ESP8266通过USB线缆与PC相连,主控板上的USB- rs232 - ttl转换器将数据流量传输到控制器的UART0接口。这反过来又提供给数据流sys.stdin。输入命令从这个文件对象提供。sys。stdout通过UART0将输出数据发送到tony的终端。因此,我们不能使用UART0向mini-MP3播放器发送命令并从中接收数据。一个UART接口只能有一个对应物。ESP8266有第二个UART接口,但实际上只有一半,因为只有一条TXD线(传输线),没有RXD线(接收管理)可用。但也会有一个树莓派Pico (W),它有两个免费提供的组件。程序是一样的,只是pin分配不同。
Micropython -语言-模块和程序
要安装托尼,你会在这里找到一个详细的说明(英文版)。还有一个关于ESP芯片上的Micropython固件(截至2024年1月25日)如何被烧毁的描述。
Micropython是一种解释器语言。Arduino IDE与Arduino IDE的主要区别在于,你只需要在ESP32上闪烁Micropython固件一次,以便控制器理解Micropython指令。您可以使用Thonny,µpycraft或ESPTOOL.PY。对于安东尼,我在这里描述了这个过程。
一旦固件闪过,你就可以很容易地在对话框中与控制器对话,测试单个命令并立即看到答案,而无需事先编译和传输整个程序。这正是Arduino IDE困扰我的地方。如果您可以检查语法和硬件的简单测试,以便在编写程序之前通过命令行尝试和改进函数和整个程序部分,则可以节省大量时间。出于这个目的,我总是喜欢创建小型测试程序。作为一种宏,它们总结了重复出现的命令。然后从这些程序片段开发整个应用程序。
自动启动
如果程序要通过打开控制器自动启动,请将程序文本复制到新创建的空白磁贴中。将此文件保存在工作区的boot.py下,并将其上传到ESP芯片。该程序将在下次复位或开机时自动启动。
测试程序
tony - ide中当前编辑器窗口中的程序通过F5按钮手动启动。这可以比鼠标点击开始按钮或通过菜单运行更快地完成。只有程序中使用的模块必须在ESP32的flash中。
在两者之间,Arduino id ?
如果您以后将控制器与Arduino IDE一起使用,只需以通常的方式刷新程序。然而,ESP32/ESP8266随后忘记了它曾经说过Micropython。相反,任何包含Arduino IDE或AT-Firmware或Lua或…可以很容易地提供micropython固件。这个过程总是像这里描述的那样。
这个项目
概述
播放列表的选择是在这个项目中通过RFID进行的,播放列表的选择是在这个项目中通过数据进行的。在这个项目中,通过读取地图上的ID来选择播放列表。然而,这显然超出了本文的范围,可能是另一个博客系列的主题。根据ID(8位十六进制数字),ESP32可以指示DFPlayer Mini播放SD卡文件夹中的MP3文件。订单名称和编号与标题编号一起显示在显示器上。用户说明也显示在显示屏中。
RFID阅读器
由于SPI总线,布线也比I2C总线更复杂,只有2条线。SPI总线设备没有硬件设备地址,但是它们有一个芯片选择连接(CS),如果要对设备进行寻址,它必须处于低电平。数据传输的工作方式也有一点不同,它总是同时发送和接收。这里没有必要说明更接近的过程,因为类Mfrc522为我们做了这些。我们只会通知建造商后续任务和传输速度。传输工作与3.2兆赫的舰队。相比之下,I2C工作频率高达400kHz。
我们自己的函数readuid()读取卡片的清晰标识,并将其作为十进制数和十六进制拨号返回。这些卡片是通过OLED显示器请求的。因此,该函数不会阻塞整个进程,超时确保有序退出。在这种情况下,返回值为None,而不是卡ID。
为了让播放列表卡发挥作用,我们需要一张主卡。为此,我们从堆栈中取出任何卡片或芯片,读取ID,从而在程序开始时用十进制值证明变量,如下所示:
Masteride = 4217116188。
在第一次启动时,ESP32注意到仍然没有包含播放列表卡数据的文件,需要主卡。识别后,将请求播放列表卡。读取ID后,将其写入文件,并再次请求主卡。继续阅读直到最后一张播放列表卡。如果在请求主卡后10秒内没有提供播放列表卡,则检测过程结束并开始主程序。为了完全从前面开始,我们可以通过thony - console删除播放列表卡id。当然,也可以在tony编辑器窗口中编辑该文件。
Dfplayer迷你
与前锋的沟通总是以同样的方式进行。发送和接收10字节的块。这种积木的结构是这样的。类Dfplayer的方法broadcommand()负责正确的传输。
开始-字节0x7e
版本号0xff
有效负载长度0x06(从版本号到参数2包括1)
命令
反馈0x00(否)或0x01(是)
参数高
参数低
检查高
检查低
EndeByte 0 xef
命令代码可以在数据表中找到。我用它来实现模块dplayer .py方法中最重要的代码。我们目前的项目只需要其中的几个。但是看看文件pfplayer.py(2024年1月24日的Rev 1.3)
声音文件的处理是用免费工具Audacity,也很容易将声音文件格式重新编码为MP3。更大的努力意味着改变文件名,使其符合DFPlayer Mini的要求。这些文件位于以两位数字命名的文件夹中。这些是播放列表。
图8:SD卡的文件夹视图
文件名可以保留其旧名称,但必须提供一个由三位十进制数组成的前缀。
图9:带有前缀的文件名
手动重命名可能仍然可以处理几个文件,如果超过十个,就会变得非常烦人。这就是为什么我使用了另一个免费工具Advanced Renamer。安装文件邀请您从芯片服务器下来。
图10:下载高级重命名器
安装时,只需跟随助手即可。收集为SD卡上的文件夹选择的文件的最佳方法是在工作目录或SD卡上。标记文件夹中的所有文件,并将包拉到重命名窗口。作为Batch方法,选择Add,然后将其放入过滤器窗口1:添加属性,如图11所示。
图11:高级重命名器中的过滤器设置
此窗口中的每个更改都会立即对预览产生影响。要接管,请单击右上方的“批量启动”。就是这样。
图12:源名称和结果
MP3播放器
在所有的准备工作之后,是时候对项目进行编码了。我们开始吧。一如既往,节目从进口业务开始。如果您在编辑器窗口中打开mp3player.py将是最好的,然后您可以在那里阅读概述,同时我们将介绍这里的工作方式。
# mp3player.py
#适用于RC522 @ 13,56 mhz
从dfplayer导入dfplayer
进口mfrc522
引脚,SoftI2C, SPI, ADC,复位
#从oled导入oled # vorssight: GND Vcc SCK SDA
从oled_SH1106导入OLED # vorssight: Vdd GND SCK SDA
从时间导入睡眠
从sys导入退出
从超时导入*
我们从DFPlayer .py中导入DFPlayer类,而从mfrc522.py中导入所有内容。这两个文件必须被放入ESP32的闪存中。两个标记都在电影管理器中,右键单击,上传到/。
模块机提供接口操作的类。根据所使用的显示模块,必须激活或注释以下两行中的一行。
对于短暂的被动休息,我们从时间模块获得睡眠()。我们使用exit() out of sys函数确保一个干净的程序退出。从模块超时,我们得到所有(*)在我们的范围,这是需要的非阻塞软件定时器。当从该模块调用方法时,该船尾并不适用于该模块。
没有星号的,例如:
超时。timeoutms (1000)
与明星:
Timeoutms (1000)
接下来,在相应的包中实例化对象。我们从ADC对象开始,我们需要从电位器读取张力。我们将控制DFPlayer Mini的音量。
卷= ADC(销(36)
vol.atten (vol.ATTN_11DB)
vol.width (vol.WIDTH_10BIT)
强力器的研磨触点连接GPIO36。如果弱变为11 dB,则达到高达2.4V的扫描。我们将分辨率设置为10位。
RX_Pin = 16
TX_Pin = 17
busyPin = 27
df = DFPlayer (busyPinNbr = busyPin,
txd = TX_Pin,
rxd = RX_Pin,
体积= 15)
Sleep(2) #等待玩家初始化
numOfTitles = df.getNumberOfFiles ()
print(" title gesamt:", numOfTitles)
DFPlayer类设置ESP32本身的UART1。构造器只需要将busy-Pin、RX-Pin和tx-Pin上的gpio编号连接即可。缺省情况下,Pin已连接。通过询问SD卡上的文件总数。
spi = spi(2,波特率= 3200000)
# cs = sda
RDR = mfrc522。MFRC522(spi, cs=5,波特率=3200000)
Spi2硬件接口占用GPIOS 18 (SCK), 23 (Mosi)和19 (Miso),还有5 (CS)。RFID板上的选片连接以SDA命名。我们将波特率定义为3200000Hz。
i2c = SoftI2C (sci =销(22),sda =销(21))
d = OLED (i2c)
对于显示,我们实例化一个I2C对象,并在此过程中给出OLED对象的构造函数。
主卡是向系统宣布播放列表标签的RFID日。我们在程序完成后立即确定数值,然后在此时输入它。
MasterID=4217116188 # 0XFB5C161C
列表列表包含播放列表的名称。
listen=[
"COUNTRY",
"BLACKMOORSNIGHT",
"YOUTUBE",
"SCHLAGER",
"HITS",
"VON CD"
"OLDIES"
]
下面是一些函数的声明。Readuid()读取标签的标识号。我们交出OLED显示对象,一个用于卡片类型的字符串和一段以毫秒为单位的时间,在此结束后,该功能将在任何情况下离开。变量song是全局声明的,这样在函数中分配的值在主程序中可用。我不会在Display和ReplaB中讨论所有版本,它们部分用于告知用户和开发过程中的故障排除,并通过自制文本进行解释。
非阻塞软件计时器的获取是通过Timeoutms()方法设置的。在Timeoutms()中,函数是隐藏的compare(),一个所谓的Closure.Timeoutms()返回一个引用compare(),我们将标识符分配给它。与sleep()不同的是,其他指令可以在计时器期间执行。因此,While循环一直运行到函数reap(), alias compare(), True返回为止。这个过程的背景可以在装饰器上的闭包文档中找到。
ef readUID(display, kartentyp, timeout):
global song
display.clearFT(0, 1, 15, 2, False)
display.writeAt("PUT ON", 0, 1, False)
display.writeAt(kartentyp, 0, 2)
readTimeOut=TimeOutMs(timeout)
while not readTimeOut():
(stat, tag_type) = rdr.request(rdr.REQIDL)
if stat == rdr.OK:
(stat, raw_uid) = rdr.anticoll()
if stat == rdr.OK:
display.clearFT(0, 3, 15, 3)
display.writeAt("Card OK", 0, 3)
userID=0
for I in range(4):
userID=(userID<<8) | raw_uid[i]
userIDS="{:#X}".format(userID)
display.writeAt(userIDS+" ", 0, 4)
sleep(2)
df.reset()
song=0
return userID, userIDS
return None
使用RDR.MEQUEST(),让我们放入一个输入进程。如果请求成功,我们将在下一步中获取ID。如果这一步也成功了,让我们开始评估。从MFRC522中,我们得到一个字节- plet,我们分四个步骤将其转换为32位数字。useride中前面的结果被推到左边,8被推到左边,插曲被装饰。然后我们将整个结果转换为十六进制字符串,通常是8位数字。
有两秒钟的时间读取显示,然后我们将DFPlayer Mini放回去,并将歌曲计数器设置为0,因为还应该通过读取ID启动一个新的播放列表。如果读取尝试失败或时间刚刚过期,则返回None,而不是数字和字符串形式的ID。
如果您已经进入程序,此时,您已经可以开始读取主卡的ID。或者,您可以mp3player.py Download并在第73行输入以下说明。然后保存并在编辑器窗口中启动程序。
退出()
根据已释放的Repl-Prompt,输入如下说明。现在把你的万事达卡交给读者。上述输出数值为Masteride a。
Readuid (D,“Test”,6000)
0 xfb5c161c
(4217116188, ' 0 xfb5c161c ')
这个主卡使用下一个函数addid(),该函数负责注册播放列表卡。如果ESP32的文件系统中还没有名为slavecards.txt的文件,主程序将在第一次启动时调用它。但是也可以手工调用addid()来创建文件和/或注册其他播放列表标签。
首先我们要主卡并读取ID。如果操作成功,则M从十进制值和十六进制字符串中接收Tupel,否则为None。我们通过解压到主ID上的数字、字符串和偏移量来粉碎这个图。当阅读指令完成后,还有三秒钟的时间放入新的播放列表卡。
def addUID(display):
display.clearAll()
m=readUID(display, "Master", 3000)
print("Master", m)
if m is not None:
mid, mids= m
if mid==MasterID:
print("Master OK")
u=readUID(display, "Slavecard", 3000)
if u is not None:
uid, uids=u
if uid is not None and uid != MasterID:
with open("slavecards.txt", "a") as f:
f.write("{}\n".format(uids))
display.writeAt("New slave written", 0, 4)
display.writeAt("{}".format(uids), 0, 5)
sleep(5)
return True
Else:
display.writeAt("ERROR!!!", 0, 3)
display.writeAt("Card not added!", 0, 4)
return False
Else:
display.writeAt("ERROR!!!", 0, 3)
display.writeAt("Not mastercard", 0, 4)
sleep(3)
return False
和之前一样,MSo现在包含U,读取过程成功时的ID值。如果uid不是None并且该卡不是master卡,则该卡被注册。使用wither-A文件对象创建,并在标识符F下打开附加文本行。我们还写入读入的十六进制字符串,并附带行结束符号“\ n”= 0x0a。通过离开萎凋块,文件将自动关闭。所以我们不需要考虑这个必要的措施。显示消息的读取时间为5秒后,该函数返回True。必须处理两个可能的错误,RC522的读取错误和不正确的主卡。
在程序开始时将读取标签()调用并在ESP32文件系统根目录下的一个文件slavecards.txt之后。当天的空列表应该记录卡片的十六进制字符串。凋零指令不能打开文件,因为它还不可用,当天仍然为空,并且IF构造的Else部分返回None。如果文件存在,则将line By line读入,删除行结束符号并将字符串附加到列表中,直到循环检测到文件的结束。在这种情况下,列表不是空的,而是返回的。
def readTags():
tags=[]
with open("slavecards.txt", "r") as f:
for line in f:
tags.append(line.strip("\n"))
if tags:
return tags
Else:
return None
SD卡上的每个文件夹可以包含不同数量的音乐标题。我们必须知道在一次循环中完成的数字,这样才能进行循环。Get()确定所有文件夹中的标题数量,并将其放入列表文件帐户中。我们从空列表开始,让我们自己小声说文件夹的数量。为了解决每个文件夹,我们在它的for循环中简单地提到了第一个标题,这样你就不会被破裂和其他噪音所激怒,我们事先把音量调到0。
def getNumberOfTitels():
fileCount=[]
nof=df.getNumberOfFolders()
df.volume(0)
sleep(0.2)
for t in range(nof):
df.play(t, 0)
sleep(0.2)
df.stop()
sleep(0.2)
n=df.getNumberOfFilesInFolder()
d.writeAt("Track:{}".format(t), 0, 4, False)
d.writeAt("Songs:{}".format(n), 0, 5)
fileCount.append(n)
df.reset()
volume=int(vol.read()*100/1024)
df.volume(volume)
sleep(0.2)
return fileCount
在for循环中,我们将标题的数量挂在后面的列表上。重置DFPlayer后,我们将音量恢复到电位器指定的值。返回标题号列表。
作为最后一个函数,我们声明player()。在播放循环的运行期间,会不断读取播放列表卡。一张新卡从刚刚播放的播放列表中退出。在本例中,变量acard将空字符串返回给主程序。所以这在函数中是可能的,我们全局声明变量。为了让玩家正确开始,我们停止了它。然后进入for循环。玩家在这里表现得很固执,跳过了第一个标题,所以我让循环的运行索引S Start为-1。is playing()现在应该立即返回我们检查的内容,然后文件夹T中的标题S让播放。
def player(d, t, nof):
global acard
df.stop()
for s in range(-1, nof):
if not df.isPlaying():
df.play(t, s)
d.clearFT(0, 5, 15, 5, False)
d.writeAt("Titel: {}".format(s), 0, 5)
while df.isPlaying():
volume=int(vol.read()*100/1024)
df.volume(volume)
u=readUID(d, "TAG TO STOP", 100)
if u is not None:
d.clearFT(0, 3, 15)
D.Cleart (0, 3, 15)
df.stop()
acard=""
return
d.clearFT(0, 3, 15)
df.stop()
在比赛进行期间,还有很多事情要做。必须查询电位器,并将值传递给DFPlayer。然后我们向RC522发出读取命令并发出可能被拆除的信息。在100毫秒= 0.1秒后出现无返回,进入下一轮While循环。但是如果注册了一张新卡,我们停止DFPlayer并准备使用card = “”播放一个新的播放列表。如果播放列表已经完全播放,我们删除显示的下部,并给DFPlayer另一个停止命令。有时这让各种命令都感到厌恶。这导致在开发程序时出现各种令人失望的时刻,这总是使得有必要打开新的解决方案。
有些变量必须为主程序初始化。
cards=[] # Liste der Karten-IDs leeren
acard="" # aktuelle Karten-ID
ncard="" # neue Karten-ID
song=0 # Stets mit Song 0 starten
第一个操作现在是读取卡片id,如果文件slavecards.txt给出。当程序第一次启动时,这里没有任何标志。因此,我们使用try和except away来保护来自reap()的调用。我们在Except块中创建文件。因此,我们通过Timeoutms()方法从接口中调用了一个非阻塞的软件定时器allread()。当计时器运行时,我们调用循环adduid() on。我们已经讨论过处理这个函数的方法。当例程True返回时,一个卡被注册,计时器被重新启动。如果没有更多的卡片,我们只需等待计时器并在第二次尝试中将IDS放入列表中。
试一试:
cards=readTags()
except OSError as e:
allRead=TimeOutMs(10000)
while not allRead():
if addUID(d):
allRead=TimeOutMs(10000)
d.clearFT(0, 3, 15, 4, False)
d.writeAt(" ALL CARDS READ", 0, 3)
cards=readTags()
If the list is not empty, it goes into the main loop that is constantly going through, even while a title is played.
IF Cards:
d.writeAt("***MP3-PLAYER***", 0, 0, False)
d.writeAt("PUT ON MP3-TAG", 0, 1, False)
d.writeAt("Hole die Anzahl", 0, 2, False)
d.writeAt("Songs/Folder", 0, 3)
filesInFolder=getNumberOfTitels()
d.clearFT(0, 2, 15)
df.stop()
while 1:
uid=readUID(d, "PLAYLIST-TAG", 1000)
if uid is not None:
ncard=uid[1]
volume=int(vol.read()*100/1024)
df.volume(volume)
我们试着读卡片。如果工作,我们显示十六进制字符串的变量Ncard到,并询问电位器下一步。
当card的内容与Ncard的内容不匹配时,开始播放新播放列表。在这种情况下,将卡片已经更新。
if acard != ncard:
acard=ncard
然后我们检查列表卡中的ID是否包含在内。如果是,我们确定条目的索引,并找到SD卡上的文件夹编号。我们将组织移交给播放列表player()。她获得了对OLED对象的引用,文件夹编号和路上的标题数量。
如果卡在卡中:
d.clearFT(0, 2, 15, 5, False)
t=cards.index(acard) # Ordnernummer
d.writeAt("PLAYLIST {}".format(t), 0, 3, False)
d.writeAt(listen[t], 0, 4)
player(d, t, filesInFolder[t])
卡片ID不在cards Found中,在进入主循环的下一轮之前,我们得到一个错误消息。
对于自主公司,不连接PC机,必须将名称为main.py的程序上传到ESP32的flash中。该过程如上所述。或者,你也可以在“文件-菜单”中选择“另存为”。然后选择目标Micropython设备。如果你现在感染了电池板上的ESP32,该程序在没有PC的情况下启动。
会议快结束了。玩家的装备还有其他选择。如果你想要声音更大,在DFPlayer Mini的立体声输出上放一个放大器。只需几个按钮,你就可以在标题中滚动,通过无线局域网连接,你可以用一个Android应用程序完全控制它,这个应用程序很容易使用麻省理工学院的应用程序- inventor 2就可以创建。
本文编译自hackster.io





