26、激光束轮廓监测与寻火机器人的搭建与实现

激光束轮廓监测与寻火机器人的搭建与实现

激光束轮廓监测

实验原理与硬件搭建

为了测量激光束的横向轮廓,我们采用了一种通过移动障碍物穿过激光束,并观察光敏电阻信号变化的方法。具体硬件搭建如下:
- 激光模块 :使用传感器套件中的激光模块,安装在面包板上,引出两根线,分别用于接地和正电源电压。通过对正电源电压进行脉冲宽度调制来调节激光强度。
- 传感器 :由光敏电阻(LDR)和 10 kΩ 电阻组成的分压器。
- 障碍物 :采用一块黑色塑料。
- 平移台 :利用旧 CD - ROM 驱动器的框架,通过步进电机驱动一个小车在约 40 mm 的距离内来回移动,步进电机驱动主轴,精确地推拉小车。
- 控制器 :使用 Arduino UNO,通过 L293D H - 桥驱动器驱动电机,在引脚 D9 上使用脉冲宽度调制来控制激光功率,并从模拟输入 A0 读取分压器与 LDR 的信号。

代码实现

以下是实现激光束轮廓监测的代码:

// Laser profile measurement, V. Ziemann, 170628
char line[30];
int settle_time=2, stepcounter=0;
int laser_power=10;
const int PA=2,PB=3,PC=4,PD=5,ENABLE=6;
const int LASER=9;
void set_coils_fullstep(int istep) { //.........set_coils
    bool patA[]={1,1,0,0};
    int pat_length=4;
    int ii;
    istep=istep % pat_length;
    if (istep < 0) istep+=pat_length;
    digitalWrite(PA,patA[istep]);
    ii=(istep+2) % pat_length;
    digitalWrite(PB,patA[ii]);
    ii=(istep+3) % pat_length;
    digitalWrite(PC,patA[ii]);
    ii=(istep+1) % pat_length;
    digitalWrite(PD,patA[ii]);
    delay(settle_time);
}
void setup() {
    //...............................setup
    Serial.begin (9600);
    while (!Serial) {;}
    pinMode(PA,OUTPUT);
    pinMode(PB,OUTPUT);
    pinMode(PC,OUTPUT);
    pinMode(PD,OUTPUT);
    pinMode(ENABLE,OUTPUT);
    digitalWrite(ENABLE,HIGH);
    analogWrite(LASER,laser_power);
}
void loop() {
    //................................loop
    if (Serial.available()) {
        Serial.readStringUntil('\n').toCharArray(line,30);
        if (strstr(line,"FMOVE ")==line) {
            int steps=(int)atof(&line[6]);
            digitalWrite(ENABLE,HIGH);
            if (steps > 0) {
                for (int i=0;i<steps;i++) set_coils_fullstep(stepcounter++);
            } else {
                for (int i=0;i<abs(steps);i++) set_coils_fullstep(stepcounter--);
            }
        } else if (strstr(line,"STEPS?")==line) {
            Serial.print("STEPS "); Serial.println(stepcounter);
        } else if (strstr(line,"STEPS ")==line) {
            stepcounter=(int)atof(&line[6]);
        } else if (strstr(line,"WAIT?")==line) {
            Serial.print("WAIT "); Serial.println(settle_time);
        } else if (strstr(line,"WAIT ")==line) {
            settle_time=(int)atof(&line[5]);
        } else if (strstr(line,"DISABLE")==line) {
            digitalWrite(ENABLE,LOW);
        } else if (strstr(line,"ENABLE")==line) {
            digitalWrite(ENABLE,HIGH);
        } else if (strstr(line,"LDR?")==line) {
            Serial.print("LDR "); Serial.println(analogRead(A0));
        } else if (strstr(line,"POWER?")==line) {
            Serial.print("POWER "); Serial.println(laser_power);
        } else if (strstr(line,"POWER ")==line) {
            laser_power=(int)atof(&line[6]);
            Serial.print("POWER "); Serial.println(laser_power);
            analogWrite(LASER,laser_power);
        } else if (strstr(line,"FSCAN")==line) {
            int steps=(int)atof(&line[6]);
            digitalWrite(ENABLE,HIGH);
            delay(100);
            for (int i=0;i<abs(steps);i++) set_coils_fullstep(stepcounter++);
            delay(100);
            for (int i=0;i<abs(steps);i++) {
                set_coils_fullstep(stepcounter--);
                delay(50);
                unsigned long sum=0;
                for (int k=1; k<10; k++) {sum+=analogRead(A0); delay(10);}
                Serial.println(sum);
            }
            digitalWrite(ENABLE,LOW);
        } else if (strstr(line,"CALIBRATE")==line) {
            for (int power=0; power<256; power++) {
                analogWrite(LASER,power);
                delay(100);
                unsigned long sum=0;
                for (int k=1; k<10; k++) {sum+=analogRead(A0); delay(10);}
                Serial.println(sum);
            }
            analogWrite(LASER,10);
        } else {
            Serial.println("unknown");
        }
    }
}

