[电子技术]模型车


迄今为止用Arduino做出来的最复杂的项目。大学生估计会把这个当做毕业设计吧。不过电子技术远不止Arudino和模型车那么简单。之后就考虑直接用单片机了。比如电压显示那个项目。

20141117_172532

车侧面

20141117_172619

车正面

为了做车,至今为止所有的工具,材料。实际上到今天12月8日为止,3个盒子已经塞不下了,太多零散的东西了。

20141117_173528

20141117_173558

简单来说,模型车,核心功能是前进,后退,左右转,速度测量,距离探测。这些功能一上,基本上arduino所有IO接口都满了(当时我还不知道Ax可以当输出接口)。哦,距离探测到一定距离会自动停止,并开动蜂鸣器。简单来说就是防撞。
驱动芯片是L298,光耦。两路。车是4WD,左边两个马达一路,右边两个马达一路。
驱动电源是两节3.7V的锂电池。没用那种焊接好的。单独买电池盒和电池。焊接电池盒的接线和接口的时候差点把车给烧了……(好久没焊了)
电源买了好多东西。电池,电池盒,可以用来当充电器的电池盒,充电器等等。不过看到一节3.7V锂电池是手机电池板的2倍时就觉得自己的电池多伟大了……
电源线路是给3.7 x 2给L298供电,同时给Arduino供电。买了个3.5mm电源输出头,用尖嘴钳把另外半边剪了,焊接普通的线。当时不知道热缩管。知道的话,就不用弄那么多焊锡上去了。

20141118_204711

线几乎占满了。

20141118_204807

线太多了,有些凌乱。

控制方案,仍旧是BC04的蓝牙方案。之前写过上位机,这次相对轻松点。协议上给蓝牙通讯包加上同步头。实际上就是一段冗余数据,下位机在读到正式开始前不会处理。由于代码比较多,后来把下位机通讯这段放到单独的库中去了。

20141117_174704

速度显示。实际用的是中断计数。由于测速码盘精度相对高,有20个,两个中断一起上感觉不行。所以后来改成左一秒右一秒。
这个速度测试也弄了一个库。不过由于追加中断需要全局函数地址,所以严格上不算完整封装。

普通的操作,前进,后退,只要调整M1,M2的输出(各两个)即可。如果要处理速度的话,需要控制ENA,ENB。所以实际上要控制6个PIN。为了上位机控制简单。我设置类似-128为后退,PWM为128。0为停止。+255,前进,PWM最大(全速前进)。这段也写成了库,方便操作。
转弯的话,实际上控制两边速度。比如左转,左边速度降低,右边全速。不过实际要调校。这次我手动控制,要一定值后才有左转显示。感觉是车转弯内距什么的问题(我会开车的哈)。
顺便说一句,如果车子开不动,或者满了,赶紧充电吧。我调试了一天不到。这货把6400mAh用完了。我还以为是程序哪里写错了。

超声波测速,很常见的技术。实际使用时注意需要设置超时时间,否则车就像假死一下不接受你的命令。理论上一定时间超声波没返回的话代表前面有好大距离,所以没问题。另外实际中发现车子的响应波有突然的尖刺的感觉。手边没示波器,暂时还是算了。有可能是电路其他地方引起的。

最终实际的项目包含库有1个主文件,4个库,自己做的最大的项目了。不过也基于此次指导Arudino中如何处理多文件。之前看到的MWC的四叶飞行器控制板,感觉也有能做的可能。不过那货是直流转交流……

最终展示下主文件的内容,其他的库看看方法名应该就明白。

#include <hcsr04.h>
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
#include "motor_ctrl.h"
#include "serial_ctrl_server.h"
#include "speed_tester.h"

#define PROTOCOL_MODELCAR_TEST 1
#define PROTOCOL_MODELCAR_BASIC_OP 4
#define PROTOCOL_MODELCAR_SPEED 6
#define PROTOCOL_MODELCAR_FREE 7

#define MCO_ADVANCE 1
#define MCO_STOP 2
#define MCO_BACK 3

#define STATUS_STOP 100
#define STATUS_ADVANCE 101
#define STATUS_BACK 102

#define PIN_BUZZ 11

#define SPEED_TEST_INTERVAL 1000

const float speedK = 3.14 * 6.4 / 4;

// pinM11, pinM12, pinM1Pwm, pinM21, pinM22, pinM2Pwm
MotorCtrl motorCtrl(7, 8, 6, 9, 10, 5);
SerialCtrlServer serialCtrlServer;
HCSR04 hcsr04(4, 12); // pin trig, pin echo
SpeedTester speedTester;

