HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • 技术面试完全指南

    • 技术面试完全指南
    • 8年面试官告诉你:90%的简历在第一轮就被刷掉了
    • 刷了500道LeetCode,终于明白大厂算法面试到底考什么
    • 高频算法题精讲-双指针与滑动窗口
    • 03-高频算法题精讲-二分查找与排序
    • 04-高频算法题精讲-树与递归
    • 05-高频算法题精讲-图与拓扑排序
    • 06-高频算法题精讲-动态规划
    • Go面试必问:一道GMP问题,干掉90%的候选人
    • 08-数据库面试高频题
    • 09-分布式系统面试题
    • 10-Kubernetes与云原生面试题
    • 11-系统设计面试方法论
    • 前端面试高频题
    • AI 与机器学习面试题
    • 行为面试与软技能

第二章 硬件平台

2.1 机器人底盘类型

2.1.1 差速底盘(Differential Drive)

差速底盘是最常见的移动机器人底盘,通过左右轮速差实现转向。

运动学特性:

  • 左右轮独立驱动
  • 转弯半径可变(0到无穷大)
  • 不能横向移动

轮式编码器:

脉冲数(PPR): 通常为500-2000
轮距(L): 两轮中心距离
轮径(R): 驱动轮半径

线速度计算:
v = (v_left + v_right) / 2
角速度计算:
ω = (v_right - v_left) / L

编码器读取(Arduino):

// 编码器中断处理
#define ENCODER_LEFT_A 2
#define ENCODER_LEFT_B 3
#define ENCODER_RIGHT_A 18
#define ENCODER_RIGHT_B 19

volatile long encoder_left = 0;
volatile long encoder_right = 0;

void setup() {
    pinMode(ENCODER_LEFT_A, INPUT_PULLUP);
    pinMode(ENCODER_LEFT_B, INPUT_PULLUP);
    pinMode(ENCODER_RIGHT_A, INPUT_PULLUP);
    pinMode(ENCODER_RIGHT_B, INPUT_PULLUP);

    attachInterrupt(digitalPinToInterrupt(ENCODER_LEFT_A),
                    leftEncoderA, CHANGE);
    attachInterrupt(digitalPinToInterrupt(ENCODER_RIGHT_A),
                    rightEncoderA, CHANGE);
}

void leftEncoderA() {
    if (digitalRead(ENCODER_LEFT_A) == digitalRead(ENCODER_LEFT_B)) {
        encoder_left++;
    } else {
        encoder_left--;
    }
}

void rightEncoderA() {
    if (digitalRead(ENCODER_RIGHT_A) == digitalRead(ENCODER_RIGHT_B)) {
        encoder_right++;
    } else {
        encoder_right--;
    }
}

// 计算速度
float getSpeed(long encoder_count, float dt) {
    const float PPR = 1000.0;  // 每转脉冲数
    const float WHEEL_RADIUS = 0.05;  // 轮半径(m)

    float rps = encoder_count / PPR / dt;  // 转/秒
    return 2 * PI * WHEEL_RADIUS * rps;  // m/s
}

2.1.2 全向轮底盘(Omni-directional)

全向轮可以沿任意方向移动,无需旋转车体。

三轮全向配置(120度分布):

运动学方程:
V1 = -0.5*Vx + 0.866*Vy + L*ω
V2 = -0.5*Vx - 0.866*Vy + L*ω
V3 = Vx + L*ω

其中:
Vx, Vy: 机器人x、y方向速度
ω: 角速度
L: 轮子到中心距离

四轮全向配置(90度分布):

// 运动学逆解
void omniWheelControl(float vx, float vy, float omega) {
    const float L = 0.2;  // 轮距的一半
    const float R = 0.05; // 轮半径

    // 四轮速度计算
    float v1 = vx - vy - L * omega;  // 左前
    float v2 = vx + vy - L * omega;  // 右前
    float v3 = vx + vy + L * omega;  // 右后
    float v4 = vx - vy + L * omega;  // 左后

    // 转换为轮速(rad/s)
    float w1 = v1 / R;
    float w2 = v2 / R;
    float w3 = v3 / R;
    float w4 = v4 / R;

    setMotorSpeed(1, w1);
    setMotorSpeed(2, w2);
    setMotorSpeed(3, w3);
    setMotorSpeed(4, w4);
}

2.1.3 麦克纳姆轮(Mecanum Wheel)

麦轮通过斜向的辊子实现全向移动。

力学分析:

每个轮子的辊子与车体成45度角

运动学方程(X型配置):
V_left_front  = Vx - Vy - L*ω
V_right_front = Vx + Vy + L*ω
V_left_rear   = Vx + Vy - L*ω
V_right_rear  = Vx - Vy + L*ω