代码解释

  • 变量声明 :在代码开头声明了一些变量,用于步进电机步骤之间的等待时间和激光功率的脉冲宽度调制初始设置。
  • set_coils_fullstep 函数 :用于设置步进电机的线圈状态,采用全步模式,避免了半步模式中因交替步骤中不同电流引起的传感器干扰。
  • setup 函数 :初始化串行通信,声明电机使用的引脚模式,并将激光功率初始化为 10(最大值为 255)。
  • loop 函数 :期望在串行线上接收单行命令,根据不同的命令执行相应的操作,如移动电机、查询步进数、设置等待时间、读取传感器值、设置激光功率等。

测试与自动化

初始测试
  • 打开 Arduino IDE 中的串行监视器,确保波特率设置为 9600。
  • 发送 LDR? 查询 LDR 的值,发送 POWER? 查询激光功率,并使用 POWER nnn (nnn 为 0 到 255 之间的值)设置新的激光功率。
  • 使用 FMOVE 20 移动步进电机 20 步,使用 FMOVE -20 使其回到起始位置。
  • 执行 FSCAN 60 ,移动障碍物 60 步,然后逐步返回起始位置,同时读取传感器并将值报告到串行线。
自动化处理

如果初始测试完成得令人满意,我们可以使用 Octave 实现自动化处理。以下是 Octave 脚本:

% scanplot2.m, V. Ziemann, 221103
close all; clear all
xscale=0.161; % mm/fullstep
s=serialport('/dev/ttyACM0',9600); % set device correctly
pause(3);
flush(s); % flush the input queue
nsteps=60;
write(s,"FSCAN 60\n");
pause(5);
xx=zeros(1,nsteps); yy=xx;
for i=1:nsteps
    xx(i)=i*xscale;
    yy(i)=str2double(serialReadline(s));
end
clear s
yy=smooth3(yy); % smooth data
subplot(2,1,1);
plot(xx,yy); % raw sensor data
ylabel('arb. units');
subplot(2,1,2);
dy=yy(2:end)-yy(1:end-1); % derivative
if (yy(1) > yy(end)) dy=-dy; end
plot(xx(1:end-1),dy);
xlabel('x [mm]'); ylabel('arb. units');
title(['FWHM = ', num2str(xscale*fwhm(dy),"%5.2f"), ' mm'])
print('laser_profile.png','-S1000,700')

脚本解释

  • 首先对障碍物的运动进行校准,计算每步对应的毫米数 xscale
  • 打开串行线,发送 FSCAN 60 命令,等待一段时间后开始从串行线读取数据。
  • 对数据进行平滑处理,绘制原始传感器数据和激光束轮廓的导数,并计算并显示激光轮廓的半高宽(FWHM)。

流程图

graph TD;
    A[开始] --> B[初始化变量和硬件];
    B --> C[等待串行命令];
    C --> D{命令类型};
    D -- FMOVE --> E[移动步进电机];
    D -- STEPS? --> F[返回步进数];
    D -- WAIT? --> G[返回等待时间];
    D -- LDR? --> H[返回 LDR 值];
    D -- POWER? --> I[返回激光功率];
    D -- POWER --> J[设置激光功率];
    D -- FSCAN --> K[扫描并记录数据];
    D -- CALIBRATE --> L[校准传感器];
    D -- 其他 --> M[返回未知命令];
    E --> C;
    F --> C;
    G --> C;
    H --> C;
    I --> C;
    J --> C;
    K --> C;
    L --> C;
    M --> C;

寻火机器人

项目概述

我们要构建一个寻火机器人,它能够检测火源的热辐射,向热源移动,并在到达热源时开始发出蜂鸣声。该项目需要解决以下子任务:
1. 检测热源及其位置。
2. 控制电机的速度和方向。
3. 检测碰撞,最好在碰撞发生之前。
4. 感应遥控器上的操纵杆和开关。
5. 在遥控器和机器人之间发送消息。

