儿子最爱车车,给他做一个。

遥控小车也是互联网上资料最丰富的一个电子DIY项目类型,然后其综合性和复杂度算得上是对入门的一个检验。最重要的是,我儿子(虽然才两岁)对车车是真爱啊。与其商场里买一堆重复的车车,不如自己动手,打造自己的梦幻之车。我这算是给他先探探路了。

最终成品先放上来,这是一个支持红外遥控的小车车。要做这么一个车车,需要准备的东西不少:

  • TT直流电机四驱底盘套装:淘宝上买的,不到30,包含了四个电机和轮子,还有亚克力底盘。还有一些电源线,固定螺丝之类的。
  • 直流电机驱动模块:选择了大家用得最多的 293,两路的即可
  • 12V,6000 mAh大容量锂电池,解决续航焦虑
  • Arduino UNO 主板套装,咸鱼上买的二手的,还包含了红外接收器和遥控器以及其他常用的各种模块
  • 电源转接头和一进二出接线器若干
  • 快递纸盒子备用,还有双面胶,纳米胶什么的,辅助固定,还有螺丝刀,尖嘴钳之类的工具。

东西陆陆续续到货,底盘的组装就折腾了一个多小时。以后记得最好找店家要详细的说明书,不然涉及到这种结构性的组装,复杂程度会随着零部件的数量增加而指数增长。

然后需要注意的是,我买的这个套装里,电机是没有焊接电源线出来的,这里我还对照着B站教程学习焊锡,虽然跟焊物大帝的距离还有很遥远,但也掌握了基本的方法:1)注意去除导线和触电的氧化层,否则不沾锡;2)先导线浸没镀锡,再到触点端子上焊锡,一下就好,不要拖泥带水。

安装固定好四个轮子之后,可以指定一个车的方向,然后逐个轮子根据这个方向来确定正负极,再然后把同侧的两个轮子作为一组,分别合并正极和负极,插入到驱动模块的OUT端子中,螺丝刀拧紧。

再然后就是把 12V 电池的正极接入到驱动板的12V的输入端,负极接GND。Arduino UNO 控制板各种连线连好,然后就写代码了。

#include <SoftwareSerial.h>
int RX = 0;    
// arduino主板上的读取引脚
int TX = 1;    
// 主板上的发送引脚,跟蓝牙模块的RXD引脚连接
SoftwareSerial bluetoothSerial(RX, TX);    
// 定义软串口接收和发送引脚

int L1 = 2;
int L2 = 3;
int R1 = 4;
int R2 = 5;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  bluetoothSerial.begin(9600);   // 初始化蓝牙通信
  pinMode(L1, OUTPUT);
  pinMode(L2, OUTPUT);
  pinMode(R1, OUTPUT);
  pinMode(R2, OUTPUT);
}

void loop() {
  //put your main code here, to run repeatedly:
  if (bluetoothSerial.available()){
    // 手机上装一下蓝牙调试工具
    // 蓝牙模块名称为 JDY-16
    char cmd = bluetoothSerial.read();
    Serial.print(cmd);
    switch (cmd){
      case 'w':
        forward();
        break;
      case 's':
        backward();
        break;
      case 'a':
        left();
        break;
      case 'd':
        right();
        break;
      default:
        stop();
    }
    delay(50);
  }
}

void forward(){
  digitalWrite(L1, HIGH);
  digitalWrite(L2, LOW);
  digitalWrite(R1, HIGH);
  digitalWrite(R2, LOW);
}


void backward(){
  digitalWrite(L2, HIGH);
  digitalWrite(L1, LOW);
  digitalWrite(R2, HIGH);
  digitalWrite(R1, LOW);
}

void right(){
  digitalWrite(L2, HIGH);
  digitalWrite(L1, LOW);
  digitalWrite(R1, HIGH);
  digitalWrite(R2, LOW);
}

void left(){
  digitalWrite(L1, HIGH);
  digitalWrite(L2, LOW);
  digitalWrite(R2, HIGH);
  digitalWrite(R1, LOW); 
}

void stop(){
  digitalWrite(L1, LOW);
  digitalWrite(L2, LOW);
  digitalWrite(R1, LOW);
  digitalWrite(R2, LOW); 
}

手机上安装了蓝牙调试的app,连接上这个 JDY-16 的蓝牙,然后设置一些按钮快捷发送命令,例如 up = "wx",按下这个按钮就等于执行了一次 前进 的命令。但是蓝牙得用手机操作,已有的游戏手柄和蓝牙模块的连接还不知道怎么弄,网上靠谱教程较少,所以还是使用更简单的红外遥控,小朋友也更容易操作。

使用红外模块,需要下载对应的 library。不知道怎么回事,在 arduino IDE 中直接下载总是失败,看报错基本是 github连不上,干脆自己通过别的方法下载后,解压到 arduino
的 libraries 目录下。

然后重新打开 arduino IDE,查找这个 IRremote的示例,选择了 TinyIRReceiver 进行测试和修改。需要注意的是,这个包默认的红外信号引脚是 2,所以我把之前控制直流电机的四个引脚移动到 3-6。然后红外信号不同的按键对应不同的十六进制数值,这个可以在调试的时候在串口查看。我这里得到:

  • 下:0x52
  • 上:0x18
  • 左:0x8
  • 右:0x5A
  • OK: 0x1C

其他都基本插入到示例代码中指定部位即可完成。完整代码如下:

#include <Arduino.h>
#include "PinDefinitionsAndMore.h"
#include "TinyIRReceiver.hpp"

#if !defined(STR_HELPER)
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
#endif

int L1 = 3;
int L2 = 4;
int R1 = 5;
int R2 = 6;

