这是一篇给新手的「从0手搓AI机器人」教程
大模型开发/技术交流
- LLM
9小时前20看过
本节目录
-
-
前置准备
-
-
-
1.1 硬件准备
-
1.2 软件准备
-
-
2. 组装 & 开发攻略
-
2.1 组装舵机和支架
-
2.2 开发板“归中”和舵机“归中”
-
2.3 组装底座和拼装
-
2.4 安装屏幕
-
2.5 软件测试
-
-
-
代码走读
-
-
-
3.1 CmdClient
-
3.2 Speaker
-
3.3 Listener
-
1. 前置准备
前置的物料准备,多亏了@Mark 和 @卓千寻 两位大佬帮统一采购,让我们到了之后直接就能组装&开发,再次感谢比心!
1.1 硬件准备
如果是想纯自己购买物料手搓,可以参考我们这次的,这篇文档里都有写:
主要就是这些东西:
变身成图片:
1.2 软件准备
-
去 Arduino 官网下载 IDE(www.arduino.cc/en/software….
-
从 Github (github.com/ideamark/de…
Arduino IDE 是一款用于编写和上传 Arduino 程序的集成开发环境。简单来说,就是让你可以编写代码并上传到Arduino板子上,控制它执行各种任务。
2. 组装 & 开发攻略
这里两位主理人提供了PPT教程,大家可以去参考
但如果作为纯新手,只看这个教程估计是搞不定,不要问我是怎么猜到的......所以接下来,我会结合着PPT的步骤,再做些补充,目的是为了更加方便于新手入门。
2.1 组装舵机和支架
先说说这个舵机是干嘛的,说实话我也是今天才知道......舵机是一种位置(角度)伺服的驱动器,适用于那些需要角度不断变化并可以保持的控制系统。简单点说,就是让东西按照你想要的角度转动。 比如在小机器人身上,让机器人的胳膊、腿按照我们想要的姿势弯曲或者伸直;或者在那种能转动摄像头的设备里,让摄像头转到想看的方向等等。
这是组装方式:
这个是两个组装舵机之后的图片,一个是x轴,一个是y轴:
这里我想说,到这一步的时候,我才理解了“手搓” 机器人的真正含义,我真后悔没有拍下那个搓刀的照片,以及搓那个舵机臂的过程.....
还有就是螺丝不能拧太紧,我那个机器人最后只能左右转,不能上下转,大佬说是螺丝拧太紧了导致的。我怀疑我最近可能是牛奶喝多了,所以这个吃奶的劲儿使出来之后,机器人看了都连连摇头......
2.2 开发板“归中”和舵机“归中”
这个术语也是今天新学的,我感觉我今天就是个快乐的小学生......
开发板“归中”
在进行整体系统调试之前,需要对开发板进行一些基础设置的 “归中” 操作。比如,将开发板上用于控制舵机的 PWM(脉冲宽度调制)信号的输出设置为中间值,这样当舵机连接时,舵机就处于一个中间位置(如果舵机的角度控制是基于 PWM 信号,并且中间 PWM 值对应舵机的中间角度)。
这个操作不是在我电脑执行的,所以没有拍过程,流程就是把开发板插到电脑,然后在Arduino IDE 里选择 “Arduino Uno”,然后会弹出你这台机器的端口,选择,然后执行下面这段代码。
舵机“归中”
舵机归中就是让舵机的输出轴转动到中间位置。例如,对于 0 - 180 度的舵机,归中就是转动到 90 度的位置;对于 - 90 - 90 度的舵机,归中就是转动到 0 度的位置。
这个在操作之前需要先完成舵机和电路板的插线,对应PPT里这张图,我当时是没有完全懂,于是就看了几遍背下来了,照着插反正也能跑。回来之后又研究了一下,现在大概可以说清楚了,解释如下:
在开发板上,13、12 这样的数字是引脚编号。这些引脚是开发板与外部设备(如传感器、执行器等)进行电气连接的接口。
5V 代表开发板上提供的 5 伏特直流电压。这个 5V 电源引脚是开发板向外提供电能的接口之一。
GND 是零电位点,其实就是我们理解的地线。在开发板的电路系统中,所有的电压都是相对于 GND 而言的。
有了这些基础知识后,我们再来看怎么插。
舵机这边的线包括信号线(黄色的)、正极线(红色的)、负极线(棕色的)。
所以就是正极线连到5V上,负极线连到GND上,信号线连到 13/12引脚上。
连完之后再执行以下上面那套程序,就完成了舵机“归中”。
电路板上插完是这样的:
2.3 组装底座和拼装
这一步就是把那个白色的十字的舵机臂,无论用什么方式,塞到那个底座里,然后从底下把螺丝拧上来。我搓、我搓、我搓搓搓,我拧、我拧、我拧拧拧......
再然后就是把它们俩组装到一块儿,这里我真的强烈建议,如果是之前没弄过的人,一定一定得有一个参照物放在那儿,摆放的方向、顺序都得一模一样,但凡有一个不一样的,就等着返工重来吧,别问我是怎么知道的
2.4 安装屏幕
先把屏幕用热熔胶固定在上面那块板上,然后就是插线,具体插的方式,就按照屏幕上上面指示的四个插线位置「GND」「VCC」「SCL」「SDA」,依次插到开发板上即可。
都插完之后就可以进行测试了,用代码库里 oled_test.ino 那个文件进行测试,如果屏幕亮了,就说明都安装正确了。插完之后是这样的:
然后还要把 robot_base.ino 的代码也通过Arduino IDE 上传到开发板上。这个过程会将编译后的程序(一个二进制文件)通过选定的端口传输到Arduino板子的内存中。
2.5 软件测试
按照PPT中的如下步骤操作,执行 action.py 和 chat.py 就可以测试转动和语音聊天了。
最后我这边的进展是运行action.py能动了,但是语音最后有一个请求超时的报错,具体原因还要再看一下了,好在已经到了我比较可控的领域里,另外后面我还会根据我的想法做定制修改,再加上离开会场之后没有连接线能用了,接下来要等买的连接线到了之后才能继续调了,不过今天还是收获满满的,也算是基本上完成啦。
3. 代码走读
这里走读的代码是Mark大佬分享在github(github.com/ideamark/de… 的代码,最开始大家可以clone下代码直接跑通就好,但后面如果想深入开发,还是需要了解代码里面的具体逻辑的。
common.py 里面提供了一些最基础的模块,是比较核心的,包括CmdClient、Speaker、Listener这三个比较主要的类。还有几个关于聊天和动作的基础方法,这几个其实大家可以后续根据自己的需求再去做改造的。
action.py 是关于机器人动作的测试。chat.py 是关于聊天的测试。
我下面主要是对CmdClient、Speaker、Listener这三个类做一个注释补充,便于大家理解。
3.1 CmdClient
CmdClient 类用于通过串口与设备进行通信。它可以列出可用的串口、选择一个串口、发送消息并等待响应。通过这些功能,可以方便地与串口设备进行交互。
下面是补充了注释的版本:
class CmdClient(object):def __init__(self, baud_rate=115200):self.baud_rate = baud_ratelogger.info("Available serial ports:")# 列出可用的串口。available_ports = self.list_serial_ports()if not available_ports:logger.error("No serial ports found.")return# 选择一个串口。self.selected_port = self.select_serial_port(available_ports)self.ser = serial.Serial(self.selected_port, self.baud_rate, timeout=1)logger.info(f"Connected to {self.selected_port} at {self.baud_rate} baud rate.")time.sleep(7)# 列出所有可用的串口。# 返回值:一个包含可用串口设备名称的列表。def list_serial_ports(self):ports = serial.tools.list_ports.comports()available_ports = []for port in ports:if "serial" in port.device.lower():available_ports.append(port.device)logger.info(port.device)return available_ports# 从指定的串口取数据。def read_from_port(self, serial_port):# 持续检查串口是否有数据等待读取。while True:if serial_port.in_waiting:data = serial_port.read(serial_port.in_waiting)# 如果有数据,读取并解码为UTF-8字符串。result = data.decode('utf-8', errors='ignore')logger.debug("\nReceived:", result.strip('\n').strip())# 从可用的串口中选择一个def select_serial_port(self, available_ports):# 如果只有一个可用串口,直接返回该串口if len(available_ports) == 1:return available_ports[0]# 使用 inquirer 库提示用户选择一个串口questions = [inquirer.List('port',message="Select a port",choices=available_ports,carousel=True)]answers = inquirer.prompt(questions)return answers['port']# 发送消息到串口并等待响应。def send(self, msg):try:encode_msg = msg.encode('utf-8')self.ser.write(encode_msg)logger.debug(f"Sent: {msg}")## 开始计时,等待响应。start_time = time.time()received_msg = ""while True:# 如果串口有数据等待读取,读取并解码为UTF-8字符串。if self.ser.in_waiting > 0:received_msg += self.ser.read(self.ser.in_waiting).decode('utf-8')# 如果超过10秒没有响应,返回。if time.time() - start_time > 10: return# 如果接收到的消息包含发送的消息,记录并返回接收到的消息。if received_msg and msg in received_msg:logger.debug(f"Received: {received_msg}")return received_msg# 每0.1秒检查一次串口。time.sleep(0.1)except serial.SerialException as e:logger.error(f"Error: {e}")return
3.2 Speaker
Speaker类作用是将文本转换为语音并播放音频。这个类的设计使得文本转语音和音频播放可以异步进行,不会阻塞主线程的执行。
class Speaker(object):def __init__(self):self.executor = ThreadPoolExecutor(max_workers=1)# 初始化 pygame.mixer,这是 pygame 库中的一个模块,用于加载和播放音频pygame.mixer.init()# 播放指定路径的音频文件。def play_audio(self, audio_path):pygame.mixer.music.load(audio_path)pygame.mixer.music.play()# 循环检查音频是否正在播放。如果音频正在播放,线程将每秒休眠一次,直到播放完成。while pygame.mixer.music.get_busy():time.sleep(1)# 将文本转换为语音并播放。def say(self, text, model="tts-1", voice="onyx", audio_path='output.mp3'):# 将文本转换为语音。response = client.audio.speech.create(model=model,voice=voice,input=text)# 将生成的音频流保存到指定路径的文件中。response.stream_to_file(audio_path)# 提交一个任务到线程池,在单独的线程中执行 play_audio 方法播放音频文件。self.executor.submit(self.play_audio, audio_path)
3.3 Listener
Listener 主要用于通过麦克风录制音频并将其转换为文本,同样是异步执行,不会阻塞主线程的执行。
这里有一个小问题,是在活动过程中 笑斌 大佬提出并改进的,原来代码里录音的代码如下:audio_data = self.recognizer.listen(source)
这会有一个问题就是录音不结束,改成下面这行就会好,我下面的代码片段里用的也是改进后的:
audio_data = self.recognizer.listen(source, timeout=3,phrase_time_limit=5)
class Listener(object):def __init__(self, cmd):self.cmd = cmd# Recognizer 用于语音识别。self.recognizer = sr.Recognizer()self.executor = ThreadPoolExecutor(max_workers=1)def hear(self, audio_path='input.wav'):# 使用 sr.Microphone 作为音频源,打开麦克风。with sr.Microphone() as source:input("\n按回车开始说话 ")print("开始说话...")# 提交一个任务到线程池,在单独的线程中执行 act_random 函数self.executor.submit(act_random, self.cmd)# 录制音频。timeout 参数表示等待音频输入的超时时间,phrase_time_limit 参数表示单次录音的最长时间。audio_data = self.recognizer.listen(source, timeout=3,phrase_time_limit=5)print("录音已完成")# 将录制的音频数据保存到指定路径的文件中。with open(audio_path, "wb") as audio_file:audio_file.write(audio_data.get_wav_data())audio_file= open(audio_path, "rb")# 将音频文件转换为文本。transcription = client.audio.transcriptions.create(model="whisper-1",file=audio_file)# 返回转换后的文本。return transcription.text
惯例结尾放一棵树
————————————————
版权声明:本文为稀土掘金博主「陌北有棵树」的原创文章
原文链接:https://juejin.cn/post/7419894731814191114
如有侵权,请联系千帆社区进行删除
评论