硬件搭建

  • 底盘 :使用带有两个直流电机和板载电池组(五个 AA 电池,提供高达 7.5 V 电压)的现成底盘。
  • 控制器 :在底盘上安装一个全尺寸面包板,用于放置 NodeMCU 控制器、H - 桥电机驱动器和一些线性电压转换器,以提供 5 V 和 3.3 V 电压。添加一个 Arduino 作为 NodeMCU 的从设备,以提供额外的 IO 引脚。
  • 传感器 :在机器人底盘上的一个专用位置安装一个模型伺服器,在其上安装一个小面包板,用于放置 BPX38 IR 光电晶体管作为火焰传感器和 HC - SR04 距离传感器。

遥控器硬件

遥控器基于第二个 NodeMCU,连接到机器人上第一个 NodeMCU 所创建的 WLAN 网络和服务器。通过 SPI 将 MCP3208 8 通道 ADC 连接到 NodeMCU,并使用两个操纵杆、两个电位器、几个分压器和两个开关设置 ADC 通道的输入电压。

遥控器代码

// RCsenderUDP, V. Ziemann, 170705
const char* ssid = "FireBot";
const char* password = "..........";
const char* host = "192.168.4.1";
const int port=1137;
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
WiFiUDP client;
int adclast[8]={0,0,0,0,0,0,0,0},D4last=HIGH;
int adccalib[8];
//...................................................ADC
#include <SPI.h>
#define CS 15
int mcp3208_read_adc(uint8_t channel) {
    // 8 single ended
    int adcvalue=0, b1=0, hi=0, lo=0, reading;
    digitalWrite (CS, LOW);
    byte commandbits = B00001100; // Startbit+(single ended=1)
    commandbits |= ((channel>>1) & 0x03);
    SPI.transfer(commandbits);
    commandbits=(channel & 0x01) << 7;
    b1 = SPI.transfer(commandbits);
    hi = b1 & B00011111;
    lo = SPI.transfer(0x00); // input is don’t care
    digitalWrite(CS, HIGH);
    reading = (hi << 7) + (lo >> 1);
    return reading;
}
void send_string(char line[]) {
    //............send_string
    client.beginPacket(host,port);
    client.write(line);
    client.endPacket();
}
void setup() {
    //..................................setup
    pinMode(LED_BUILTIN,OUTPUT);
    digitalWrite(LED_BUILTIN,LOW);
    pinMode(CS,OUTPUT);
    digitalWrite(CS,HIGH);
    SPI.begin();
    SPI.setFrequency(100000);
    SPI.setBitOrder(MSBFIRST);
    SPI.setDataMode(SPI_MODE0);
    pinMode(LED_BUILTIN,OUTPUT);
    digitalWrite(LED_BUILTIN,LOW);
    Serial.begin(115200);
    pinMode(D3,INPUT_PULLUP);
    pinMode(D4,INPUT_PULLUP);
    delay(1000);
    WiFi.mode(WIFI_STA); // needed for reliable communication
    WiFi.begin(ssid,password);
    while (WiFi.status() != WL_CONNECTED) {
        Serial.print("."); delay(500);
    }
    Serial.print("\nConnected to ");
    Serial.print(ssid);
    Serial.print(" with IP address: ");
    Serial.println(WiFi.localIP());
    client.begin(port);
    for (int k=0;k<8;k++) {
        adccalib[k]=mcp3208_read_adc(k);
    }
    digitalWrite(LED_BUILTIN,HIGH);
}
void loop() {
    //..................................loop
    char line[30];
    int adc[8];
    for (int k=0;k<8;k++) {adc[k]=mcp3208_read_adc(k);}
    if ((abs(adc[0]-adclast[0]) > 16) || (abs(adc[1]-adclast[1]) > 16)) {
        adclast[0]=adc[0]; adclast[1]=adc[1];
        int val0=(adc[0]-adccalib[0])*1023.0/2048.0;
        int val1=(adc[1]-adccalib[1])*1023.0/2048.0;
        sprintf(line,"RSPEED %d",(int)(val0+0.5*val1)); send_string(line);
        sprintf(line,"LSPEED %d",(int)(val0-0.5*val1)); send_string(line);
    }
    if (abs(adc[5]-adclast[5]) > 16) {
        adclast[5]=adc[5];
        int val=adc[5]*180.0/4095;
        sprintf(line,"SERVO %d",val); send_string(line);
    }
    if (abs(adc[7]-adclast[7]) > 5) {
        // check the buttons
        adclast[7]=adc[7];
        if (adc[7] < 1000) {
            Serial.println("Red button right pressed");
            sprintf(line,"FINDFIRE 1"); send_string(line);
        } else if (adc[7] < 1700) {
            Serial.println("Blue button right pressed");
            sprintf(line,"RANGE?"); send_string(line);
        } else if (adc[7] < 2250) {
            Serial.println("Joystick button right pressed");
            sprintf(line,"NEXTEVENT 0"); send_string(line);
        } else if (adc[7] < 3580) {
            Serial.println("Joystick button left pressed");
            sprintf(line,"NEXTEVENT 1"); send_string(line);
        } else if (adc[7] < 3660) {
            Serial.println("Blue button left pressed");
            sprintf(line,"BEEP 1000"); send_string(line);
            Serial.println(line);
        } else if (adc[7] < 3750) {
            Serial.println("Red button left pressed");
            sprintf(line,"NEXTEVENT 3"); send_string(line);
        }
    }
    if (digitalRead(D4) != D4last) {
        D4last=digitalRead(D4);
        sprintf(line,"D0 %d",D4last); send_string(line);
    }
    yield();
    int packetsize=client.parsePacket();
    if (packetsize) {
        char line[30];
        int len=client.read(line,30); line[len]='\0';
        Serial.print("Message:"); Serial.println(line);
    }
    if (Serial.available()) {
        Serial.readStringUntil('\n').toCharArray(line,30);
        send_string(line);
    }
}