LiquidCrystal_I2C lcd(0x27, 16, 2);

int workingStatus;

void setup() {
  motorCtrl.init();
  serialCtrlServer.init();
  hcsr04.init();
  
  speedTester.init();
  attachInterrupt(0, speedTestIncreaseRightCount, CHANGE);
  
  lcd.init();
  lcd.backlight();
  
  Serial.begin(115200);
  workingStatus = STATUS_STOP;
  
  pinMode(2, INPUT);
  pinMode(3, INPUT);
}

void loop() {
  checkDistance();
  serialCtrlServer.read();
  processCmd();
  speedTest();
}

void checkDistance() {
  float distance = hcsr04.getDistance(2320);
  if(distance >= 0 && distance < 40) {
    if(workingStatus == STATUS_ADVANCE) {
      stopMe();
      buzzWarning();
    }
  }
}

void speedTest() {
  switch(speedTester.check(SPEED_TEST_INTERVAL)) {
    case SpeedTester::TEST_SIDE_LEFT:
      detachInterrupt(0);
      attachInterrupt(1, speedTestIncreaseLeftCount, CHANGE);
      lcd.clear();
      lcd.print("LSV: ");
      lcd.setCursor(0, 1);
      lcd.print(speedTester.getLeftSpeed() * speedK);
      lcd.print("m/s");
      break;
    case SpeedTester::TEST_SIDE_RIGHT:
      attachInterrupt(0, speedTestIncreaseRightCount, CHANGE);
      detachInterrupt(1);
      lcd.clear();
      lcd.print("RSV: ");
      lcd.setCursor(0, 1);
      lcd.print(speedTester.getRightSpeed() * speedK);
      lcd.print("m/s");
      break;
  }
}

void speedTestIncreaseLeftCount() {
  speedTester.increaseLeftCount();
}

void speedTestIncreaseRightCount() {
  speedTester.increaseRightCount();
}

void processCmd() {
  if(serialCtrlServer.cmdAvailable()) {
    switch(serialCtrlServer.getProtocolType()) {
      case PROTOCOL_MODELCAR_TEST:
        serialCtrlServer.done(SerialCtrlServer::RETURN_CODE_OK);
        break;
      case PROTOCOL_MODELCAR_BASIC_OP:
        processModelCarBasicOpCmd();
        serialCtrlServer.done(SerialCtrlServer::RETURN_CODE_OK);
        break;
      case PROTOCOL_MODELCAR_SPEED:
        processModelCarSpeedCmd();
        serialCtrlServer.done(SerialCtrlServer::RETURN_CODE_OK);
        break;
      case PROTOCOL_MODELCAR_FREE:
        processModelCarFreeCmd();
        serialCtrlServer.done(SerialCtrlServer::RETURN_CODE_OK);
        break;
      default:
        serialCtrlServer.done(SerialCtrlServer::RETURN_CODE_UNKNOWN_PROTOCOL);
        break;
    }
  }
}

void processModelCarFreeCmd() {
  int pwm1 = serialCtrlServer.getPayloadByte(0);
  int pwm2 = serialCtrlServer.getPayloadByte(1);
  switch(workingStatus) {
    case STATUS_ADVANCE:
      motorCtrl.run(pwm1, pwm2);
      break;
    case STATUS_BACK:
      motorCtrl.run(-pwm1, -pwm2);
      break;
  }
}

void processModelCarSpeedCmd() {
  int pwm = serialCtrlServer.getPayloadByte(0);
  switch(workingStatus) {
    case STATUS_ADVANCE:
      motorCtrl.run(pwm, pwm);
      break;
    case STATUS_BACK:
      motorCtrl.run(-pwm, -pwm);
      break;
  }
}

void processModelCarBasicOpCmd() {
  switch(serialCtrlServer.getPayloadByte(0)) {
    case MCO_ADVANCE:
      if(workingStatus != STATUS_ADVANCE) {
        motorCtrl.run(180, 180);
        workingStatus = STATUS_ADVANCE;
      }
      break;
    case MCO_STOP:
      stopMe();
      break;
    case MCO_BACK:
      if(workingStatus != STATUS_BACK) {
        motorCtrl.run(-180, -180);
        workingStatus = STATUS_BACK;
      }
      break;
  }
}

void buzzWarning() {
  for(int i = 0; i < 3; i++) {
    delay(250);
    analogWrite(PIN_BUZZ, 128);
    delay(250);
    analogWrite(PIN_BUZZ, LOW);
  }
}

void stopMe() {
  if(workingStatus != STATUS_STOP) {
    motorCtrl.stop();
    workingStatus = STATUS_STOP;
  }
}