概述 视觉循迹 躲避悬崖 双灰度循迹 三灰度循迹 触须避障 红外避障 寻迹与路口 灭火 红外跟随 悬崖巡检 无线遥控 蓝牙遥控 |
2.结构说明
该样机由两个 直流驱动轮模组 构成,驱动轮模组呈轴对称分布在车架上,再使用万向轮对车尾进行支撑,保持车身水平。
1.运动功能说明
023号双轮底盘可以通过两个驱动轮的 差速运动 来实现前进、后退、原地转向、大半径转向等基本行驶功能。
前进与后退 原地转向 大半径转向
3.运动功能实现
3.1 电子硬件
在这个示例中,我们采用了以下硬件,请大家参考:
3.2 编写程序
前进功能的代码(点击查看:Forward.ino)
后退功能的代码(点击查看:Backward.ino)
主控板 | Basra(兼容Arduino Uno) |
扩展板 | Bigfish2.1 |
电池 | 7.4V锂电池 |
序号 | 内容 |
1 | 样机3D文件 |
2 | 例程源代码 |
将TT马达接在两个直流电机接口上,两个直流电机接口的针脚号分别为(D5,D6)以及(D9,D10),并将主控板和电池在车身固定好。
编程环境 | Arduino IDE |
语言 | C语言 |
驱动部件 | 直流电机 |
4.扩展样机
本样机也有一些扩展,如使用多个万向轮做支撑的版本,如下图所示。
5.资料清单
原地转向功能的代码(点击查看:TurnInPlace.ino)
大半径转向功能的代码(点击查看:BigTurn.ino)
【整体打包】-【023】小型双轮差速底盘机器人-概述-资料附件.rar | 910.96KB | 下载41次 | 下载 |
1. 任务描述
在机器人小车(R023d)上搭载摄像头,摄像头采集图像信息并通过WiFi将信息传递给PC端,然后PC端使用OpenCV对摄像头读取到的视频进行灰度化、高斯滤波、腐蚀、膨胀等处理,使图像分为黑白两色。PC端进行图像信息处理并将处理结果传递为下位机,下位机接收上位机处理的图像信息结果后便会控制小车相应运动,小车运动包含前进、左转、右转、停止。
2. 电子硬件
在这个示例中,我们采用了以下硬件,请大家参考:
主控板 | Basra(兼容Arduino Uno) |
扩展板 | Bigfish2.1 |
电池 | 7.4V锂电池 |
通信 | 2510通信转接板 |
WiFi路由器 | |
其它 | 摄像头x1、计算机x1 |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-02-02 https://www.robotway.com/ ----------------------------------------------------------------------------------- /* wift car: 2019/08/19: JN left: 9, 5; right: 10, 6; */ const String FORWARD = "F"; const String BACK = "B"; const String LEFT = "L"; const String RIGHT = "R"; const String STOP = "S"; int speed_left = 41; int speed_right = 41; void setup() { Serial.begin(9600); pinMode(5, OUTPUT); pinMode(6, OUTPUT);
pinMode(9, OUTPUT); pinMode(10, OUTPUT); Stop(); delay(1000); } void loop() { String data = SerialRead();
//if(data != ""){ if(data == FORWARD) Forward(); else if(data == BACK) Back(); else if(data == LEFT) Left(); else if(data == RIGHT) Right(); else if(data == STOP) Stop(); // } } String SerialRead(){ String str; while(Serial.available()){ str += char(Serial.read()); } return str; } void Forward(){ analogWrite(9, speed_left); analogWrite(5, 0); analogWrite(6, 0); analogWrite(10, speed_right); } void Back(){ analogWrite(9, 0); analogWrite(5, speed_left); analogWrite(6, speed_right); analogWrite(10, 0); } void Left(){ analogWrite(9, 0); analogWrite(5, speed_left); analogWrite(6, 0); analogWrite(10, speed_right); } void Right(){ analogWrite(9, speed_left); analogWrite(5, 0); analogWrite(6, speed_right); analogWrite(10, 0); } void Stop(){ analogWrite(9, speed_left); analogWrite(5, speed_left); analogWrite(6, speed_right); analogWrite(10,speed_right); } |
/******************************************************************************************* 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-02-02 https://www.robotway.com/ --------------------------------------------------------------------------------------- using System; using System.IO; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Windows.Media.Animation; using System.Threading; using OpenCvSharp; using System.Drawing; using System.Drawing.Imaging; using System.Net; using System.Net.Sockets; namespace Tracking_Car { /// <summary> /// Tracking_Car /// </summary> public partial class MainWindow : System.Windows.Window { //定义视频,控制地址以及控制端口变量 static string CameraIp = "http://192.168.8.1:8083/?action=stream"; static string ControlIp = "192.168.8.1"; static string Port = "2001"; //定义上位机发送的控制命令变量 //定义命令变量 string CMD_FORWARD = "", CMD_TURN_LEFT = "", CMD_TURN_RIGHT = "", CMD_STOP = ""; /* * 指针角度对应各颜色 * 25 -> 红色 * 90 -> 绿色 * 150 -> 蓝色 */ int ANGLE_LEFT = 0; int ANGLE_GO = 0; int ANGLE_RIGHT = 0; //黑色像素在左右两侧所占比例 double numOfleft = 0.0; double numOfright = 0.0; //创建视频图像实例 VideoCapture capture = new VideoCapture(CameraIp); //图像大小:宽度 X 长度 = 160 X 120; Mat frame = new Mat(); //存储视频每一帧图像像素 Mat result = new Mat(); //存储二值化图像 static byte[] kernelValues = { 0, 1, 0, 1, 1, 1, 0, 1, 0 }; // cross (+) Mat kernel = new Mat(3, 3, MatType.CV_8UC1, kernelValues); //图像中心线坐标 int x1, y1, x2, y2; //窗口面积 float area; //视频显示切换变量 Boolean isChange = false; //循迹开始开关变量 Boolean isBegin = false; public MainWindow() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { Assignment(); } //变量赋值函数 private void Assignment() { ANGLE_LEFT = 25; ANGLE_GO = 90; ANGLE_RIGHT = 150; rateLeft.Height = 10; rateRight.Height = 10; x1 = 80; y1 = 0; x2 = x1; y2 = 120; area = 160 * 120 / 2; CMD_FORWARD = "F"; CMD_TURN_LEFT = "L"; CMD_TURN_RIGHT = "R"; CMD_STOP = "S"; } /// <summary> /// MatToBitmap(Mat image) /// </summary> public static Bitmap MatToBitmap(Mat image) { return OpenCvSharp.Extensions.BitmapConverter.ToBitmap(image); } /// <summary> /// BitmapToBitmapImage(System.Drawing.Bitmap bitmap) /// </summary> public static BitmapImage BitmapToBitmapImage(Bitmap bitmap) { using (MemoryStream stream = new MemoryStream()) { bitmap.Save(stream, ImageFormat.Png); //格式选Bmp时,不带透明度 stream.Position = 0; BitmapImage result = new BitmapImage(); result.BeginInit(); // According to MSDN, "The default OnDemand cache option retains access to the stream until the image is needed." // Force the bitmap to load right now so we can dispose the stream. result.CacheOption = BitmapCacheOption.OnLoad; result.StreamSource = stream; result.EndInit(); result.Freeze(); return result; } } //颜色指示动画函数 int angelCurrent = 0; private void ColorIndicate(int where) { RotateTransform rt = new RotateTransform(); rt.CenterX = 130; rt.CenterY = 200; this.indicatorPin.RenderTransform = rt; double timeAnimation = Math.Abs(angelCurrent - where) * 5; DoubleAnimation da = new DoubleAnimation(angelCurrent, where, new Duration(TimeSpan.FromMilliseconds(timeAnimation))); da.AccelerationRatio = 0.8; rt.BeginAnimation(RotateTransform.AngleProperty, da); switch (where) { case 25: dirDisplay.Content = "左转"; break; case 90: dirDisplay.Content = "前进"; break; case 150: dirDisplay.Content = "右转"; break; default: dirDisplay.Content = "方向指示"; break; } angelCurrent = where; } //检测函数 private void ColorDetect() { //将摄像头RGB图像转化为灰度图,便于后续算法处理 Mat gray = frame.CvtColor(ColorConversionCodes.BGR2GRAY); //进行高斯滤波 Mat binary = gray.Threshold(0, 255, ThresholdTypes.Otsu | ThresholdTypes.Binary); //闭运算,先膨胀后腐蚀,消除小型黑洞 Cv2.Dilate(binary, binary, null); Cv2.Erode(binary, binary, kernel); result = binary.Clone(); result.Line(new OpenCvSharp.Point(x1, y1), new OpenCvSharp.Point(x2, y2), new Scalar(255, 255, 255), 1); float rateOfleft = 0, rateOfRight = 0; var indexer = result.GetGenericIndexer<Vec3b>(); for (int i = 0; i < result.Rows; i++) { for (int j = 0; j < result.Cols; j++) { int B = indexer[i, j][0]; int G = indexer[i, j][1]; int R = indexer[i, j][2]; if (B == 0 && G == 0 && R == 0) { if (j <= x1) { numOfleft++; } else { numOfright++; } } } } rateOfleft = (float)(numOfleft) / area * 100; rateOfRight = (float)(numOfright) / area * 100; rateLeft.Height = rateOfleft; rateRight.Height = rateOfRight; numOfleft = 0; numOfright = 0; } //命令发送函数 void SendData(string data) { try { IPAddress ips = IPAddress.Parse(ControlIp.ToString());//("192.168.8.1"); IPEndPoint ipe = new IPEndPoint(ips, Convert.ToInt32(Port.ToString()));//把ip和端口转化为IPEndPoint实例 Socket c = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//创建一个Socket c.Connect(ipe);//连接到服务器 byte[] bs = Encoding.ASCII.GetBytes(data); c.Send(bs, bs.Length, 0);//发送测试信息 c.Close(); } catch (Exception e) { MessageBox.Show(e.Message); } } //方向指示更新及命令发送 private void CommandSend() { double l = rateLeft.Height; double r = rateRight.Height; if (isBegin) { if (Math.Abs(l - r) < 20) //两侧黑色轨迹基本相同,前进 { ColorIndicate(ANGLE_GO); SendData(CMD_FORWARD); } else if ((l - r) < -50) //左侧黑色轨迹小于右侧,右转 { ColorIndicate(ANGLE_RIGHT); SendData(CMD_TURN_RIGHT); } else if ((l - r) > 50) //右侧黑色轨迹小于左侧,左转 { ColorIndicate(ANGLE_LEFT); SendData(CMD_TURN_LEFT); } } } //视频显示函数 private void ThreadCapShow() { while (true) { try { capture.Read(frame); // same as cvQueryFrame if (frame.Empty()) break; this.Dispatcher.Invoke( new Action( delegate { if (isChange) { //检测图像左右两侧黑色像素所占的比例,并显示图像 ColorDetect(); originImage.Source = BitmapToBitmapImage(MatToBitmap(result)); CommandSend(); result = null; } else { originImage.Source = BitmapToBitmapImage(MatToBitmap(frame)); } } )); //Cv2.WaitKey(100); //bitimg = null; } catch { } } } //加载视频 private void loadBtn_Click(object sender, RoutedEventArgs e) { if (originImage.Source != null) return; Thread m_thread = new Thread(ThreadCapShow); m_thread.IsBackground = true; m_thread.Start(); } //切换视频显示,显示检测结果 private void changeBtn_Click(object sender, RoutedEventArgs e) { if (!isChange) { isChange = true; changeBtn.Content = "返回"; } else { isChange = false; changeBtn.Content = "切换"; //指针角度归零 ColorIndicate(0); rateLeft.Height = 10; rateRight.Height = 10; result = null; } } |
3. 功能实现
视觉小车巡黑线工作原理:
(1) 摄像头采集图像信息;
(2) 通过 WiFi 将图像信息传递给 PC 端(VS2015 配置的 OpenCV 环境);
(3) 在 PC 端使用 OpenCV 对摄像头读取到的视频进行灰度化、高斯滤波、腐蚀、膨胀等处理,使图像分为黑白两色,采用 RGB 颜色模型作为黑白颜色判断;
(4) 将图像对称分成左右两半,分别判断左、右计算检测在显示的摄像范围内的黑色像素区域所占比例=黑色像素范围/显示的摄像范围;
(5) 比较两侧黑色像素区域所占比例大小确定前进方向,如果左侧比例大于右侧,则小车左偏离,进行右转;
(6) PC端进行图像信息处理,将处理结果传递为下位机,下位机控制小车进行相应的运动;
3.1硬件连接
接线说明:
① 将2510通信转接板连接到扩展板的扩展坞上面;
② 用3根母对母杜邦线将2510通信转接板与WiFi路由器连接起来,GND-GND、RX-RX、TX-TX;
③ 找到1根USB线,一端连接到2510通信转接板接口上,另一端连接到WiFi路由器USB接口上;
④ 将摄像头线连接到WiFi路由器接口上。
3.2示例程序
编程环境:Arduino 1.8.19
① 下位机例程:
下位机接收上位机处理的图像信息结果控制小车相应运动,小车运动包含前进、左转、右转、停止。
参考例程代码(car.ino)如下:
② 上位机例程:
上位机(Visual Studio 2015.net下配置OpenCV环境)进行图像信息处理。下面提供一个参考例程(MainWindow.xaml.cs),大家可尝试根据实验效果改写。
4. 资料清单
序号 | 内容 |
1 | 【R023】-视觉循迹-程序源代码 |
2 | 【R023】-视觉循迹-样机3D文件 |
//循迹开始 private void bgeinBtn_Click(object sender, RoutedEventArgs e) { isBegin = true; } //循迹停止 private void stopBtn_Click(object sender, RoutedEventArgs e) { isBegin = false; SendData(CMD_STOP); } } } |
【整体打包】-【R023】小型双轮差速底盘-视觉循迹-资料附件.zip | 1.08MB | 下载7次 | 下载 |
躲避悬崖
1. 功能说明
本实验使用的样机为R023样机小型双轮差速底盘。在样机前方安装3个 近红外传感器 ,实现机器人躲避悬崖、在某平台上移动时不会掉下去的效果。
2. 电子硬件
在这个示例中,我们采用了以下硬件,请大家参考:
主控板 | Basra(兼容Arduino Uno) |
扩展板 | Bigfish2.1 |
传感器 | 近红外传感器 |
电池 | 7.4V锂电池 |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-02-10 https://www.robotway.com/ ------------------------------------------------------------------------------------*/ void Right(); void Left(); void Stop(); void Forward(); void Back(); void setup() { pinMode( 17, INPUT); pinMode( 18, INPUT); pinMode( 14, INPUT); pinMode( 10, OUTPUT); pinMode( 6, OUTPUT); pinMode( 5, OUTPUT); pinMode( 9, OUTPUT); } void loop() { if (((!( digitalRead(14)) && ! (digitalRead(17)) ) )) { Forward(); } if (( digitalRead(14) )) { Left(); delay( 300 ); } if (( digitalRead(17) )) { Right(); delay( 300 ); } if (( digitalRead(18) )) { Back(); delay( 1000 ); analogWrite(5 , 100); analogWrite(6 , 0); analogWrite(9 , 0); analogWrite(10 , 100); delay( 1500 ); } } void Right() { analogWrite(5 , 0); analogWrite(6 , 0); analogWrite(9 , 100); analogWrite(10 , 0); } void Forward() { analogWrite(5 , 100); analogWrite(6 , 0); analogWrite(9 , 100); analogWrite(10 , 0); } void Back() { analogWrite(5 , 0); analogWrite(6 , 100); analogWrite(9 , 0); analogWrite(10 , 100); } void Left() { analogWrite(5 , 100); analogWrite(6 , 0); analogWrite(9 , 0); analogWrite(10 , 0); } void Stop() { analogWrite(5 , 0); analogWrite(6 , 0); analogWrite(9 , 0); analogWrite(10 , 0); } |
左轮直流电机连在D9,D10接口上;右轮直流电机连在D5,D6接口上;3个近红外传感器从左到右分别连在A0、A4、A3接口上。
3. 示例程序
编程环境:Arduino 1.8.19
编写并烧录以下程序(nine.ino),该程序将实现演示视频中的动作。
4. 扩展
本实验采用3个近红外传感器,利用的是近红外传感器能够识别到距离较近的桌面,无法识别到距离较远的地面,因此桌面要距离地面远一些。
本实验还可以使用灰度传感器或者白标传感器。利用的是灰度和白标在悬崖处极难收到反射回来的红外线的原理,因此相应的桌面必须是浅色,如果桌面也是深色,灰度和白标传感器就无法区分桌面和悬崖了。
5. 资料清单
序号 | 内容 |
1 | R023-躲避悬崖-例程源代码 |
【整体打包】-【R023】小型双轮差速底盘-躲避悬崖-资料附件.zip | 2.72KB | 下载3次 | 下载 |
1. 功能说明
在机器人车体上安装2个 灰度传感器 ,实现机器人按照下图所指定的路线进行导航运动,来模拟仓库物流机器人按指定路线行进的工作过程。
2. 使用样机
本实验使用的样机为R023e样机。
3. 功能实现
3.1 电子硬件
在这个示例中,我们采用了以下硬件,请大家参考:
主控板 | Basra(兼容Arduino Uno) |
扩展板 | |
传感器 | 灰度传感器 |
电池 | 7.4V锂电池 |
传感器1 | 传感器2 | 小车状态 | 动作 |
0 | 1 | 小车左偏 | 向右调整 |
1 | 0 | 小车右偏 | 向左调整 |
1 | 1 | 到达终点 | 停止 |
0 | 0 | 正常 | 前进 |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-02-09 https://www.robotway.com/ ------------------------------------------------------------------------------------*/ void turnleft_slow(); void forward(); void carstop(); void turnright_slow(); void setup() { pinMode( 18, INPUT); pinMode( 14, INPUT); pinMode( 10, OUTPUT); pinMode( 6, OUTPUT); pinMode( 5, OUTPUT); pinMode( 9, OUTPUT); } void loop() { if (( digitalRead(14) && digitalRead(18) )) { forward(); } if (( !( digitalRead(14) ) && digitalRead(18) )) { turnleft_slow(); } if (( digitalRead(14) && !( digitalRead(18) ) )) { turnright_slow(); } if (( !( digitalRead(14) ) && !( digitalRead(18) ) )) { carstop(); delay( 5000 ); } } void turnright_slow() { analogWrite(6 , 80); analogWrite(10 , 0); analogWrite(5 , 0); analogWrite(9 , 0); } void carstop() { analogWrite(6 , 0); analogWrite(10 , 0); analogWrite(5 , 0); analogWrite(9 , 0); } void turnleft_slow() { analogWrite(6 , 0); analogWrite(10 , 0); analogWrite(5 , 80); analogWrite(9 , 0); } void forward() { analogWrite(6 , 80); analogWrite(10 , 0); analogWrite(5 , 80); analogWrite(9 , 0); } |
void stop(); void left(); void right(); void forwards(); void setup() { pinMode( 18, INPUT); pinMode( 14, INPUT); pinMode( 10, OUTPUT); pinMode( 6, OUTPUT); pinMode( 5, OUTPUT); pinMode( 9, OUTPUT); } void loop() { if (( !( digitalRead(14) ) && digitalRead(18) )) { right(); } else { if (( digitalRead(14) && !( digitalRead(18) ) )) { left(); } else { if (( !( digitalRead(14) ) && !( digitalRead(18) ) )) { stop(); } else { forwards(); } } } } void stop() { analogWrite(5 , 0); analogWrite(9 , 0); analogWrite(6 , 0); analogWrite(10 , 0); } void right() { analogWrite(5 , 150); analogWrite(9 , 0); analogWrite(6 , 0); analogWrite(10 , 150); } void forwards() { analogWrite(5 , 150); analogWrite(9 , 0); analogWrite(6 , 150); analogWrite(10 , 0); } void left() { analogWrite(5 , 0); analogWrite(9 , 150); analogWrite(6 , 150); analogWrite(10 , 0); } |
电路连接说明:
① 电机连在D6,D10及D5,D9接口上;
② 2个灰度传感器分别接在扩展板的传感器接口A0、A4上。
3.2 编写程序
传感器触发情况、小车行驶状态、对应行为策略表:
① 根据实验内容,利用多分支结构设计出程序流程图。
机器人轨迹导航任务流程图
② 根据设计好的程序流程图进行编程,编写并烧录以下程序(blackline_4if.ino),该程序将实现演示视频中的动作。
编程环境:Arduino 1.8.19
也可以使用if…else嵌套写法(blackline_ifelse.ino)。
4. 资料清单
序号 | 内容 |
1 | 【R023】-灰度循迹2-例程源代码 |
2 | 【R023】-灰度循迹2-样机3D文件 |
【整体打包】-【R023】小型双轮差速底盘-2灰度循迹-资料附件.zip | 558.36KB | 下载5次 | 下载 |
三灰度循迹
1. 功能说明
在小型双轮差速底盘样机前方安装3个 灰度传感器 ,实现机器人沿下图所指定的跑道路线进行运动的效果。
2. 使用样机
本实验使用的样机为R023样机。
3. 功能实现
3.1 电子硬件
在这个示例中,我们采用了以下硬件,请大家参考:
主控板 | Basra(兼容Arduino Uno) |
扩展板 | |
传感器 | 灰度传感器 |
电池 | 7.4V锂电池 |
如果 机器人的某几个传感器触发了; 机器人的某几个电机做个什么事; 做多久; 如果 机器人的另外某几个传感器触发了; 机器人的某几个电机做个什么事; 做多久; |
如果 机器人的1号传感器触发了; 机器人的左侧电机顺时针转; 机器人的右侧电机逆时针转; 持续5秒; 如果 机器人的2号传感器触发了; 机器人的左侧电机逆时针转; 机器人的右侧电机顺时针转; 持续5秒; 否则 都不转 |
if { Sensor(端口a,触发);//传感器触发时此句为真,否则为假 } { Motor(L,顺); Motor(R,逆); Delay 5; } if { Sensor(端口b,触发); } { Motor(L,逆); Motor(R,顺); Delay 5; } else { Motor(L,停); Motor(R,停); } |
状态序号 | 传感器1 |
1 | 1 |
2 | 0 |
0 | 1 |
1 | 0 |
状态序号 | 传感器1 | 传感器2 |
1 | 1 | 1 |
2 | 1 | 0 |
3 | 0 | 1 |
4 | 0 | 0 |
新序号 | 传感器1 | 传感器2 |
0 | 0 | 0 |
1 | 0 | 1 |
2 | 1 | 0 |
3 | 1 | 1 |
传感器1 | 传感器2 | 传感器3 | 序号 | 小车状态 | 动作 |
0 | 0 | 0 | 0 | 都没触发,可能是跑偏了 | 后退,转向 |
0 | 0 | 1 | 1 | 小车左偏 | 左轮逆时针转,向右调整 |
0 | 1 | 0 | 2 | 小车正中 | 左轮逆时针转,右轮顺时针转,前进 |
0 | 1 | 1 | 3 | 在这个行进方向上不可能 | 无 |
1 | 0 | 0 | 4 | 小车右偏 | 右轮顺时针转,向左调整 |
1 | 0 | 1 | 5 | 在此跑道上不可能 | 无 |
1 | 1 | 0 | 6 | 遇到转角 | 右轮顺时针转,左转 |
1 | 1 | 1 | 7 | 在此跑道上不可能 | 无 |
状态序号 | 传感器1 | 传感器2 | 伪码 |
1 | 1 | 1 | if { |
2 | 1 | 0 | if { |
3 | 0 | 1 | if { |
4 | 0 | 0 | else …… |
switch(s) { case 1 : {动作1;}break; case 2 : {动作2;}break; case 3 : {动作3;}break; case 4 : Act_Stop();break; default:;break; } |
s=0; for(i=0;i<2;i++) //因为此例中有2个传感器,i取2 { s=s|(Servo(i+1,触发判断)<<i);//获得传感器值,移位,或运算 } |
switch(s) { case 0x00 : {动作0;}break; //序号也可以写作16进制数值 case 0x01 : {动作1;}break; case 0x02 : {动作2;}break; case 0x03 : {动作3;}break; default:;break; } |
s=0; for(i=0;i<3;i++) { s=s|(Input(i+1,1)<<i); } switch(s) { case 0x00 : 停;break; case 0x01 : {Motor(L,逆);Motor(R,停);}break; case 0x02 : {Motor(L,逆);Motor(R,顺);}break; case 0x04 : {Motor(L,停);Motor(R,顺);}break; case 0x06 : {Motor(L,停);Motor(R,顺);}break; default:;break; } |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-02-09 https://www.robotway.com/ ------------------------------------------------------------------------------------*/ int pin[3] = {A0, A3, A4}; //按车头前进方向,从右至左定义,后面经过公式计算,会转化为从左至右的顺序 int s; void setup() { pinMode( 5 , OUTPUT); pinMode( 6 , OUTPUT); pinMode( 9 , OUTPUT); pinMode( 10 , OUTPUT); } void loop() { s = 0; for(int i=0; i<3; i++) //循环获取三个传感器的值 { s|= (!digitalRead(pin[i]) << i); //经过左移运算和或运算后,按照A0、A3、A4的顺序产生一个三位2进制数值,表示3个传感器的组合触发状态 } switch (s) { case 0x00: //三个均未触发 back(); Left(); break; case 0x01: //右侧传感器触发,直线上摆动或遇到右转弯 Right(); break; case 0x02: //中间传感器触发,直线上直行 Forwards(); break; case 0x04: //左侧传感器触发,直线上摆动或遇到左转弯 Left(); break; case 0x06: //左侧两个触发,遇到左转弯 Left(); break; default:;break; } } void Left() { digitalWrite( 5 , LOW ); digitalWrite( 6 , HIGH); digitalWrite( 9 , HIGH ); digitalWrite( 10 , LOW ); } void Right() { digitalWrite( 5 , HIGH ); digitalWrite( 6 , LOW ); digitalWrite( 9 , LOW ); digitalWrite( 10 , HIGH ); } void Forwards() { digitalWrite( 5 , HIGH ); digitalWrite( 6 , LOW ); digitalWrite( 9 , HIGH ); digitalWrite( 10 , LOW ); } void back() { digitalWrite( 5 , LOW ); digitalWrite( 6 , HIGH ); digitalWrite( 9 , LOW ); digitalWrite( 10 , HIGH ); } |
传感器1 | 传感器2 | | 二进制结果 | 十进制结果 |
1 | 1 | 11 | 3 | |
1 | 0 | 10 | 2 | |
0 | 1 | 01 | 1 | |
0 | 0 | 00 | 0 |
电路连接说明:
① 左轮直流电机连在D9,D10接口上;
② 右轮直流电机连在D5,D6接口上;
③ 3个灰度传感器从左至右连接在A0,A4,A3端口上。
所以我们总是要用到大量的 if 语句,比如双轮小车的某个功能:
用伪码写出来就是:
在只有一个传感器的情况下,我们假设这是个开关量传感器。那么我们可以得到一个状态表格:
这个传感器有两个状态。
而当有两个传感器时,则有四个状态。
如果我们用 if 语句写这四个状态,就显得比较长。
在编程的时候,状态罗列的越全,机器人的bug就越少。但是随着传感器的增多,状态数量按2的N次幂增加,大量的if语句使执行效率变得很低,经常出现识别不灵的情况。我们需要换一种高效写法。
多个确定数量的传感器的触发组合,符合有限状态机的概念,有限状态机一般是用Switch语句来实现。如:
不难发现,这段语句实现的关键,就是识别出上页表中的1、2、3、4,四个状态序号。
那么问题就来了:我们如何让机器人知道自己传感器的触发组合对应于1、2、3、4的哪个序号呢?
二进制状态表
下面,我们把每组传感器返回值看成一个二进制数值。
结果我们发现了一种新的、可计算的编码方式:
于是,只要我们知道了传感器们的触发状态,也就知道了序号;知道了序号,也就知道了传感器们的触发状态。用这个序号去写switch语句,再合适不过了。下面我们要做的是,用一种算法,让机器人能够返回自己接收到的传感器组合值的二进制数据。
算法精解
我们可以使用以下算法来实现:
l 首先设置一个变量s,这个s,将存储传感器组的二进制状态序号。
l 我们还需要用到一个重要的运算符“<<”,这个运算符的意义是:左移
如:1<<n,意思是1向左移动n位,空出来的数位用0填补。
如:1<<1,结果就是10;1<<2,结果就是100;101<<1,结果就是1010
l 只要让机器人依次返回各个传感器的状态数值,最早获取的,移到最左;第二获得的,移到“倒数第二左”,……,以此类推。即可获得。
如两个传感器均触发:
先获得1号的数值(真)并左移0位,得
再获得2号的数值(真)并左移1位,得
两数值取“或”,即可得11
数学问题解决了,很容易就可以转化为程序语句:
于是switch语句可以写为:
策略表
下面我们以本实验中的“小型双轮差速底盘-3灰度循迹”程序为例,再来推导一遍。
传感器触发情况、小车行驶状态、对应行为策略表如下:
伪码如下:
这段代码中的动作,完全由策略表分析获得,因此,当状态比较多时,用户要学会利用策略表进行分析,从而确定机器人的动作策略,而不是凭空想象。
3.3 编写程序
编程环境:Arduino 1.8.19
编写并烧录以下程序(Track_Car.ino),该程序将实现演示视频中的动作。
4. 资料清单
序号 | 内容 |
1 | 【R023】-3灰度循迹-例程源代码 |
【整体打包】-【R023】小型双轮差速底盘-3灰度循迹-资料附件.zip | 3.17KB | 下载4次 | 下载 |
触须避障
1. 功能说明
在R023d机器人车体上安装2个 触须传感器 ,实现机器人小车避障功能。
2. 电子硬件
在这个示例中,我们采用了以下硬件,请大家参考:
主控板 | |
扩展板 | |
传感器 | 触须传感器 |
电池 | 7.4V锂电池 |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-04-23 https://www.robotway.com/ ------------------------------*/ void tui(); void youzhuan(); void zuozhuan(); void go(); void setup() { pinMode( 18, INPUT); pinMode( 14, INPUT); pinMode( 10, OUTPUT); pinMode( 6, OUTPUT); pinMode( 5, OUTPUT); pinMode( 9, OUTPUT); } void loop() { if (( !( digitalRead(14) ) && digitalRead(18) )) { tui(); delay( 1000 ); zuozhuan(); delay( 1000 ); } else { if (( digitalRead(14) && !( digitalRead(18) ) )) { tui(); delay( 1000 ); youzhuan(); delay( 1000 ); } else { if (( !( digitalRead(14) ) && !( digitalRead(18) ) )) { tui(); delay( 1000 ); youzhuan(); delay( 1000 ); } else { go(); } } } } void zuozhuan() { analogWrite(6 , 0); analogWrite(10 , 0); analogWrite(5 , 80); analogWrite(9 , 0); } void youzhuan() { analogWrite(6 , 80); analogWrite(10 , 0); analogWrite(5 , 0); analogWrite(9 , 0); } void go() { analogWrite(6 , 80); analogWrite(10 , 0); analogWrite(5 , 80); analogWrite(9 , 0); } void tui() { analogWrite(6 , 0); analogWrite(10 , 80); analogWrite(5 , 0); analogWrite(9 , 80); } |
电路连接:小车左轮电机连到Bigfish扩展板的D9,D10接口,小车右轮电机连到Bigfish扩展板的D5,D6;左侧触须传感器连接在Bigfish
扩展板A4端口,右侧触须传感器连接在Bigfish扩展板A0端口(如下图所示)
3. 功能实现
编程环境:Arduino 1.8.19
实现思路:机器人小车前进过程中,左边触须传感器触发后,小车右转;右边触须传感器触发后,小车左转;两个传感器同时触发后,
小车后退、转弯并前进。
将参考例程(sketch_apr23a.ino)下载到主控板,小车将实现避障功能,实验效果可参考演示视频。
4. 资料清单
序号 | 内容 |
1 | 【R023】-触须避障-例程源代码 |
2 | 【R023】-触须避障-样机3D文件 |
【整体打包】-【R023】小型双轮差速底盘-触须避障-资料附件.zip | 383.46KB | 下载2次 | 下载 |
红外避障
1. 功能说明
在R023e机器人车体上安装1个 近红外传感器 ,实现机器人小车避障功能。
2. 电子硬件
在这个示例中,我们采用了以下硬件,请大家参考:
主控板 | |
扩展板 | |
传感器 | 近红外传感器 |
电池 | 7.4V锂电池 |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-04-25 https://www.robotway.com/ ------------------------------*/ void forward(); void turnright(); void carstop(); void setup() { pinMode( 14, INPUT); pinMode( 10, OUTPUT); pinMode( 6, OUTPUT); pinMode( 5, OUTPUT); pinMode( 9, OUTPUT); } void loop() { if (!( digitalRead(14) )) { turnright(); delay( 1000 ); forward(); delay( 2000 ); carstop(); delay( 5000 ); } else { forward(); } } void turnright() { analogWrite(6 , 80); analogWrite(10 , 0); analogWrite(5 , 0); analogWrite(9 , 80); } void forward() { analogWrite(6 , 80); analogWrite(10 , 0); analogWrite(5 , 80); analogWrite(9 , 0); } void carstop() { analogWrite(6 , 0); analogWrite(10 , 0); analogWrite(5 , 0); analogWrite(9 , 0); } |
电路连接说明:
① 2个电机分别连在Bigfish扩展板的D6,D10及D5,D9接口上;
② 近红外传感器接在Bigfish扩展板的A0端口上。
3. 功能实现
编程环境:Arduino 1.8.19
实现思路:在机器人前方放置障碍物,当机器人前方检测到有障碍物时,实现机器人右转后再前进2s躲避障碍物;否则机器人继续直行,以此来模拟汽车避障过程。
将参考例程(sketch_apr25a.ino)下载到主控板,小车将实现避障功能。
4. 资料下载
序号 | 内容 |
1 | 【R023】-红外避障-程序源代码 |
2 | 【R023】-红外避障-样机3D文件 |
【整体打包】-【R023】小型双轮差速底盘-红外避障-资料附件.zip | 503.51KB | 下载3次 | 下载 |
寻迹
1. 功能说明
寻迹机器人是一种能够跟踪特定物体或线路的机器人。它们通常具有以下功能和特点:
① 传感器:寻迹机器人配备了用于感知环境的传感器,如摄像头、灰度传感器等。这些传感器可以探测地面上的标记、颜色、纹理或其他特定
特征,以确定要跟踪的目标。
② 自主导航:寻迹机器人通常具备自主导航能力,可以根据目标物体的位置和运动轨迹进行移动和调整。它们可能使用轮式、履带或其他移动
机构来在地面上移动。
③ 跟踪精度:寻迹机器人通常被设计为能够实时跟踪目标物体,并尽可能准确地保持距离和方向。一些高级寻迹机器人还可以通过预测目标物
体的运动来提高跟踪的精度。
④ 应用场景:寻迹机器人可以应用于多种场景,如工业生产线上的零部件跟踪、物流仓库中的货物识别与追踪、安防领域中的行人监控等。它
们在自动化、智能化和效率提升方面具有广泛的应用前景。
本文示例将实现R023样机小型双轮差速底盘机器人沿直线寻迹行走的一个功能。
2. 结构装配
按照下图所示方式进行安装:
3. 电子硬件
在这个示例中,我们采用了以下硬件,请大家参考:
主控板 | |
扩展板 | |
传感器 | 灰度传感器 |
电池 | 7.4V锂电池 |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-07-14 https://www.robotway.com/ ------------------------------*/ #include <Servo.h>//调用舵机库 Servo myservo;//声明一个舵机类 void setup()//Arduino的设置函数 { myservo.attach(4);//绑定控制舵机的引脚 } void loop()//Arduino的循环函数 { for(int i=0; i<180; i++){//通过调节i值控制舵机的运行参数 myservo.write(i);//输出控制舵机的运行参数 delay(500);//延时 } } |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-07-14 https://www.robotway.com/ ------------------------------*/ #include <Servo.h> #define middle1 88//定义对应舵机的中间值,即停止转动的值 #define middle2 88//此值需要测量,各个舵机不一定相同 Servo myservo[2];//定义一个舵机类数组 void setup() { myservo[0].attach(4); myservo[1].attach(3); } void loop() { Left();//调用左转函数 delay(1000); Right(); delay(1000); Forwards(); delay(1000); stop(); delay(1000); } void Left()//左转函数 { myservo[0].write(middle1); myservo[1].write(middle2 + 20); } void Right()//右转函数 { myservo[0].write(middle1 - 20); myservo[1].write(middle2); } void Forwards()//前进函数 { myservo[0].write(middle1 - 20); myservo[1].write(middle2 + 20); } void stop()//后退函数 { myservo[0].write(middle1); myservo[1].write(middle2); } |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-07-14 https://www.robotway.com/ ------------------------------*/ #include <LedControl.h>//调用点阵库函数 #include <Servo.h> LedControl lc=LedControl(12,11,13,1);//声明点阵类,并设置对应的引脚 int pin[3] = {A0, A4, A3};//设置传感器的对应的三个引脚 byte value;//声明传感器值变量 void setup() { LedInit();//初始化点阵 } /************************************************************************************ 此程序用到了for与switch的配合框架,可用于多传感器的实时处理,请细细体会! 具体解析:for循环中使用了位处理,这样的结果就是value的一个数据位对应一个传感器的状态, 此程序value的类型为byte,则可支持8个传感器,如果要使用更多传感器可定义int等。 传感器触发时返回值为0,因此value值与传感器触发的状态对应关系以A0触发为例: A0传感器触发-->二进制:00000110-->十六进制:0x06-->对应case 0x06; 所以这样做的好处就是当传感器的状态发生改变时程序可以快速的到达指定的处理方式 **************************************************************************************/ void loop() { value = 0; for(int i=0; i<3; i++){//通过循环检测,读取传感器的状态值 value |= (digitalRead(pin[i]) << i);//通过位处理得到结果值,digitalRead()用于读取数字值 } switch (value) {//根据结果值进行相应的事件处理 case 0x00://全部触发 LedOn(0);//点亮相应的点阵 LedOn(1); LedOn(2); break; case 0x01://触发右边两个 LedOn(1); LedOn(2); break; case 0x03://触发右边一个 LedOn(2); break; case 0x04://触发左边两个 LedOn(0); LedOn(1); break; case 0x05://触发中间一个 LedOn(1); break; case 0x06://触发左边一个 LedOn(0); break; default: ; } } void LedOn(int key)//根据参数点亮相应的点阵LED { lc.clearDisplay(0); for(int i=0; i<2; i++){ for(int j=3*key; j<3*key+2; j++){ lc.setLed(0, i, j, true); } } } void LedInit() //点阵初始化函数 { lc.shutdown(0,false); lc.setIntensity(0,8); lc.clearDisplay(0); } |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-07-14 https://www.robotway.com/ ------------------------------*/ #include <Servo.h> #define middle1 88 #define middle2 88 Servo myservo[2]; int pin[3] = {A0, A4, A3}; byte value; byte value_his = 0;//记录上一次的传感器值 void setup() { myservo[0].attach(4); myservo[1].attach(3); } void loop() { value = 0; for(int i=0; i<3; i++){ value |= (digitalRead(pin[i]) << i); } if(value == 0x07){//当传感器都没有触发时默认为上一次的值 value = value_his; } switch (value) { case 0x00://全部触发 Forwards(); break; case 0x01://触发右边两个 while(digitalRead(pin[1])){//通过while循环使小车回到跑道中间 Right(); } break; case 0x03://触发右边一个 while(digitalRead(pin[1])){ Right(); } break; case 0x04://触发左边两个 while(digitalRead(pin[1])){ Left(); } break; case 0x05://触发中间一个 Forwards(); break; case 0x06://触发左边一个 while(digitalRead(pin[1])){ Left(); } break; default: stop(); } value_his = value; } void Left() { myservo[0].write(middle1); myservo[1].write(middle2 + 20); } void Right() { myservo[0].write(middle1 - 20); myservo[1].write(middle2); } void Forwards() { myservo[0].write(middle1 - 20); myservo[1].write(middle2 + 20); } void stop() { myservo[0].write(middle1); myservo[1].write(middle2); } |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-07-14 https://www.robotway.com/ ------------------------------*/ #include <Servo.h> #include <LedControl.h> #define middle1 88 #define middle2 88 Servo myservo[2]; LedControl lc=LedControl(12,11,13,1); int pin[3] = {A0, A4, A3}; byte value; byte value_his = 0; int time[3];//用于记录传感器的触发时间 void setup() { LedInit(); myservo[0].attach(4); myservo[1].attach(3); } void loop() { value = 0; for(int i=0; i<3; i++){ value |= (digitalRead(pin[i]) << i); if(!digitalRead(pin[i])){ time[i] = millis();//调用mills函数可以得到此时单片机的运行时间 } } if(TimeDeal()) { if(millis() > 1000){//用于排除刚开机时的误判 LedDis();//十字路口显示 } } if(value == 0x07){//当传感器都没有触发时默认为上一次的值 value = value_his; } switch (value) { case 0x00://全部触发 Forwards(); break; case 0x01://触发右边两个 while(digitalRead(pin[1])){ Right(); } break; case 0x03://触发右边一个 while(digitalRead(pin[1])){ Right(); } break; case 0x04://触发左边两个 while(digitalRead(pin[1])){ Left(); } break; case 0x05://触发中间一个 Forwards(); break; case 0x06://触发左边一个 while(digitalRead(pin[1])){ Left(); } break; default: stop(); } value_his = value; lc.clearDisplay(0); } void Left() { myservo[0].write(middle1); myservo[1].write(middle2 + 20); } void Right() { myservo[0].write(middle1 - 20); myservo[1].write(middle2); } void Forwards() { myservo[0].write(middle1 - 20); myservo[1].write(middle2 + 20); } void stop() { myservo[0].write(middle1); myservo[1].write(middle2); } bool TimeDeal()//十字路口识别函数 { if(millis() > 500){ if((abs(time[1] - time[0]) < 100) && (abs(time[1] - time[2]) < 100)){//当中间传感器与另外两个传感器触发的时间小于100毫秒时判定为十字路口 return true; } else return false; } } void LedDis()//十字路口显示函数 { for(int i=3; i<5; i++){ for(int j=0; j<8; j++){ lc.setLed(0, i, j, true); } } for(int i=3; i<5; i++){ for(int j=0; j<8; j++){ lc.setLed(0, j, i, true); } } } void LedInit() { lc.shutdown(0,false); //start the 8*8 led lc.setIntensity(0,8); lc.clearDisplay(0); } |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-07-14 https://www.robotway.com/ ------------------------------*/ #include <LedControl.h> #include <Servo.h> #define middle1 88 #define middle2 88 LedControl lc=LedControl(12,11,13,1); Servo myservo[2]; int pin[3] = {A0, A4, A3}; int time[3]; byte value; byte value_his = 0; int flag = 0; int times = 0; void setup() { LedInit(); Serial.begin(9600);//串口,用于调试 myservo[0].attach(4); myservo[1].attach(3); } void loop() { value = 0; for(int i=0; i<3; i++){ value |= (digitalRead(pin[i]) << i); if(!digitalRead(pin[i])){ time[i] = millis(); } } if(TimeDeal()) { times++; Serial.print(times); } else { if(times > 1){ Serial.println(); flag += 1; Serial.println(flag); } times = 0; } if(flag == 3){ while(1){ stop(); } } if(value == 0x07){ value = value_his; } switch (value) { case 0x00://全部触发 LedOn(0); LedOn(1); LedOn(2); Forwards(); //delay(500); break; case 0x01://触发右边两个 LedOn(1); LedOn(2); while(digitalRead(pin[1])){ Right(); } break; case 0x03://触发右边一个 LedOn(2); while(digitalRead(pin[1])){ Right(); } break; case 0x04://触发左边两个 LedOn(0); LedOn(1); while(digitalRead(pin[1])){ Left(); } break; case 0x05://触发中间一个 LedOn(1); Forwards(); break; case 0x06://触发左边一个 LedOn(0); while(digitalRead(pin[1])){ Left(); } break; default: stop(); } value_his = value; lc.clearDisplay(0); } bool TimeDeal() { if(millis() > 500){ if((abs(time[1] - time[0]) < 100) && (abs(time[1] - time[2]) < 100)){ return true; } else return false; } } void Left() { myservo[0].write(middle1); myservo[1].write(middle2 + 20); } void Right() { myservo[0].write(middle1 - 20); myservo[1].write(middle2); } void Forwards() { myservo[0].write(middle1 - 20); myservo[1].write(middle2 + 20); } void stop() { myservo[0].write(middle1); myservo[1].write(middle2); } void LedOn(int key) { for(int i=0; i<2; i++){ for(int j=3*key; j<3*key+2; j++){ lc.setLed(0, i, j, true); } } } void LedInit() { lc.shutdown(0,false); //start the 8*8 led lc.setIntensity(0,8); lc.clearDisplay(0); } |
按照下图所示方式进行电路连接:
4. 功能实现
编程环境:Arduino 1.8.19
① 下面提供一个控制轮子转动方向和速度的参考例程(Test1.ino):
② 下面提供一个控制轮子前进、停止、左转、右转、左微调、右微调的参考例程(Test2.ino):
③ 下面提供一个将灰度传感器数据显示到LED点阵上的参考例程(Test3.ino):
④ 下面提供一个小车行走直线的参考例程(Test4.ino):
⑤ 下面提供一个小车识别十字路口的参考例程(Test5.ino):
⑥ 下面提供一个小车实现寻迹的完整程序(TrackingCar.ino):
5. 资料清单
序号 | 内容 |
1 | 程序源代码 |
【整体打包】-【R023】小型双轮差速底盘-寻迹与路口-资料附件.zip | 16.25KB | 下载2次 | 下载 |
灭火
1. 功能说明
灭火机器人是一种特殊的机器人,专门用于进行火灾扑救和灭火任务。它们通常具备以下功能和特点:
① 火灾侦测:灭火机器人配备了各种传感器和探测设备,可以检测烟雾、温度升高等火灾迹象。
② 火灾扑救:灭火机器人可携带水枪或干粉喷射器等扑救工具,能够快速响应并灭除小型火灾或控制火势。
③ 自主导航:灭火机器人通常拥有自主导航和避障系统,能够在火灾现场进行移动和操作,同时规避障碍物。
④ 远程操作:由于火灾环境通常危险且恶劣,灭火机器人通常可以通过遥控或预设指令来进行操作,使人员可以在安全区域远程操控。
⑤ 视频监控:一些灭火机器人还配备了摄像头或红外传感器,能够实时传输图像或视频给指挥中心,以辅助决策和情报收集。
⑥ 多种应用场景:灭火机器人广泛应用于各类场所,如工厂、仓库、地下车库、建筑物、船舶等,帮助人们更有效地对抗火灾。
本文示例将实现当R023样机小型双轮差速底盘机器人检测到火源时进行灭火的一个功能。
2. 结构装配
按照下图所示方式进行安装:
3. 电子硬件
在这个示例中,我们采用了以下硬件,请大家参考:
主控板 | |
扩展板 | |
传感器 | 超声测距模块 |
火焰传感器 | |
电池 | 7.4V锂电池 |
其它 | 风扇、马达驱动 |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-07-14 https://www.robotway.com/ ------------------------------*/ #include <Servo.h>//调用舵机库函数 #define middle1 105//定义对应舵机的中间值,即停止转动的值 #define middle2 89//此值需要测量,各个舵机不一定相同 Servo myservo[2];//声明舵机类数组,用于控制的两个舵机 void setup() { myservo[0].attach(4);//绑定舵机的输出引脚 myservo[1].attach(3); } void loop() { Left();//调用左转函数 delay(1000);//延时函数 Right(); delay(1000); Forwards(); delay(1000); stop(); delay(1000); } void Left()//左转函数 { myservo[0].write(middle1);//输出相应的舵机参数,使舵机运行 myservo[1].write(middle2 + 20); } void Right()//右转函数 { myservo[0].write(middle1 - 20); myservo[1].write(middle2); } void Forwards()//前进函数 { myservo[0].write(middle1 - 20); myservo[1].write(middle2 + 20); } void stop()//后退函数 { myservo[0].write(middle1); myservo[1].write(middle2); } |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-07-14 https://www.robotway.com/ ------------------------------*/ void setup() { pinMode(5, OUTPUT);//设置引脚5的功能为输出 } void loop() { WindStart(); delay(1000); WindStop(); delay(1000); } void WindStart()//风扇旋转函数 { digitalWrite(5, HIGH);//置高引脚5的输出电平 } void WindStop()//风扇旋转停止函数 { digitalWrite(5, LOW);//置低引脚5的输出电平 } |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-07-14 https://www.robotway.com/ ------------------------------*/ #define ECHOPIN A3//使用宏定义对超声波模块连接的引脚进行定义 #define TRIGPIN 2 void setup() { Serial.begin(9600);//打开串口 pinMode(ECHOPIN, INPUT); pinMode(TRIGPIN, OUTPUT); } void loop() { Serial.println(Distance());//串口打印距离 delay(500); } int Distance()//超声波距离测量函数,返回测量的距离 { digitalWrite(TRIGPIN, LOW); delayMicroseconds(2); digitalWrite(TRIGPIN, HIGH); delayMicroseconds(10); digitalWrite(TRIGPIN, LOW); int distance = pulseIn(ECHOPIN, HIGH); distance= distance/58; return distance; } |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-07-14 https://www.robotway.com/ ------------------------------*/ int pin[3] = {A0, A2, A4};//定义火焰传感器的引脚,此处用到了三个 int value[3]; //用于存储传感器的变量值 void setup() { SerialUSB.begin(9600);//打开串口 } void loop() { for(int i=0; i<3; i++){//通过循环扫描读取传感器的值 value[i] = analogRead(pin[i]);//analogRead()用于读取传感器的AD值 } SerialUSB.println(ValueDeal());//串口打印传感器的触发状态 delay(500); } int ValueDeal()//火焰传感器识别函数,根据返回值的不同可以判断传感器的触发状态 { if(value[0] < 900 || value[1] < 900 || value[2] < 900){ if(value[0] < value[1]){ if(value [0] < value[2]){ return 0;//A0引脚连接的传感器触发 } else return 2;//A4引脚连接的传感器触发 } else { if(value[1] < value[2]){ return 1;//A2引脚连接的传感器触发 } else return 2; } } return 3;//无触发 } |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-07-14 https://www.robotway.com/ ------------------------------*/ #include <Servo.h> #define ECHOPIN A3 #define TRIGPIN 2 #define middle1 105 #define middle2 89 int pin[3] = {A0, A2, A4}; int value[3]; Servo myservo[2]; void setup() { SerialUSB.begin(9600); pinMode(ECHOPIN, INPUT); pinMode(TRIGPIN, OUTPUT); myservo[0].attach(4); myservo[1].attach(3); pinMode(5, OUTPUT); } void loop() { for(int i=0; i<3; i++){ value[i] = analogRead(pin[i]); } int distance = Distance(); SerialUSB.println(distance); switch (ValueDeal()) { case 0: Left(); break; case 1: Forward(); if(distance < 10){ while(analogRead(pin[1]) < 900){ Stop(); WindStart(); delay(2000); } } break; case 2: Right(); break; case 3: if(distance < 10){ Back(); delay(1000); Left(); delay(500); } else Forward(); break; default: Stop(); } WindStop(); } int ValueDeal() { if(value[0] < 900 || value[1] < 900 || value[2] < 900){ if(value[0] < value[1]){ if(value [0] < value[2]){ return 0; } else return 2; } else { if(value[1] < value[2]){ return 1; } else return 2; } } return 3; } int Distance() { digitalWrite(TRIGPIN, LOW); delayMicroseconds(2); digitalWrite(TRIGPIN, HIGH); delayMicroseconds(10); digitalWrite(TRIGPIN, LOW); int distance = pulseIn(ECHOPIN, HIGH, 14705); distance = (int)(distance/58); return distance; } void WindStart() { digitalWrite(5, HIGH); } void WindStop() { digitalWrite(5, LOW); } void Forward() { myservo[0].write(middle1 - 30); myservo[1].write(middle2 + 30); } void Back() { myservo[0].write(middle1 + 20); myservo[1].write(middle2 - 20); } void Left() { myservo[0].write(middle1); myservo[1].write(middle2 + 30); } void Right() { myservo[0].write(middle1 - 30); myservo[1].write(middle2); } void Stop() { myservo[0].write(middle1); myservo[1].write(middle2); } |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-07-14 https://www.robotway.com/ ------------------------------*/ #include <Servo.h> #define ECHOPIN A3 #define TRIGPIN 2 #define middle1 105 #define middle2 89 int pin[3] = {A0, A2, A4}; int value[3]; Servo myservo[2]; void setup() { pinMode(ECHOPIN, INPUT); pinMode(TRIGPIN, OUTPUT); myservo[0].attach(4); myservo[1].attach(3); pinMode(5, OUTPUT); } void loop() { for(int i=0; i<3; i++){ value[i] = analogRead(pin[i]); } int distance = Distance(); switch (ValueDeal()) {//根据传感器处理函数的返回值进行相应的处理 case 0: Left(); break; case 1: Forward(); break; case 2: Right(); break; case 3: if(distance < 10){//当小车距离障碍10CM时进行避障处理 Back(); delay(1000); Left(); delay(500); } else Forward(); break; default: Stop(); } } int ValueDeal() { if(value[0] < 900 || value[1] < 900 || value[2] < 900){ if(value[0] < value[1]){ if(value [0] < value[2]){ return 0; } else return 2; } else { if(value[1] < value[2]){ return 1; } else return 2; } } return 3; } int Distance() { digitalWrite(TRIGPIN, LOW); delayMicroseconds(2); digitalWrite(TRIGPIN, HIGH); delayMicroseconds(10); digitalWrite(TRIGPIN, LOW); int distance = pulseIn(ECHOPIN, HIGH, 14705); distance = (int)(distance/58); return distance; } void Forward() { myservo[0].write(middle1 - 30); myservo[1].write(middle2 + 30); } void Back() { myservo[0].write(middle1 + 20); myservo[1].write(middle2 - 20); } void Left() { myservo[0].write(middle1); myservo[1].write(middle2 + 30); } void Right() { myservo[0].write(middle1 - 30); myservo[1].write(middle2); } void Stop() { myservo[0].write(middle1); myservo[1].write(middle2); } |
按照下图所示方式进行电路连接:
4. 功能实现
编程环境:Arduino 1.8.19
① 下面提供一个控制小车前后左右停的参考例程(Test1.ino):
② 下面提供一个控制风扇转动与停止的参考例程(Test2.ino):
③ 下面提供一个超声波模块测试距离的参考例程(Test3.ino):
④ 下面提供一个识别火焰的参考例程(Test4.ino):
⑤ 下面提供一个小车在行进间进行避障的参考例程(Test5.ino):
⑥ 下面提供一个实现灭火的完整程序(FireOffCar.ino):
5. 资料下载
序号 | 内容 |
1 | 程序源代码 |
【整体打包】-【R023】小型双轮差速底盘-灭火-资料附件.zip | 14.76KB | 下载2次 | 下载 |
红外跟随
1. 功能说明
本文示例将实现R023样机小型双轮差速底盘跟随人移动的功能。在小型双轮差速底盘前方按下图所示安装3个 近红外传感器,制作一个红外线发射源,实现当红外发射源在机器人的检测范围内任意放置或移动时,机器人能追踪该发射源。
2. 电子硬件
在这个示例中,我们采用了以下硬件,请大家参考:
主控板 | |
扩展板 | |
传感器 | |
电池 | 7.4V锂电池 |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-07-20 https://www.robotway.com/ ------------------------------*/ void Right(); void Left(); void Stop(); void Back(); void Forward(); void setup() { pinMode( 17, INPUT); pinMode( 18, INPUT); pinMode( 14, INPUT); pinMode( 10, OUTPUT); pinMode( 6, OUTPUT); pinMode( 5, OUTPUT); pinMode( 9, OUTPUT); } void loop() { if (!( digitalRead(18) )) { Stop(); } if (!( digitalRead(14) )) { Right(); } if (!( digitalRead(17) )) { Left(); } if (digitalRead(18)) { Forward(); } } void Left() { analogWrite(5 , 0); analogWrite(6 , 0); analogWrite(9 , 0); analogWrite(10 , 100); } void Stop() { analogWrite(5 , 0); analogWrite(6 , 0); analogWrite(9 , 0); analogWrite(10 , 0); } void Forward() { analogWrite(5 , 0); analogWrite(6 , 125); analogWrite(9 , 0); analogWrite(10 , 100); } void Back() { analogWrite(5 , 125); analogWrite(6 , 0); analogWrite(9 , 100); analogWrite(10 , 0); } void Right() { analogWrite(5 , 0); analogWrite(6 , 125); analogWrite(9 , 0); analogWrite(10 , 0); } |
电路连接:
① 三个近红外传感器从左至右分别连接在Bigfish扩展板的A0,A4,A3端口。
② 左轮直流电机连在Bigfish扩展板的D9,D10接口;右轮直流电机连在Bigfish扩展板的D5,D6接口。
3. 功能实现
编程环境:Arduino 1.8.19
下面提供一个小型双轮差速底盘跟随人移动的参考例程(Infrareda_following.ino):
4. 资料清单
序号 | 内容 |
1 | R023-红外跟随-程序源代码 |
【整体打包】-【R023】小型双轮差速底盘-红外跟随-资料附件.zip | 2.84KB | 下载5次 | 下载 |
悬崖巡检
2. 电子硬件
在这个示例中,我们采用了以下硬件,请大家参考:
主控板 | |
扩展板 | |
传感器 | |
电池 | 7.4V锂电池 |
电路连接:
① 近红外传感器连接在Bigfish扩展板的A0端口;灰度传感器连接在Bigfish扩展板的A4端口。
② 2个直流电机分别连在Bigfish扩展板的9,10接口和5,6接口。
3. 功能实现
编程环境:Arduino 1.8.19
下面提供一个小型双轮差速底盘悬崖巡检的参考例程(Cliff_avoidance_robot.ino):
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-07-19 https://www.robotway.com/ ------------------------------*/ /************************************************************************************************************************************************* 实验需求: 实现悬崖巡检机器人 实现思路: 程序的整体思路为:用近红外来检测停放小车的桌面,用灰度来检测悬崖。 当近红外持续触发时,说明小车在桌面,小车保持前进,如果灰度传感器触发,说明小车遇到悬崖, 小车先后退350毫秒,在左转350毫秒远离悬崖。如果近红外没有触发,说明小车也检测到了悬崖。否则,小车前进。 *************************************************************************************************************************************************/ /****************************************************************** 实验接线: 近红外传感器接到A0(即14引脚); 灰度传感器接到A4(即18引脚); 直流电机1接5,6引脚; 直流电机2接9,10引脚; ******************************************************************/ int _ABVAR_1_Near_infrared_sensor = 0 ; int _ABVAR_2_Grayscale_sensor = 0 ; boolean __ardublockDigitalRead(int pinNumber) { pinMode(pinNumber, INPUT); return digitalRead(pinNumber); } void forward(); void back(); void Cliff(); void left(); void setup() { pinMode( 10, OUTPUT); pinMode( 6, OUTPUT); pinMode( 5, OUTPUT); pinMode( 9, OUTPUT); _ABVAR_1_Near_infrared_sensor = 14 ; _ABVAR_2_Grayscale_sensor = 18 ; } void loop() { if (__ardublockDigitalRead(_ABVAR_1_Near_infrared_sensor)) { Cliff(); //如果近红外没有触发,说明小车检测到了悬崖。 } if (!( __ardublockDigitalRead(_ABVAR_2_Grayscale_sensor) )) { Cliff(); //如果灰度传感器触发,说明小车遇到悬崖,小车先后退350毫秒,在左转350毫秒远离悬崖。 } else { forward(); } } void back() { analogWrite(5 , 0); analogWrite(6 , 100); analogWrite(9 , 0); analogWrite(10 , 100); } void forward() { analogWrite(5 , 100); analogWrite(6 , 0); analogWrite(9 , 80); analogWrite(10 , 0); } void Cliff() { back(); delay( 350 ); left(); delay( 350 ); } void left() { analogWrite(5 , 100); analogWrite(6 , 0); analogWrite(9 , 0); analogWrite(10 , 100); } |
4. 资料清单
序号 | 内容 |
1 | 【R023】-悬崖巡检-程序源代码 |
【整体打包】-【R023】小型双轮差速底盘-悬崖巡检-资料附件.zip | 3.4KB | 下载2次 | 下载 |
无线遥控
2. 电子硬件
在这个示例中,我们采用了以下硬件,请大家参考:
主控板 | |
扩展板 | Bigfish2.1扩展板 x1 |
Birdmen手柄扩展板 x1 | |
通信 | NRF无线通信模块 x2 |
电池 | 7.4V锂电池 |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-07-21 https://www.robotway.com/ ------------------------------*/ int _ABVAR_1_A0Value = 0 ; int _ABVAR_2_A1Value = 0 ; void ADGet(); void SerialPrint(); void setup() { Serial.begin(9600); } void loop() { ADGet(); SerialPrint(); delay( 100 ); } void SerialPrint() { if (( ( ( _ABVAR_1_A0Value ) == ( 0 ) ) && ( ( _ABVAR_2_A1Value ) == ( 1 ) ) )) { Serial.print("1"); Serial.println(); } if (( ( ( _ABVAR_1_A0Value ) == ( 1 ) ) && ( ( _ABVAR_2_A1Value ) == ( 0 ) ) )) { Serial.print("2"); Serial.println(); } if (( ( ( _ABVAR_1_A0Value ) == ( 1 ) ) && ( ( _ABVAR_2_A1Value ) == ( 1 ) ) )) { Serial.print("3"); Serial.println(); } if (( ( ( _ABVAR_1_A0Value ) == ( 1 ) ) && ( ( _ABVAR_2_A1Value ) == ( 2 ) ) )) { Serial.print("4"); Serial.println(); } if (( ( ( _ABVAR_1_A0Value ) == ( 2 ) ) && ( ( _ABVAR_2_A1Value ) == ( 1 ) ) )) { Serial.print("5"); Serial.println(); } } void ADGet() { _ABVAR_1_A0Value = analogRead(14) ; _ABVAR_2_A1Value = analogRead(15) ; _ABVAR_1_A0Value = map ( _ABVAR_1_A0Value , 0 , 1024 , 0 , 3 ) ; _ABVAR_2_A1Value = map ( _ABVAR_2_A1Value , 0 , 1024 , 0 , 3 ) ; } |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-07-21 https://www.robotway.com/ ------------------------------*/ int _ABVAR_1__data = 0 ; int _ABVAR_2__data_his = 0 ; int _ABVAR_3_0 = 0 ;
void Right(); void Select(); void Left(); void Stop(); void Forward(); void Back();
void setup() { Serial.begin(9600); pinMode( 5 , OUTPUT); pinMode( 6 , OUTPUT); pinMode( 9 , OUTPUT); pinMode( 10 , OUTPUT); _ABVAR_1__data = 3 ;
_ABVAR_2__data_his = 3 ;
}
void loop() { _ABVAR_1__data = Serial.parseInt() ; if (( ( _ABVAR_1__data ) > ( 0 ) )) { Select(); } else { _ABVAR_1__data = _ABVAR_2__data_his ; Select(); } _ABVAR_2__data_his = _ABVAR_1__data ; _ABVAR_1__data = _ABVAR_3_0 ; delay( 50 ); }
void Left() { digitalWrite( 5 , LOW ); digitalWrite( 6 , HIGH ); digitalWrite( 9 , HIGH ); digitalWrite( 10 , LOW ); }
void Stop() { digitalWrite( 5 , LOW ); digitalWrite( 6 , LOW ); digitalWrite( 9 , LOW ); digitalWrite( 10 , LOW ); }
void Back() { digitalWrite( 5 , LOW ); digitalWrite( 6 , HIGH ); digitalWrite( 9 , LOW ); digitalWrite( 10 , HIGH ); }
void Forward() { digitalWrite( 5 , HIGH ); digitalWrite( 6 , LOW ); digitalWrite( 9 , HIGH ); digitalWrite( 10 , LOW ); }
void Select() { if (( ( _ABVAR_1__data ) == ( 1 ) )) { Forward(); } if (( ( _ABVAR_1__data ) == ( 2 ) )) { Back(); } if (( ( _ABVAR_1__data ) == ( 3 ) )) { Stop(); } if (( ( _ABVAR_1__data ) == ( 4 ) )) { Left(); } if (( ( _ABVAR_1__data ) == ( 5 ) )) { Right(); } }
void Right() { digitalWrite( 5 , HIGH ); digitalWrite( 6 , LOW ); digitalWrite( 9 , LOW ); digitalWrite( 10 , HIGH ); } |
3. 功能实现
编程环境:Arduino 1.8.19
3.1下载固件
分别下载master_.ino和slave_.ino到两个Basra主控板(软件解析:master_.ino为上位机程序,slave_.ino为下位机程序)。
注意:先不要堆叠NRF无线通信模块,因为会占用串口,造成下载失败。
① 参考例程(master_.ino):
② 参考例程(slave_.ino):
3.2 硬件连接
① 将Birdmen手柄扩展板堆叠到下载了master_.ino的Basra主控板上,然后堆叠上NRF无线通信模块。
② 将Bigfish扩展板堆叠到下载了slave_.ino的Basra主控板上,再堆叠上NRF无线通信模块。
③ 将小型双轮差速底盘的直流电机连到Bigfish扩展板的电机接口上。
④ 用master右侧的摇杆遥控slave,调整电机的接线,直到摇杆动作和小车动作匹配。
4. 资料清单
序号 | 内容 |
1 | 程序源代码 |
【整体打包】-【R023】小型双轮差速底盘-无线遥控-资料附件.rar | 3.8KB | 下载2次 | 下载 |
1. 功能说明
本文示例所实现的功能为:用手机APP遥控R023样机小型双轮差速底盘实现前进和后退。
2. 电子硬件
在这个示例中,我们采用了以下硬件,请大家参考:
主控板 | |
扩展板 | |
通信 | |
电池 | 7.4V锂电池 |
其它 | 安卓手机 |
/*------------------------------------------------------------------------------------ 版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved. Distributed under MIT license.See file LICENSE for detail or copy at https://opensource.org/licenses/MIT by 机器谱 2023-07-21 https://www.robotway.com/ ------------------------------*/ int _ABVAR_1_data = 0 ;
void setup() { pinMode( 10, OUTPUT); pinMode( 6, OUTPUT); pinMode( 5, OUTPUT); pinMode( 9, OUTPUT); Serial.begin(9600); }
void loop() { _ABVAR_1_data = Serial.parseInt() ; if (( ( _ABVAR_1_data ) == ( 1 ) )) { analogWrite(9 , 255); analogWrite(10 , 0); analogWrite(5 , 255); analogWrite(6 , 0); } if (( ( _ABVAR_1_data ) == ( 2 ) )) { analogWrite(9 , 0); analogWrite(10 , 255); analogWrite(5 , 0); analogWrite(6 , 255); } if (( ( _ABVAR_1_data ) == ( 0 ) )) { analogWrite(9 , 0); analogWrite(10 , 0); analogWrite(5 , 0); analogWrite(6 , 0); } } |
电路连接:
① 将蓝牙串口模块按下图所示方式安装在Bigfish扩展板上。
② 左轮直流电机连在Bigfish扩展板的D5,D6接口;右轮直流电机连在Bigfish扩展板的D9,D10接口。
3.2 示例程序
编程环境:Arduino 1.8.19
下面提供一个用手机APP遥控小型双轮差速底盘前进和后退的参考例程(lanyamokuai.ino):
4. 资料清单
序号 | 内容 |
1 | 程序源代码 |
2 | 蓝牙串口助手 |
【整体打包】-【R023】小型双轮差速底盘-Android蓝牙通信-资料附件.rar | 57.39KB | 下载7次 | 下载 |
|