代码解释

  • 变量声明 :定义了 WLAN 名称、密码、机器人的 IP 地址和端口号,声明了 WiFi 库和客户端,以及用于存储 ADC 读数和校准值的变量。
  • mcp3208_read_adc 函数 :用于读取 ADC 的单个通道,返回 12 位 ADC 读数。
  • send_string 函数 :封装了 UDP 数据包的构建和发送。
  • setup 函数 :配置 IO 引脚,初始化 SPI 通信和串行线,连接到 WLAN 网络,连接到机器人上的服务器,并读取所有 ADC 通道的值进行校准。
  • loop 函数 :读取所有 ADC 通道的值,根据通道值的变化发送不同的命令到机器人,如设置电机速度、设置伺服器角度、检测按钮按下等。同时检查是否有来自机器人的消息,并将其复制到串行线。

机器人代码

// RCreceiver, V. Ziemann, 170701
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
const char *ap_ssid = "FireBot";
const char *ap_password = "..........";
const int port=1137;
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
WiFiUDP server;
#include <Servo.h>
Servo myServo;
int servo_pos=90,servo_inc=5;
int right_sensor=-1,left_sensor=-1;
long lasttime=0,sleeptime=1000,nextevent=1;
void send_string(char line[]) {
    //.......send_string
    server.beginPacket(server.remoteIP(),port);
    server.write(line);
    server.endPacket();
}
int range() { //..............................range
    digitalWrite(D1,LOW);
    delayMicroseconds(2);
    digitalWrite(D1,HIGH);
    delayMicroseconds(10);
    digitalWrite(D1,LOW);
    int val=(int)(0.017*pulseIn(D2,HIGH));
    if (val<10) tone(D8,1000,200);
    return val;
}
void motor_speed(int left, int right) {
    //...........motor_speed
    left=Max(-1023,Min(1023,left));
    analogWrite(D3,0);
    analogWrite(D4,0);
    if (left<0) {analogWrite(D3,abs(left));}
    else {analogWrite(D4,abs(left));}
    right=Max(-1023,Min(1023,right));
    analogWrite(D5,0);
    analogWrite(D6,0);
    if (right<0) {analogWrite(D5,abs(right));}
    else {analogWrite(D6,abs(right));}
}
void motor_stop() {
    //...................motor_stop
    analogWrite(D3,0);
    analogWrite(D4,0);
    analogWrite(D5,0);
    analogWrite(D6,0);
}
void setup() {
    //..............................setup
    pinMode(LED_BUILTIN,OUTPUT);
    digitalWrite(LED_BUILTIN,LOW);
    pinMode(D1,OUTPUT); // HCSR04-TRIG
    digitalWrite(D1,LOW);
    pinMode(D2,INPUT); // HCSR04-ECHO
    pinMode(D3,OUTPUT);
    pinMode(D4,OUTPUT);
    analogWrite(D3,0);
    analogWrite(D4,0);
    pinMode(D5,OUTPUT);
    pinMode(D6,OUTPUT);
    analogWrite(D5,0);
    analogWrite(D6,0);
    Serial.begin(38400);
    WiFi.softAP(ap_ssid,ap_password);
    IPAddress myIP = WiFi.softAPIP();
    server.begin(port);
    Serial.print("\nAccess point and server started at address: ");
    Serial.print(myIP); Serial.print(" and port: "); Serial.println(port);
    Serial.print("with SSID: "); Serial.println(ap_ssid);
    pinMode(D7,OUTPUT); // D7=Servo, D8=Tone
    myServo.attach(D7);
    digitalWrite(LED_BUILTIN,HIGH);
    tone(D8,880,500);
    lasttime=millis();
}
void loop() {
    //...................................loop
    char line[30];
    int packetsize=server.parsePacket();
    if (packetsize) {
        int len=server.read(line,30);
        line[len]='\0';
        if (strstr(line,"LSPEED ")==line) {
            int val=(int)atof(&line[7]);
            val=Max(-1023,Min(1023,val));
            analogWrite(D3,0);
            analogWrite(D4,0);
            if (val<0) {analogWrite(D3,abs(val));}
            else {analogWrite(D4,abs(val));}
        } else if (strstr(line,"RSPEED ")==line) {
            int val=(int)atof(&line[7]);
            val=Max(-1023,Min(1023,val));
            analogWrite(D5,0);
            analogWrite(D6,0);
            if (val<0) {analogWrite(D5,abs(val));}
            else {analogWrite(D6,abs(val));}
        } else if (strstr(line,":")==line) {
            line[len]='\n'; line[len+1]='\0';
            Serial.println(line);
        } else if (strstr(line,"D0 ")==line) {
            int val=(int)atof(&line[3]);
            if (val==0) {
                digitalWrite(LED_BUILTIN,HIGH);
            } else {
                digitalWrite(LED_BUILTIN,LOW);
            }
        } else if (strstr(line,"SERVO ")==line) {
            int val=(int)atof(&line[6]);
            myServo.write(val);
        } else if (strstr(line,"BEEP ")) {
            int val=(int)atof(&line[5]);
            Serial.print("BEEP val= "); Serial.println(val);
            tone(D8,440,val);
        } else if (strstr(line,"A0?")) {
            int val=analogRead(A0);
            Serial.print("A0 "); Serial.println(val);
            sprintf(line,"A0 %d",val); send_string(line);
        } else if (strstr(line,"RANGE?")) {
            int val=range();
            sprintf(line,"RANGE %d",val); send_string(line);
        } else if (strstr(line,"SCANRANGE ")==line) {
            int val=(int)atof(&line[10]);
            int minval=2000,minpos=-1;
            if (val>0) {
                myServo.write(10);
                delay(1000);
                for (int k=10;k<170;k+=5) {
                    myServo.write(k); delay(200);
                    val=range();
                    if (val<minval) { minval=val; minpos=k;}
                    sprintf(line,"SCANRANGE %d %d",k,val); send_string(line);
                }
                myServo.write(minpos);
                sprintf(line,"MINIMUM at %d",minpos); send_string(line);
            } else {
                myServo.write(90);
            }
        } else if (strstr(line,"FINDFIRE ")==line) {
            int val=(int)atof(&line[9]);
            int minval=2000,minpos=-1;
            if (val>0) {
                myServo.write(10);
                delay(1000);
                for (int k=10;k<170;k+=5) {
                    myServo.write(k); delay(200);
                    val=analogRead(A0);
                    if (val<minval) { minval=val; minpos=k;}
                    sprintf(line,"FINDFIRE %d %d",k,val); send_string(line);
                }
                myServo.write(minpos);
                sprintf(line,"MINIMUM at %d",minpos); send_string(line);
            } else {
                myServo.write(90);
            }
        } else if (strstr(line,"NEXTEVENT ")==line) {
            nextevent=(int)atof(&line[10]);
        } else if (strstr(line,"SLEEPTIME ")==line) {
            sleeptime=(int)atof(&line[10]);
        } else {
            Serial.println("unknown");
        }
    }
    yield();
    if (millis()>lasttime+sleeptime) {
        // next scheduled event
        switch (nextevent) {
            case 1:
                // determine range
                sprintf(line,"RANGE %d",range()); send_string(line);
                sprintf(line,"A0 %d",analogRead(A0)); send_string(line);
                nextevent=1;
                break;
            case 2:
                // scan with servo
                servo_pos+=servo_inc;
                if (servo_pos>170) {servo_inc=-servo_inc;}
                if (servo_pos<10) {servo_inc=-servo_inc;}
                myServo.write(servo_pos);
                nextevent=2;
            case 3:
                // request direction sensors
                if (range()<10) {
                    motor_stop();
                    nextevent=1;
                } else {
                    Serial.println(":A0?\n:A1?");
                    sleeptime=50; nextevent=4;
                }
                break;
            case 4:
                // read direction sensors and take action
                if ((right_sensor>0) && (left_sensor>0)) { // new data
                    if ((left_sensor<250) || (right_sensor<250)) {
                        sprintf(line,"SENSORS %d %d",left_sensor,right_sensor);
                        send_string(line);
                        int val0=(int)(600+0.2*(right_sensor-left_sensor));
                        val0=Max(-1023,Min(1023,val0));
                        int val1=(int)(600-0.2*(right_sensor-left_sensor));
                        val1=Max(-1023,Min(1023,val1));
                        motor_speed(val0,val1);
                    } else {
                        motor_stop();
                    }
                    right_sensor=-1; left_sensor=-1;
                    sleeptime=1000; nextevent=3;
                }
            default:
                break;
        }
        lasttime=millis();
    }
    yield();
    if (Serial.available()) {
        Serial.readStringUntil('\n').toCharArray(line,30);
        send_string(line);
        if (strstr(line,".A0 ")==line) {
            right_sensor=(int)atof(&line[3]);
        } else if (strstr(line,".A1 ")==line) {
            left_sensor=(int)atof(&line[3]);
        }
    }
}