逆解(已知轮速求车体速度):
Vx = (V_lf + V_rf + V_lr + V_rr) / 4
Vy = (-V_lf + V_rf + V_lr - V_rr) / 4
ω = (-V_lf + V_rf - V_lr + V_rr) / (4*L)

2.1.4 底盘对比表格

底盘类型运动灵活性承载能力控制复杂度成本适用场景
差速低高简单低室内导航、仓储
三轮全向中中中等中狭窄空间、服务
四轮全向高中中等中精确定位
麦轮高高复杂高工业、竞赛

2.2 电机驱动系统

2.2.1 直流电机(DC Motor)

H桥驱动原理:

H桥允许电流双向流动,实现正反转

      S1      S3
       |      |
       +------+
       |      |
    +--+  M   +--+
       |      |
       +------+
       |      |
      S2      S4

正转: S1、S4导通
反转: S2、S3导通
制动: S1、S3或S2、S4导通

L298N驱动模块:

// L298N控制代码
#define IN1 7
#define IN2 6
#define ENA 5  // PWM引脚

void setup() {
    pinMode(IN1, OUTPUT);
    pinMode(IN2, OUTPUT);
    pinMode(ENA, OUTPUT);
}

// 电机控制函数
void motorControl(int speed) {
    // speed范围: -255到255
    if (speed > 0) {
        // 正转
        digitalWrite(IN1, HIGH);
        digitalWrite(IN2, LOW);
        analogWrite(ENA, speed);
    } else if (speed < 0) {
        // 反转
        digitalWrite(IN1, LOW);
        digitalWrite(IN2, HIGH);
        analogWrite(ENA, -speed);
    } else {
        // 停止
        digitalWrite(IN1, LOW);
        digitalWrite(IN2, LOW);
        analogWrite(ENA, 0);
    }
}

// PWM调速
void loop() {
    // 加速
    for (int speed = 0; speed <= 255; speed += 5) {
        motorControl(speed);
        delay(50);
    }

    // 减速
    for (int speed = 255; speed >= 0; speed -= 5) {
        motorControl(speed);
        delay(50);
    }
}

TB6612驱动模块(更高效):

// TB6612控制
#define PWMA 5
#define AIN1 7
#define AIN2 6
#define STBY 8  // Standby引脚

void setup() {
    pinMode(PWMA, OUTPUT);
    pinMode(AIN1, OUTPUT);
    pinMode(AIN2, OUTPUT);
    pinMode(STBY, OUTPUT);
    digitalWrite(STBY, HIGH);  // 使能
}

void motorDrive(int speed) {
    if (speed > 0) {
        digitalWrite(AIN1, HIGH);
        digitalWrite(AIN2, LOW);
    } else {
        digitalWrite(AIN1, LOW);
        digitalWrite(AIN2, HIGH);
        speed = -speed;
    }
    analogWrite(PWMA, speed);
}

2.2.2 步进电机(Stepper Motor)

细分驱动:

全步: 1.8度/步 (200步/转)
半步: 0.9度/步 (400步/转)
1/4步: 0.45度/步 (800步/转)
1/8步: 0.225度/步 (1600步/转)
1/16步: 0.1125度/步 (3200步/转)

A4988驱动器控制:

#define STEP_PIN 3
#define DIR_PIN 4
#define ENABLE_PIN 5

// 微步设置引脚
#define MS1 6
#define MS2 7
#define MS3 8

void setup() {
    pinMode(STEP_PIN, OUTPUT);
    pinMode(DIR_PIN, OUTPUT);
    pinMode(ENABLE_PIN, OUTPUT);
    pinMode(MS1, OUTPUT);
    pinMode(MS2, OUTPUT);
    pinMode(MS3, OUTPUT);

    // 设置1/16细分
    digitalWrite(MS1, HIGH);
    digitalWrite(MS2, HIGH);
    digitalWrite(MS3, HIGH);

    digitalWrite(ENABLE_PIN, LOW);  // 使能
}

// S型加减速曲线
void moveWithAccel(long steps, int max_speed, int accel_steps) {
    digitalWrite(DIR_PIN, steps > 0 ? HIGH : LOW);
    steps = abs(steps);

    for (long i = 0; i < steps; i++) {
        int delay_time;

        if (i < accel_steps) {
            // 加速阶段
            float ratio = (float)i / accel_steps;
            delay_time = 2000 - ratio * (2000 - max_speed);
        } else if (i > steps - accel_steps) {
            // 减速阶段
            float ratio = (float)(steps - i) / accel_steps;
            delay_time = 2000 - ratio * (2000 - max_speed);
        } else {
            // 匀速阶段
            delay_time = max_speed;
        }

        digitalWrite(STEP_PIN, HIGH);
        delayMicroseconds(delay_time);
        digitalWrite(STEP_PIN, LOW);
        delayMicroseconds(delay_time);
    }
}

