[Arduino] 从零制作蓝牙小车
本文最后更新于 570 天前,其中的信息可能已经有所发展或是发生改变。

0 序言

制作小车前,我们需要先明确自己的需求:

  • 通信:BT-04 蓝牙模块
  • 电机驱动:L298N电机驱动模块
  • 底盘架构:三个轮子,呈三角形排布。其中两个轮子与电机相连,另一个为万向轮。

1 小车安装


2 蓝牙通信

2.1 数据包结构

小车计划采用“蓝牙调试器”软件的专业调试模式进行控制:软件链接。我们需要先熟悉该软件发送的数据包结构。

包头原数据校验包尾
1字节自定义1字节1字节
0xA5自定义“原数据”所有字节之和的低8位0x5A
数据包结构

传输的数据类型及位数我们可以在软件中自行设置。

2.2 Arduino代码

基础配置信息

// 头文件
#include "HardwareSerial.h"

// 数据长度设置
#define rxDataLenth 5   // 包头(1) + 前进(1) + 转向(1) + 校验(1) + 包尾(1)
#define bufferSize 10   // 缓存区长度

bool isReading = false;
int  length    = 0;
char rxHead    = 0xA5;  // 包头
char rxTail    = 0x5A;  // 包尾

数据包结构体

typedef struct {
  int go;   // 前进 -128 ~ 127
  int turn; // 转向 -128 ~ 127
} rxTypedef;

初始化函数

BT-04蓝牙模块的波特率为9600 bit/s,串口的波特率应与蓝牙模块相同,设置为9600 bit/s

void bluetoothInit(rxTypedef* remoteInfo) {
  // 初始化数据
  remoteInfo->go = 0;
  remoteInfo->turn = 0;
  // 启动串口 波特率设置为9600 bit/s
  Serial.begin(9600);
}

串口数据接收函数

每当Arduino接收到数据时,会调用 serialEvent函数,我们在该函数中对接收到的数据进行处理。

char rxBuffer[bufferSize]; // 缓存接收数据

void serialEvent() {
  // 调用serialEvent函数时可能已经读入多个数据,我们需要对当前已接收的所有数据进行处理
  while (Serial.available()) {
    char tmp = Serial.read(); // 从串口中读入一帧数据
    if (tmp == rxHead) {      // 如果是包头,则开始记录数据
      length = 0;
      isReading = true;
    } else if (isReading == false) {
      // 如果既不是包头,也不在读数据,则忽略这一帧数据
      continue;
    }

    // 将数据存入缓存数组
    rxBuffer[length++] = tmp;

    if (tmp == rxTail) {  // 如果是包尾,则停止记录数据,并开始解包操作
      isReading = false;
      remoteInfoUpdate(); // 解包函数
    } else if (length >= bufferSize) { // 长度超出缓存区长度接收异常,停止接收并清空缓存
      length = 0;
      isReading = false;
    }
  }
}

数据包解包函数

从缓存数组中取出每个变量的数据。此处未进行数据校验。

void remoteInfoUpdate() {
  if (length != rxDataLenth) { // 数据包长度检测
    return;
  } else {
    remoteInfo.go = rxBuffer[1];
    remoteInfo.turn = rxBuffer[2];
  }
}

至此,蓝牙通信的有关代码全部完成。


3 电机驱动模块

3.1 L298N模块的使用

这是一个L298N电机驱动模块的图片。你可能会觉得接口有一点点多。但其实搞清楚它的控制原理后就能很快完成接线。

输出A输出B两个接口分别与小车的两个电机相连(不用区分正负);12V供电接口与12V锂电池正极相连;GND接口同时与电池负极和Arduino的GND接口相连;5V供电接口与Arduino的Vin接口相连。


对于通道A使能、逻辑输入这几个接口,网络上的文章描述的都较为复杂。此处我会给出一个更简单的理解方式。注:由于通道AB的功能相同,下文只对一个通道进行描述。