代码解释

  • 变量和函数定义 :定义了 Max Min 函数,声明了 WLAN 信息、端口号、WiFi 库和服务器,以及用于控制伺服器、读取传感器和实现状态机的变量。还定义了 send_string range motor_speed motor_stop 等函数。
  • setup 函数 :配置 IO 引脚,初始化串行通信,创建 WLAN 接入点和服务器,连接伺服器,发出提示音,并记录当前时间。
  • loop 函数
    • 检查是否有来自遥控器的 UDP 数据包,根据不同的命令执行相应的操作,如设置电机速度、设置伺服器角度、读取传感器值等。
    • 实现一个简单的状态机,根据 nextevent 变量的值执行不同的操作,如检测距离、扫描伺服器、请求方向传感器数据、读取方向传感器数据并控制电机等。
    • 检查串行线是否有数据,根据数据更新传感器值。

从设备代码

// Slaveduino, V. Ziemann, 170723
void setup() { //.........................setup
    Serial.begin(38400);
    while (!Serial) {;}
    pinMode(13,OUTPUT);
    digitalWrite(13,LOW);
    pinMode(8,INPUT_PULLUP);
}
void loop() { //...........................loop
    char line[30];
    if (Serial.available()) {
        Serial.readStringUntil('\n').toCharArray(line,30);
        if (strstr(line,":A0?")==line) {
            Serial.print(".A0 "); Serial.println(analogRead(A0));
        } else if (strstr(line,":A1?")==line) {
            Serial.print(".A1 "); Serial.println(analogRead(A1));
        } else if (strstr(line,":D13 ")==line) {
            int val=(int)atof(&line[4]);
            if (val==0) {digitalWrite(13,LOW);} else {digitalWrite(13,HIGH);}
        } else if (strstr(line,":D8?")==line) {
            Serial.print(".D8 "); Serial.println(digitalRead(8));
        }
        delay(3);
    }
}