2.2.3 舵机(Servo Motor)

PWM角度控制:

标准舵机:
- PWM周期: 20ms (50Hz)
- 0度: 0.5ms高电平 (占空比2.5%)
- 90度: 1.5ms高电平 (占空比7.5%)
- 180度: 2.5ms高电平 (占空比12.5%)

Arduino舵机控制:

#include <Servo.h>

Servo myservo;

void setup() {
    myservo.attach(9);  // 连接到引脚9
}

void loop() {
    // 扫描0-180度
    for (int pos = 0; pos <= 180; pos += 1) {
        myservo.write(pos);
        delay(15);
    }

    for (int pos = 180; pos >= 0; pos -= 1) {
        myservo.write(pos);
        delay(15);
    }
}

// 自定义PWM控制(更精确)
void setServoPulse(int pin, int angle) {
    int pulse_width = map(angle, 0, 180, 500, 2500);
    digitalWrite(pin, HIGH);
    delayMicroseconds(pulse_width);
    digitalWrite(pin, LOW);
    delay(20 - pulse_width/1000);
}

2.3 传感器硬件

2.3.1 激光雷达

RPLIDAR A1规格:

扫描频率: 5.5Hz (330rpm)
采样频率: 8000次/秒
测距范围: 0.15-12m
角度分辨率: 1度
接口: UART (115200bps)
供电: 5V DC

Arduino串口读取:

#define RPLIDAR_MOTOR 3  // PWM控制电机

void setup() {
    Serial.begin(115200);  // 连接RPLIDAR
    Serial1.begin(115200); // 调试输出

    pinMode(RPLIDAR_MOTOR, OUTPUT);
    analogWrite(RPLIDAR_MOTOR, 255);  // 启动电机
}

void loop() {
    if (Serial.available() >= 5) {
        byte sync1 = Serial.read();
        byte sync2 = Serial.read();

        if (sync1 == 0xA5 && sync2 == 0x5A) {
            // 读取数据包
            byte quality = Serial.read();
            byte angle_low = Serial.read();
            byte angle_high = Serial.read();
            byte distance_low = Serial.read();
            byte distance_high = Serial.read();

            float angle = ((angle_high << 8) | angle_low) / 64.0;
            float distance = ((distance_high << 8) | distance_low) / 4.0;

            Serial1.print("Angle: ");
            Serial1.print(angle);
            Serial1.print(" Distance: ");
            Serial1.println(distance);
        }
    }
}

树莓派ROS集成:

#!/usr/bin/env python
import rospy
from sensor_msgs.msg import LaserScan

# 安装rplidar驱动
# sudo apt install ros-noetic-rplidar-ros

# launch文件配置
"""
<launch>
  <node name="rplidar_node" pkg="rplidar_ros" type="rplidarNode" output="screen">
    <param name="serial_port" type="string" value="/dev/ttyUSB0"/>
    <param name="serial_baudrate" type="int" value="115200"/>
    <param name="frame_id" type="string" value="laser"/>
    <param name="inverted" type="bool" value="false"/>
    <param name="angle_compensate" type="bool" value="true"/>
  </node>
</launch>
"""

2.3.2 深度相机

Intel RealSense D435规格:

深度技术: 主动立体视觉
RGB分辨率: 1920x1080 @ 30fps
深度分辨率: 1280x720 @ 90fps
测距范围: 0.3-10m
视场角: 87°×58°
接口: USB 3.0

树莓派配置:

# 安装RealSense SDK
sudo apt install librealsense2-dkms librealsense2-utils

# 安装ROS包
sudo apt install ros-noetic-realsense2-camera

# 测试
realsense-viewer

ROS启动配置:

<launch>
  <node name="realsense_node" pkg="realsense2_camera" type="realsense2_camera_node">
    <param name="enable_depth" value="true"/>
    <param name="enable_color" value="true"/>
    <param name="enable_infra1" value="false"/>
    <param name="enable_infra2" value="false"/>
    <param name="depth_width" value="640"/>
    <param name="depth_height" value="480"/>
    <param name="depth_fps" value="30"/>
    <param name="color_width" value="640"/>
    <param name="color_height" value="480"/>
    <param name="color_fps" value="30"/>
  </node>
</launch>

2.3.3 IMU(惯性测量单元)

MPU6050规格:

加速度计: ±2g/±4g/±8g/±16g
陀螺仪: ±250/±500/±1000/±2000 °/s
接口: I2C
I2C地址: 0x68 (AD0=0) 或 0x69 (AD0=1)
供电: 3-5V

Arduino I2C读取:

#include <Wire.h>

const int MPU6050_ADDR = 0x68;

void setup() {
    Serial.begin(9600);
    Wire.begin();

    // 唤醒MPU6050
    Wire.beginTransmission(MPU6050_ADDR);
    Wire.write(0x6B);  // PWR_MGMT_1寄存器
    Wire.write(0);     // 设置为0(唤醒)
    Wire.endTransmission(true);
}