void setup() {
    Serial.begin(115200);
    pinMode(L1, OUTPUT);
    pinMode(L2, OUTPUT);
    pinMode(R1, OUTPUT);
    pinMode(R2, OUTPUT);
    while (!Serial)
        ; // Wait for Serial to become available. Is optimized away for some cores.

#if defined(__AVR_ATmega32U4__) || defined(SERIAL_PORT_USBVIRTUAL) || defined(SERIAL_USB) /*stm32duino*/|| defined(USBCON) /*STM32_stm32*/ \
    || defined(SERIALUSB_PID)  || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_attiny3217)
    delay(4000); // To be able to connect Serial monitor after reset or power up and before first print out. Do not wait for an attached Serial Monitor!
#endif
    // Just to know which program is running on my Arduino
#if defined(ESP8266) || defined(ESP32)
    Serial.println();
#endif
    Serial.println(F("START " __FILE__ " from " __DATE__ "\r\nUsing library version " VERSION_TINYIR));

    // Enables the interrupt generation on change of IR input signal
    if (!initPCIInterruptForTinyReceiver()) {
        Serial.println(F("No interrupt available for pin " STR(IR_RECEIVE_PIN))); // optimized out by the compiler, if not required :-)
    }
#if defined(USE_FAST_PROTOCOL)
    Serial.println(F("Ready to receive Fast IR signals at pin " STR(IR_RECEIVE_PIN)));
#else
    Serial.println(F("Ready to receive NEC IR signals at pin " STR(IR_RECEIVE_PIN)));
#endif
}

void loop() {
    if (TinyIRReceiverData.justWritten) {
        TinyIRReceiverData.justWritten = false;
#if !defined(USE_FAST_PROTOCOL)
        // We have no address at FAST protocol
        Serial.print(F("Address=0x"));
        Serial.print(TinyIRReceiverData.Address, HEX);
        Serial.print(' ');
#endif
        Serial.print(F("Command=0x"));
        Serial.print(TinyIRReceiverData.Command, HEX);
        if (TinyIRReceiverData.Flags == IRDATA_FLAGS_IS_REPEAT) {
            Serial.print(F(" Repeat"));
        }
        if (TinyIRReceiverData.Flags == IRDATA_FLAGS_PARITY_FAILED) {
            Serial.print(F(" Parity failed"));
#if !defined(USE_EXTENDED_NEC_PROTOCOL) && !defined(USE_ONKYO_PROTOCOL)
            Serial.print(F(", try USE_EXTENDED_NEC_PROTOCOL or USE_ONKYO_PROTOCOL"));
#endif
        }
        Serial.println();
    }
    /*
     * Put your code here
     */
    switch (TinyIRReceiverData.Command){
      case 0x18:
        Serial.println("Up");
        forward();
        TinyIRReceiverData.Command = 0x1c;
        //重置信号,否则一直保持 up 输出。
        break;
      case 0x52:
        Serial.println("Down");
        backward();
        TinyIRReceiverData.Command = 0x1c;
        break;
      case 0x8:
        Serial.println("Left");
        left();
        TinyIRReceiverData.Command = 0x1c;
        break;
      case 0x5A:
        Serial.println("Right");
        right();
        TinyIRReceiverData.Command = 0x1c;
        break;
      default:
        // Serial.println("Stop");
        stop();
    }
    delay(50);
}

/*
 * Optional code, if you require a callback
 */
#if defined(USE_CALLBACK_FOR_TINY_RECEIVER)
/*
 * This is the function, which is called if a complete frame was received
 * It runs in an ISR context with interrupts enabled, so functions like delay() etc. should work here
 */
#  if defined(ESP8266) || defined(ESP32)
IRAM_ATTR
#  endif

void handleReceivedTinyIRData() {
#  if defined(ARDUINO_ARCH_MBED) || defined(ESP32)
    /*
     * Printing is not allowed in ISR context for any kind of RTOS, so we use the slihjtly more complex,
     * but recommended way for handling a callback :-). Copy data for main loop.
     * For Mbed we get a kernel panic and "Error Message: Semaphore: 0x0, Not allowed in ISR context" for Serial.print()
     * for ESP32 we get a "Guru Meditation Error: Core  1 panic'ed" (we also have an RTOS running!)
     */
    // Do something useful here...
#  else
    // As an example, print very short output, since we are in an interrupt context and do not want to miss the next interrupts of the repeats coming soon
    printTinyReceiverResultMinimal(&Serial);
#  endif
}
#endif


void forward(){
  digitalWrite(L1, HIGH);
  digitalWrite(L2, LOW);
  digitalWrite(R1, HIGH);
  digitalWrite(R2, LOW);
}


void backward(){
  digitalWrite(L2, HIGH);
  digitalWrite(L1, LOW);
  digitalWrite(R2, HIGH);
  digitalWrite(R1, LOW);
}

void right(){
  digitalWrite(L2, HIGH);
  digitalWrite(L1, LOW);
  digitalWrite(R1, HIGH);
  digitalWrite(R2, LOW);
}

void left(){
  digitalWrite(L1, HIGH);
  digitalWrite(L2, LOW);
  digitalWrite(R2, HIGH);
  digitalWrite(R1, LOW); 
}

void stop(){
  digitalWrite(L1, LOW);
  digitalWrite(L2, LOW);
  digitalWrite(R1, LOW);
  digitalWrite(R2, LOW); 
}

使用简易红外遥控器玩了一下,感觉明显不如蓝牙的响应灵敏,而且超过三米距离就不太能控制了。不过我觉得这个如果是在家里给小朋友玩玩已经足够了。

最后修改:2024 年 09 月 06 日
请大力赞赏以支持本站持续运行!