代码解释

  • setup 函数 :初始化串行通信,配置引脚模式。
  • loop 函数 :检查串行线是否有数据,根据不同的请求执行相应的操作,如读取模拟输入、控制引脚电平、读取数字输入等,并在回复中添加前缀 “.”。

流程图

graph TD;
    A[开始] --> B[初始化硬件和变量];
    B --> C[等待 UDP 数据包];
    C --> D{是否有数据包};
    D -- 是 --> E{命令类型};
    E -- LSPEED --> F[设置左电机速度];
    E -- RSPEED --> G[设置右电机速度];
    E -- SERVO --> H[设置伺服器角度];
    E -- BEEP --> I[发出蜂鸣声];
    E -- A0? --> J[返回 A0 传感器值];
    E -- RANGE? --> K[返回距离值];
    E -- SCANRANGE --> L[扫描距离并返回最小值位置];
    E -- FINDFIRE --> M[扫描火源并返回最小值位置];
    E -- NEXTEVENT --> N[设置下一个事件];
    E -- SLEEPTIME --> O[设置睡眠时间];
    E -- 其他 --> P[返回未知命令];
    F --> C;
    G --> C;
    H --> C;
    I --> C;
    J --> C;
    K --> C;
    L --> C;
    M --> C;
    N --> C;
    O --> C;
    P --> C;
    D -- 否 --> Q{是否到达下一个事件时间};
    Q -- 是 --> R{事件类型};
    R -- 1 --> S[检测距离和 A0 传感器值];
    R -- 2 --> T[扫描伺服器];
    R -- 3 --> U{是否有障碍物};
    U -- 是 --> V[停止电机并设置事件 1];
    U -- 否 --> W[请求方向传感器数据并设置事件 4];
    R -- 4 --> X{传感器数据是否有效};
    X -- 是 --> Y{是否检测到火源};
    Y -- 是 --> Z[发送传感器数据并控制电机];
    Y -- 否 --> AA[停止电机并设置事件 3];
    X -- 否 --> C;
    S --> C;
    T --> C;
    V --> C;
    W --> C;
    Z --> C;
    AA --> C;
    Q -- 否 --> C;