void loop() {
    int16_t ax, ay, az, gx, gy, gz;

    // 读取加速度计和陀螺仪
    Wire.beginTransmission(MPU6050_ADDR);
    Wire.write(0x3B);  // 从ACCEL_XOUT_H开始读
    Wire.endTransmission(false);
    Wire.requestFrom(MPU6050_ADDR, 14, true);

    ax = Wire.read() << 8 | Wire.read();
    ay = Wire.read() << 8 | Wire.read();
    az = Wire.read() << 8 | Wire.read();
    Wire.read(); Wire.read();  // 跳过温度
    gx = Wire.read() << 8 | Wire.read();
    gy = Wire.read() << 8 | Wire.read();
    gz = Wire.read() << 8 | Wire.read();

    // 转换为实际值
    float accel_x = ax / 16384.0;  // ±2g
    float accel_y = ay / 16384.0;
    float accel_z = az / 16384.0;
    float gyro_x = gx / 131.0;     // ±250°/s
    float gyro_y = gy / 131.0;
    float gyro_z = gz / 131.0;

    Serial.print("AccX: "); Serial.print(accel_x);
    Serial.print(" AccY: "); Serial.print(accel_y);
    Serial.print(" AccZ: "); Serial.print(accel_z);
    Serial.print(" GyroX: "); Serial.print(gyro_x);
    Serial.print(" GyroY: "); Serial.print(gyro_y);
    Serial.print(" GyroZ: "); Serial.println(gyro_z);

    delay(100);
}

BNO055(带磁力计和融合算法):

#include <Adafruit_BNO055.h>

Adafruit_BNO055 bno = Adafruit_BNO055(55);

void setup() {
    Serial.begin(9600);
    if (!bno.begin()) {
        Serial.println("BNO055未检测到");
        while (1);
    }

    bno.setExtCrystalUse(true);
}

void loop() {
    // 获取欧拉角(内部融合算法)
    sensors_event_t event;
    bno.getEvent(&event);

    Serial.print("Heading: "); Serial.print(event.orientation.x);
    Serial.print(" Roll: "); Serial.print(event.orientation.y);
    Serial.print(" Pitch: "); Serial.println(event.orientation.z);

    // 获取四元数
    imu::Quaternion quat = bno.getQuat();
    Serial.print("Quat w: "); Serial.print(quat.w());
    Serial.print(" x: "); Serial.print(quat.x());
    Serial.print(" y: "); Serial.print(quat.y());
    Serial.print(" z: "); Serial.println(quat.z());

    delay(100);
}

2.3.4 超声波传感器

HC-SR04规格:

测距范围: 2cm-400cm
精度: 3mm
工作频率: 40kHz
触发信号: 10us高电平
回响信号: 150us-25ms

Arduino测距代码:

#define TRIG_PIN 9
#define ECHO_PIN 10

void setup() {
    Serial.begin(9600);
    pinMode(TRIG_PIN, OUTPUT);
    pinMode(ECHO_PIN, INPUT);
}

float getDistance() {
    // 发送触发信号
    digitalWrite(TRIG_PIN, LOW);
    delayMicroseconds(2);
    digitalWrite(TRIG_PIN, HIGH);
    delayMicroseconds(10);
    digitalWrite(TRIG_PIN, LOW);

    // 测量回响时间
    long duration = pulseIn(ECHO_PIN, HIGH, 30000);  // 超时30ms

    if (duration == 0) {
        return -1;  // 超时
    }

    // 计算距离(cm)
    float distance = duration * 0.034 / 2;
    return distance;
}

void loop() {
    float dist = getDistance();

    if (dist > 0) {
        Serial.print("距离: ");
        Serial.print(dist);
        Serial.println(" cm");
    } else {
        Serial.println("测量失败");
    }

    delay(200);
}

2.4 单片机与开发板

2.4.1 Arduino平台

Arduino Mega 2560规格:

微控制器: ATmega2560
工作电压: 5V
数字I/O: 54个(15个PWM)
模拟输入: 16个
串口: 4个(UART)
Flash: 256KB
SRAM: 8KB
时钟频率: 16MHz

机器人底盘控制完整代码:

// 差速机器人控制
#define MOTOR_L1 7
#define MOTOR_L2 6
#define PWM_L 5
#define MOTOR_R1 4
#define MOTOR_R2 3
#define PWM_R 2

#define ENCODER_L_A 18
#define ENCODER_L_B 19
#define ENCODER_R_A 20
#define ENCODER_R_B 21

volatile long encoder_left = 0;
volatile long encoder_right = 0;