图中的两个5V针脚会恒定输出5V电压;两个使能针脚可以理解为开关,当接到5V电压时,对应通道打开;1、2针脚用于控制电机的转动方向,1、2针脚分别为高电平时电机会转动且转动方向相反,其他情况电机不转动。


因此,如果我们想利用PWM来对电机进行调速,我们有两种接线方式:

3.2 接线方式一(不推荐)

我们不拔除通道使能5V之间的跳帽。这种情况下,通道会一直保持打开状态。我们分别将1、2两个针脚接到Arduino的两个PWM接口,通过分别控制1、2两个针脚对应的输出来控制电机旋转方向和速度。显然,这种接线方式会占用较多的PWM引脚(对于每个通道需要占用两个PWM引脚),但不需要占用普通引脚。在PWM引脚较少的情况下不推荐使用这种方案。

3.3 接线方式二(推荐)

拔除通道使能5V之间的跳帽,将使能针脚与Arduino的PWM引脚相连、1、2两个针脚与Arduino的两个普通引脚相连。这是,我们可以通过PWM引脚来控制通道的开关,用于调速;控制1、2两个针脚的电平来控制电机的转动方向。对于每个通道,这种接线方式只需要占用一个PWM引脚,但是会占用两个普通引脚。我们需要根据实际的引脚数量在两种接线方式中进行权衡。本文接下来的代码部分以第二种接线方式为例

3.4 Arduino代码

电机基础信息配置

// 电机引脚设置
#define Motor1_M1_Port 12
#define Motor1_M2_Port 13
#define Motor1_PWM_Port 3

#define Motor2_M1_Port A4
#define Motor2_M2_Port A5
#define Motor2_PWM_Port 11

电机初始化函数

void motorInit() {
  // 设置引脚模式
  pinMode(Motor1_M1_Port, OUTPUT);
  pinMode(Motor1_M2_Port, OUTPUT);
  pinMode(Motor2_M1_Port, OUTPUT);
  pinMode(Motor2_M2_Port, OUTPUT);

  // 将引脚置于低电平
  digitalWrite(Motor1_M1_Port, LOW);
  digitalWrite(Motor1_M2_Port, LOW);
  digitalWrite(Motor2_M1_Port, LOW);
  digitalWrite(Motor2_M2_Port, LOW);
}

电机速度设置函数

void motorSetSpeed(int v1, int v2) {
  // 设置转动方向
  if (v1 >= 0) {
    digitalWrite(Motor1_M1_Port, LOW);
    digitalWrite(Motor1_M2_Port, HIGH);
  } else {
    digitalWrite(Motor1_M1_Port, HIGH);
    digitalWrite(Motor1_M2_Port, LOW);
  }
  if (v2 >= 0) {
    digitalWrite(Motor2_M1_Port, LOW);
    digitalWrite(Motor2_M2_Port, HIGH);
  } else {
    digitalWrite(Motor2_M1_Port, HIGH);
    digitalWrite(Motor2_M2_Port, LOW);
  }

  // 设置速度 通过PWM控制
  analogWrite(Motor1_PWM_Port, abs(v1));  // Value = 0 ~ 255
  analogWrite(Motor2_PWM_Port, abs(v2));  // Value = 0 ~ 255
}

电机控制函数

void motorCtrl(int go, int turn) {
  if (turn < 0) {
    motorSetSpeed(go * 2 + turn, go * 2);
  } else {
    motorSetSpeed(go * 2, go * 2 - turn);
  }
}

至此,电机的驱动代码全部完成。


4 setup函数和loop函数

setup函数只会运行一次。在此函数中,我们进行各类初始化操作。

void setup() {

  // 初始化
  motorInit();
  bluetoothInit(&remoteInfo);
}

loop函数会循环运行。在此函数中,我们执行需要循环的操作。

void loop() {
  // 电机控制
  motorCtrl(remoteInfo.go, remoteInfo.turn);
  delay(100);
}


欢迎通过E-mail:blog#drinkcat.com 与我进行交流。
关注我的微信订阅号:饮猫DrinkCat在BIT,即是对我最大的支持~


暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