总结与展望

通过上述实验,我们成功搭建了激光束轮廓监测系统和寻火机器人。激光束轮廓监测系统可以帮助我们了解激光束的横向分布情况,而寻火机器人则展示了如何将传感器、执行器和控制器结合起来,实现一个具有特定功能的系统。然而,这两个系统都存在一些局限性,例如激光束轮廓监测系统可能受到障碍物边缘衍射的影响,寻火机器人的性能还不够理想,热源需要较近才能被检测到,运动也比较笨拙。未来可以对这些系统进行进一步的优化和改进,如采用更好的步进电机驱动器、增加更多的传感器、优化算法等,以提高系统的性能和可靠性。同时,还可以探索更多的应用场景,如将寻火机器人应用于火灾救援、工业安全监测等领域。

系统局限性与改进方向

激光束轮廓监测系统

局限性分析
  • 衍射影响 :障碍物边缘的衍射可能会对激光束的测量产生干扰,导致测量结果不准确。例如,当障碍物移动时,边缘的衍射效应可能会使传感器接收到的信号出现波动,从而影响对激光束轮廓的精确测量。
  • 步长问题 :步进电机的步长可能不够精细,无法满足更高精度的测量需求。如果步长过大,可能会错过激光束中的一些细节信息,导致测量结果不够准确。
  • 强度相关非线性 :传感器系统可能存在与激光强度相关的非线性问题,需要进行校准和修正。不同强度的激光可能会使传感器的响应产生偏差,从而影响测量结果的准确性。
改进建议
  • 优化障碍物设计 :可以考虑采用更光滑的障碍物边缘,或者使用特殊的材料来减少衍射的影响。例如,使用光学平面镜作为障碍物,以减少边缘的不规则性。
  • 更换步进电机驱动器 :采用具有微步进功能的电机驱动器,能够提供更精细的步长,从而提高测量的分辨率。
  • 进行强度校准 :利用 CALIBRATE 命令提供的基础信息,对传感器的非线性响应进行校准,以提高测量结果的准确性。

寻火机器人

局限性分析
  • 检测范围有限 :目前机器人对热源的检测范围较近,需要热源靠近才能检测到,这限制了其在实际应用中的效果。
  • 运动不够灵活 :机器人的运动较为笨拙,不能灵活地避开障碍物和准确地朝向热源移动,影响了其执行任务的效率。
  • 传感器数量和布局 :仅使用三个红外光电晶体管可能不足以准确确定热源的位置,且传感器的布局可能不够合理,导致信息获取不全面。
改进建议
  • 增加传感器数量和类型 :可以添加更多的红外传感器或其他类型的传感器,如热成像传感器,以扩大检测范围和提高检测精度。
  • 优化运动算法 :采用更先进的路径规划和避障算法,使机器人能够更灵活地移动,提高其在复杂环境中的适应性。
  • 改进传感器布局 :重新设计传感器的布局,使其能够更全面地覆盖机器人周围的区域,从而更准确地确定热源的位置。

拓展应用与项目思路

TCP 与 UDP 的选择

在寻火机器人的通信中,我们使用了 UDP 连接而不是 TCP 连接。TCP 连接使用广泛的握手协议,确保数据的可靠传输,但这也导致了响应速度较慢。而 UDP 连接不使用握手协议,响应速度更快,但可靠性较低。在实际应用中,需要根据具体需求来选择合适的通信协议。如果对数据的可靠性要求较高,如传输重要的控制指令,建议使用 TCP 连接;如果对响应速度要求较高,如实时传输传感器数据,UDP 连接可能更合适。

增加功能模块

气体传感器

可以添加一个 MQ - x 气体传感器,并编写一个独立的状态机(线程),定期读取传感器数据。当检测到有明显的气体存在时,发出特殊的警报。以下是一个简单的示例代码:

// 伪代码示例
#include <MQxSensor.h>

MQxSensor gasSensor;

void setup() {
    // 初始化气体传感器
    gasSensor.begin();
}

void loop() {
    int gasValue = gasSensor.read();
    if (gasValue > THRESHOLD) {
        // 发出特殊警报
        tone(D8, 1000, 1000);
    }
    delay(1000); // 每秒读取一次
}
LCD 显示屏