const float WHEEL_RADIUS = 0.05;  // 轮半径(m)
const float WHEEL_BASE = 0.3;     // 轮距(m)
const float PPR = 1000.0;         // 编码器精度

// PID参数
float Kp = 1.0, Ki = 0.1, Kd = 0.05;
float error_left = 0, error_right = 0;
float integral_left = 0, integral_right = 0;
float last_error_left = 0, last_error_right = 0;

void setup() {
    Serial.begin(115200);

    // 电机引脚
    pinMode(MOTOR_L1, OUTPUT);
    pinMode(MOTOR_L2, OUTPUT);
    pinMode(PWM_L, OUTPUT);
    pinMode(MOTOR_R1, OUTPUT);
    pinMode(MOTOR_R2, OUTPUT);
    pinMode(PWM_R, OUTPUT);

    // 编码器引脚
    pinMode(ENCODER_L_A, INPUT_PULLUP);
    pinMode(ENCODER_L_B, INPUT_PULLUP);
    pinMode(ENCODER_R_A, INPUT_PULLUP);
    pinMode(ENCODER_R_B, INPUT_PULLUP);

    attachInterrupt(digitalPinToInterrupt(ENCODER_L_A), leftEncoder, CHANGE);
    attachInterrupt(digitalPinToInterrupt(ENCODER_R_A), rightEncoder, CHANGE);
}

void leftEncoder() {
    if (digitalRead(ENCODER_L_A) == digitalRead(ENCODER_L_B)) {
        encoder_left++;
    } else {
        encoder_left--;
    }
}

void rightEncoder() {
    if (digitalRead(ENCODER_R_A) == digitalRead(ENCODER_R_B)) {
        encoder_right++;
    } else {
        encoder_right--;
    }
}

void setMotor(int motor, int speed) {
    // motor: 0=左, 1=右
    // speed: -255到255
    int pin1, pin2, pwm_pin;

    if (motor == 0) {
        pin1 = MOTOR_L1; pin2 = MOTOR_L2; pwm_pin = PWM_L;
    } else {
        pin1 = MOTOR_R1; pin2 = MOTOR_R2; pwm_pin = PWM_R;
    }

    if (speed > 0) {
        digitalWrite(pin1, HIGH);
        digitalWrite(pin2, LOW);
        analogWrite(pwm_pin, constrain(speed, 0, 255));
    } else if (speed < 0) {
        digitalWrite(pin1, LOW);
        digitalWrite(pin2, HIGH);
        analogWrite(pwm_pin, constrain(-speed, 0, 255));
    } else {
        digitalWrite(pin1, LOW);
        digitalWrite(pin2, LOW);
        analogWrite(pwm_pin, 0);
    }
}

int pidControl(float target, float current, float &integral,
               float &last_error) {
    float error = target - current;
    integral += error;
    integral = constrain(integral, -100, 100);  // 抗积分饱和

    float derivative = error - last_error;
    float output = Kp * error + Ki * integral + Kd * derivative;

    last_error = error;
    return constrain((int)output, -255, 255);
}

void setVelocity(float linear, float angular) {
    // 运动学逆解
    float v_left = linear - angular * WHEEL_BASE / 2;
    float v_right = linear + angular * WHEEL_BASE / 2;

    // 这里应该使用PID闭环控制
    // 简化版直接映射
    int pwm_left = (int)(v_left * 255 / 1.0);   // 假设最大速度1m/s
    int pwm_right = (int)(v_right * 255 / 1.0);

    setMotor(0, pwm_left);
    setMotor(1, pwm_right);
}

void loop() {
    // 串口接收速度命令
    if (Serial.available() >= 8) {
        float linear, angular;
        Serial.readBytes((char*)&linear, 4);
        Serial.readBytes((char*)&angular, 4);

        setVelocity(linear, angular);
    }

    // 发送里程计数据
    static unsigned long last_time = 0;
    unsigned long current_time = millis();

    if (current_time - last_time >= 50) {  // 20Hz
        float dt = (current_time - last_time) / 1000.0;

        // 计算速度
        float v_left = (encoder_left / PPR) * (2 * PI * WHEEL_RADIUS) / dt;
        float v_right = (encoder_right / PPR) * (2 * PI * WHEEL_RADIUS) / dt;

        encoder_left = 0;
        encoder_right = 0;

        // 发送数据
        Serial.print(v_left); Serial.print(",");
        Serial.println(v_right);

        last_time = current_time;
    }
}

2.4.2 树莓派4B

规格:

处理器: Broadcom BCM2711 (4核 Cortex-A72 @ 1.5GHz)
内存: 2GB/4GB/8GB LPDDR4
网络: Gigabit Ethernet, WiFi 5, Bluetooth 5.0
GPIO: 40针
USB: 2x USB 3.0, 2x USB 2.0
供电: 5V 3A USB-C
操作系统: Ubuntu 20.04 + ROS Noetic