在遥控器上添加一个带有 I2C 接口的 LCD 显示屏,用于显示机器人的状态信息。可以使用 Wire 库来实现与显示屏的通信。以下是一个简单的示例代码:

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2); // 假设地址为 0x27,16x2 显示屏

void setup() {
    lcd.begin();
    lcd.backlight();
}

void loop() {
    // 假设从机器人接收到状态信息
    String status = "Robot is running";
    lcd.setCursor(0, 0);
    lcd.print(status);
    delay(1000);
}

其他项目思路

远程控制船

可以使用从计算机中回收的风扇作为动力源,构建一个远程控制船。通过一个模型伺服器控制螺旋桨后面的鳍片,来改变气流方向,从而实现船的转向。可以参考寻火机器人的遥控器和通信机制,实现对船的远程控制。

线跟随器

使用 LDR 或光电晶体管指向地面,编写程序使机器人能够跟随由胶带制成的白色(或黑色)线条。可以通过比较不同传感器的读数来确定机器人的位置,并调整电机的速度和方向,使机器人沿着线条移动。

自动售货机

构建一个自动售货机,能够检测投入硬币的大小和重量,并将口香糖或其他物品从安全位置推到取货口。可以使用压力传感器和尺寸传感器来检测硬币,使用步进电机来推动物品。

模型起重机

根据之前的问题构建一个模型起重机,并为集装箱配备标记,如周期性闪烁的 LED。编写一个算法,使起重机能够自动运行,例如根据 LED 的闪烁模式来识别集装箱,并将其移动到指定位置。

远程控制帆船

要远程控制帆船,需要使用舵机来控制帆的角度和舵的方向。可以使用风向传感器和 GPS 模块来实现自主控制。在远程控制方面,可以使用无线通信模块,如蓝牙或 Wi - Fi,来发送控制指令。

实验结果呈现与报告撰写

实验数据收集与分析

在完成实验后,需要收集和分析实验数据。对于激光束轮廓监测系统,可以记录不同位置的传感器读数,并绘制激光束的横向轮廓图。对于寻火机器人,可以记录机器人的运动轨迹、传感器读数和响应时间等数据。通过对这些数据的分析,可以评估系统的性能和可靠性。

结果呈现

可以通过制作图表、图像和视频等方式来呈现实验结果。例如,使用 Octave 脚本生成的激光束轮廓图可以直观地展示激光束的分布情况;使用相机记录机器人的运动过程,并制作成视频,能够更生动地展示机器人的工作情况。

报告撰写

报告是对实验的总结和记录,应包括以下内容:
1. 实验目的 :明确实验的目标和意义。
2. 实验原理 :详细介绍实验所依据的原理和方法。
3. 实验步骤 :描述实验的具体操作步骤,包括硬件搭建和软件编程。
4. 实验结果 :展示实验数据和分析结果,使用图表和图像进行辅助说明。
5. 结论与讨论 :总结实验的主要成果,分析实验中存在的问题和局限性,并提出改进建议。
6. 参考文献 :列出在实验过程中参考的相关文献和资料。

总结

通过本文的介绍,我们详细了解了激光束轮廓监测系统和寻火机器人的搭建过程,包括硬件选择、软件编程、实验测试和数据分析等方面。同时,我们也探讨了系统的局限性和改进方向,以及一些拓展应用和项目思路。这些实验不仅展示了如何将传感器、执行器和控制器结合起来,实现具有特定功能的系统,还为我们提供了一个学习和实践的平台,帮助我们提高解决实际问题的能力。在未来的学习和工作中,我们可以进一步探索这些技术的应用,不断创新和改进,为实现更智能、更高效的系统而努力。

表格:系统对比

系统名称 功能 局限性 改进方向
激光束轮廓监测系统 测量激光束的横向轮廓 衍射影响、步长问题、强度相关非线性 优化障碍物设计、更换步进电机驱动器、进行强度校准
寻火机器人 检测热源并向其移动 检测范围有限、运动不够灵活、传感器数量和布局不足 增加传感器数量和类型、优化运动算法、改进传感器布局

mermaid 流程图:项目拓展思路

graph LR;
    A[项目拓展] --> B[增加功能模块];
    A --> C[其他项目思路];
    B --> B1[气体传感器];
    B --> B2[LCD 显示屏];
    C --> C1[远程控制船];
    C --> C2[线跟随器];
    C --> C3[自动售货机];
    C --> C4[模型起重机];
    C --> C5[远程控制帆船];
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值