ROS安装:

# 安装ROS Noetic
sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
sudo apt install curl
curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo apt-key add -
sudo apt update
sudo apt install ros-noetic-desktop

# 环境配置
echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc
source ~/.bashrc

GPIO控制(Python):

#!/usr/bin/env python3
import RPi.GPIO as GPIO
import time

# 电机控制
MOTOR_L1 = 17
MOTOR_L2 = 27
PWM_L = 18

GPIO.setmode(GPIO.BCM)
GPIO.setup(MOTOR_L1, GPIO.OUT)
GPIO.setup(MOTOR_L2, GPIO.OUT)
GPIO.setup(PWM_L, GPIO.OUT)

pwm = GPIO.PWM(PWM_L, 1000)  # 1kHz
pwm.start(0)

def set_motor(speed):
    """speed: -100到100"""
    if speed > 0:
        GPIO.output(MOTOR_L1, GPIO.HIGH)
        GPIO.output(MOTOR_L2, GPIO.LOW)
        pwm.ChangeDutyCycle(speed)
    elif speed < 0:
        GPIO.output(MOTOR_L1, GPIO.LOW)
        GPIO.output(MOTOR_L2, GPIO.HIGH)
        pwm.ChangeDutyCycle(-speed)
    else:
        GPIO.output(MOTOR_L1, GPIO.LOW)
        GPIO.output(MOTOR_L2, GPIO.LOW)
        pwm.ChangeDutyCycle(0)

try:
    while True:
        # 加速
        for speed in range(0, 101, 10):
            set_motor(speed)
            time.sleep(0.1)

        # 减速
        for speed in range(100, -1, -10):
            set_motor(speed)
            time.sleep(0.1)
except KeyboardInterrupt:
    pwm.stop()
    GPIO.cleanup()

串口通信(与Arduino通信):

#!/usr/bin/env python3
import serial
import struct

ser = serial.Serial('/dev/ttyACM0', 115200, timeout=1)

def send_velocity(linear, angular):
    """发送速度命令到Arduino"""
    data = struct.pack('ff', linear, angular)
    ser.write(data)

def read_odometry():
    """读取里程计数据"""
    if ser.in_waiting:
        line = ser.readline().decode('utf-8').strip()
        if ',' in line:
            v_left, v_right = map(float, line.split(','))
            return v_left, v_right
    return None, None

# 使用示例
send_velocity(0.5, 0.0)  # 直行0.5m/s
time.sleep(1)
v_left, v_right = read_odometry()
print(f"Left: {v_left}, Right: {v_right}")

2.4.3 Jetson Nano/Xavier

Jetson Nano规格:

GPU: 128-core Maxwell
CPU: Quad-core ARM A57 @ 1.43GHz
内存: 4GB LPDDR4
存储: microSD
供电: 5V 4A / 5V 2A(低功耗模式)
功耗: 10W(最大)
CUDA核心: 128
适用: 深度学习推理

CUDA加速示例:

import cv2
import numpy as np
import tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit

# YOLO TensorRT推理
class YOLOTensorRT:
    def __init__(self, engine_path):
        self.logger = trt.Logger(trt.Logger.WARNING)
        with open(engine_path, 'rb') as f:
            self.engine = trt.Runtime(self.logger).deserialize_cuda_engine(f.read())
        self.context = self.engine.create_execution_context()

        # 分配显存
        self.inputs = []
        self.outputs = []
        self.bindings = []

        for binding in self.engine:
            size = trt.volume(self.engine.get_binding_shape(binding))
            dtype = trt.nptype(self.engine.get_binding_dtype(binding))
            host_mem = cuda.pagelocked_empty(size, dtype)
            device_mem = cuda.mem_alloc(host_mem.nbytes)

            self.bindings.append(int(device_mem))

            if self.engine.binding_is_input(binding):
                self.inputs.append({'host': host_mem, 'device': device_mem})
            else:
                self.outputs.append({'host': host_mem, 'device': device_mem})

    def infer(self, image):
        # 图像预处理
        input_image = cv2.resize(image, (640, 640))
        input_image = input_image.astype(np.float32) / 255.0
        input_image = np.transpose(input_image, (2, 0, 1))

        # 复制到GPU
        np.copyto(self.inputs[0]['host'], input_image.ravel())
        cuda.memcpy_htod(self.inputs[0]['device'], self.inputs[0]['host'])

        # 推理
        self.context.execute_v2(bindings=self.bindings)

        # 复制回CPU
        cuda.memcpy_dtoh(self.outputs[0]['host'], self.outputs[0]['device'])

        return self.outputs[0]['host']

2.5 电源设计

2.5.1 电池选择

锂电池对比:

类型电压容量重量放电率适用场景
186503.7V2000-3500mAh45g/节1-3C通用
聚合物3.7V定制轻10-30C高功率
磷酸铁锂3.2V高重1-5C长寿命

电池组配置:

3S电池组:
3节串联 = 11.1V标称电压
满电: 12.6V
安全截止: 9V

功率计算:
电流 = 功率 / 电压
例: 50W电机在12V下
I = 50W / 12V = 4.17A

容量计算:
续航时间 = 电池容量 / 平均电流
例: 5000mAh电池,平均4A
续航 = 5Ah / 4A = 1.25小时

2.5.2 电源转换

降压模块(DC-DC Buck):

输入: 12V(电池)
输出1: 5V 3A (树莓派)
输出2: 5V 2A (传感器)
输出3: 12V (电机驱动)

推荐芯片:
LM2596: 3A输出
XL4015: 5A输出

电路原理图:

        +12V Battery
            |
            |
        [保护二极管]
            |
            +--- [DC-DC 1] ---> 5V 3A (树莓派)
            |
            +--- [DC-DC 2] ---> 5V 2A (传感器)
            |
            +--- [直通] -----> 12V (电机)
            |
           GND

2.5.3 电源保护电路

完整保护方案:

1. 过流保护: 自恢复保险丝
2. 反接保护: 肖特基二极管或MOSFET
3. 过压保护: 稳压管
4. 电池管理: BMS(充放电保护)

保护电路代码(电压监测):

#define BATTERY_PIN A0
#define VOLTAGE_DIVIDER_RATIO 4.0  // 分压比

float getBatteryVoltage() {
    int raw = analogRead(BATTERY_PIN);
    float voltage = (raw / 1023.0) * 5.0 * VOLTAGE_DIVIDER_RATIO;
    return voltage;
}

void checkBattery() {
    float voltage = getBatteryVoltage();

    if (voltage < 9.0) {
        // 电池电压过低,停止电机
        Serial.println("警告: 电池电压过低!");
        stopAllMotors();
        digitalWrite(LED_PIN, HIGH);  // 报警LED
    } else if (voltage < 10.0) {
        // 低电量警告
        Serial.println("警告: 电池电量低");
    }
}

2.6 完整硬件系统架构

2.6.1 系统电路图

                    [12V 锂电池组]
                         |
                    [电源开关]
                         |
              +----------+----------+
              |          |          |
         [12V直通]   [5V 3A]   [5V 2A]
              |          |          |
        [电机驱动]  [树莓派4B]  [传感器]
         L298N         |          |
        /     \      [USB]     [I2C/UART]
       /       \       |          |
   [左电机] [右电机] [Arduino] [激光雷达]
       |       |     Mega       [IMU]
   [编码器]  [编码器]  |       [超声波]
                      |
                 [舵机控制]

2.6.2 BOM清单示例

序号名称型号数量单价总价备注
1主控板树莓派4B 4GB1350350核心控制
2单片机Arduino Mega 256015050底层控制
3激光雷达RPLIDAR A11500500SLAM建图
4IMUMPU605011515姿态测量
5电机驱动L298N21530双路H桥
6直流电机12V 200RPM23060带编码器
7轮子橡胶轮65mm21020防滑
8万向轮小型万向轮155支撑
9锂电池3S 11.1V 5000mAh1120120聚合物
10电源模块LM2596降压模块210205V输出
11超声波HC-SR044520避障
12底盘亚克力板13030300x200mm
13连接线杜邦线若干12020公母/母母
14开关船型开关133电源开关
15散热散热片+风扇11515树莓派散热
总计1258

2.7 高频面试题

问题1: 直流电机的PWM调速原理是什么?如何提高控制精度?

答案:

PWM调速原理:

  • PWM(脉宽调制)通过改变高电平占空比控制电机平均电压
  • 占空比 = 高电平时间 / 周期时间
  • 平均电压 = 占空比 × 供电电压
  • 电机转速正比于平均电压

提高精度的方法:

  1. 提高PWM频率

    • 常用频率: 1kHz-20kHz
    • 频率过低: 电机抖动
    • 频率过高: 开关损耗大
  2. 使用编码器闭环控制

    float target_speed = 1.0;  // m/s
    float current_speed = getSpeedFromEncoder();
    float error = target_speed - current_speed;
    int pwm = pid_controller(error);
    
  3. 加减速曲线

    • 避免突然启停
    • S型曲线更平滑
  4. 电压补偿

    • 监测电池电压
    • 根据电压调整PWM
  5. 死区补偿

    • 电机启动存在最小电压
    • PWM低于阈值时不转动

问题2: 如何选择合适的电机驱动芯片?L298N和TB6612有什么区别?

答案:

对比表格:

特性L298NTB6612
工作电压5-35V4.5-13.5V
最大电流2A(持续)1.2A(持续)
效率约50%约90%
压降3-4V1V
发热严重较小
价格便宜稍贵
适用低速大扭矩高速小负载

选择建议:

  1. L298N适用场景:

    • 大功率电机(>1A)
    • 电压充足(>12V)
    • 成本敏感
    • 不在意效率
  2. TB6612适用场景:

    • 小功率电机(<1A)
    • 电池供电(效率重要)
    • 发热受限
    • 低压应用(5-12V)
  3. 其他选择:

    • DRV8833: 小功率,低压
    • BTS7960: 大功率(43A)
    • A4950: 步进电机

问题3: 激光雷达和深度相机的区别?如何选择?

答案:

对比表格:

特性激光雷达深度相机
测距原理激光飞行时间立体视觉/结构光
精度高(mm级)中(cm级)
测距范围远(>10m)近(0.3-10m)
扫描角度360度有限(<90度)
环境光影响小大
室外使用好差
获取信息2D距离3D点云+RGB
价格贵便宜
数据量小大

选择建议:

  1. 选择激光雷达:

    • 室外环境
    • 2D导航建图
    • 远距离测量
    • 360度感知
  2. 选择深度相机:

    • 室内环境
    • 需要颜色信息
    • 3D重建
    • 目标识别
    • 成本敏感
  3. 组合使用:

    • 激光雷达: 全局导航
    • 深度相机: 局部避障+识别
    • 互补优势

问题4: 树莓派和Arduino在机器人系统中如何分工?

答案:

分工原则:

Arduino (底层控制):
- 实时性要求高的任务
- 硬件接口直接控制
- 高频率循环(>100Hz)
- 简单逻辑

树莓派 (高层决策):
- 复杂算法
- ROS节点运行
- 传感器数据处理
- 导航规划

典型分工方案:

任务Arduino树莓派
电机PWM控制是
编码器读取是
PID速度控制是
超声波测距是
激光雷达读取是
图像处理是
SLAM建图是
路径规划是
ROS节点是

通信方式:

# 树莓派通过串口发送速度命令
import serial
ser = serial.Serial('/dev/ttyACM0', 115200)

# 发送线速度和角速度
cmd = f"{linear_vel},{angular_vel}\n"
ser.write(cmd.encode())

# Arduino接收并控制电机
String data = Serial.readStringUntil('\n');
float linear = data.substring(0, data.indexOf(',')).toFloat();
float angular = data.substring(data.indexOf(',') + 1).toFloat();

优势:

  • Arduino: 实时性好,稳定
  • 树莓派: 算力强,生态丰富
  • 分工明确,各司其职

问题5: 如何设计机器人的电源系统?需要考虑哪些因素?

答案:

设计步骤:

  1. 功耗评估
树莓派4B: 5V 3A = 15W
Arduino Mega: 5V 0.5A = 2.5W
电机(2个): 12V 2A×2 = 48W
激光雷达: 5V 1A = 5W
传感器: 5V 0.5A = 2.5W
-----------------------------------
总功耗: 约73W
峰值功耗(电机启动): 约100W
  1. 电池选择
选择12V(3S)锂电池
容量: 5000mAh

续航时间计算:
平均电流 = 73W / 12V = 6.1A
续航 = 5Ah / 6.1A = 0.82小时 ≈ 50分钟

建议选择8000mAh或更大
  1. 电源分配
12V主线 ──┬─→ [电机驱动] (12V直通)
          │
          ├─→ [DC-DC 5V/3A] → 树莓派
          │
          ├─→ [DC-DC 5V/2A] → Arduino + 传感器
          │
          └─→ [DC-DC 5V/1A] → 激光雷达(独立供电)
  1. 保护设计
// 电压监测
void batteryMonitor() {
    float voltage = getBatteryVoltage();

    if (voltage < 9.0) {
        emergencyStop();  // 紧急停止
    } else if (voltage < 10.0) {
        reducePower();    // 降低功率
        warning();        // 发出警告
    }
}

// 功率管理
void powerManagement() {
    if (isLowBattery()) {
        disableNonCritical();  // 关闭非关键功能
        enableReturnHome();    // 启动返航
    }
}
  1. 安全考虑
  • 自恢复保险丝(每路)
  • 反接保护二极管
  • 急停开关
  • BMS保护板
  • 过温监测
  1. 布线规范
  • 粗线走大电流(≥18AWG)
  • 信号线与动力线分离
  • 使用屏蔽线(减少干扰)
  • 所有地线连接到公共地

完整电源方案框图:

[3S锂电池 11.1V 5000mAh]
         |
    [BMS保护板]
         |
    [主开关]
         |
    [自恢复保险丝 10A]
         |
    [电压监测分压器]
         |
    +----+----+----+
    |    |    |    |
  [12V] [5V] [5V] [5V]
    |    3A   2A   1A
  电机  树莓派 Arduino 雷达