最近帮朋友制作了一个小项目——仿生螃蟹。该项目具有俩个模式,一个是自动运行模式,一个是遥控模式。经过沟通,发现项目对于遥控的要求不高,于是推荐他们整体使用了红外遥控,毕竟红外遥控成本更低,开发上也比较省事(毕竟是朋友,而且项目的难度很简单,经费方面你们懂的)。
首先来介绍一下项目的使用的硬件。
综合项目的要求,包含传感器较少,对于数据的处理量不大,所以选择易于开发的Arduino单片机。一开始的时候,准备使用Arduino nano单片机,nano型号小巧,比较符合要求,后来发现,通过nano控制6个舵机,2个电机有点困难,于是使用了Arduino uno单片机。
其余硬件非常普通,6个舵机,分别控制仿生螃蟹臂的各个关节运动,通过2个电机控制腿的运行,至于8个腿直接是如何连接的,俩个电机如何实现8条腿的控制,这个需要找机械方面专业人员进行解读,本文只说程序方面。6个舵机、2个电机的供电是无法使用板子进行供电,所以做了一块电源板,电源板由7V电池直接供电。电机通过L298N直接控制。
总结一下,硬件清单如下:(1)arduino uno开发板一块(2)L298N驱动板一块(3)电源板一块(4)舵机6个(5)电机2个。
通过上面的内容,对硬件有了一个基本了解,接下来进入我们的正题。
首先介绍一下我的开发环境,这里使用的是VSCode安装相关插件来开发的,个人感觉,使用ArduinoIDE开发少量代码还行,代码较多的,ArduinoIDE的布局看起来确实不舒服(毕竟我是搞java的,javaide看多了,突然看ArduinoIDE有点不习惯),所以配置了vscode来使用。Vscode开发页面如图1所示,ArduinoIDE如图2所示。当然,还是看个人习惯,怎么舒服怎么来。
图1 vscode
图2 arduino ide
首先,我们来看一下项目的结构,如图3 项目结构所示。
图3 目结构
第一个.vscode大家无需管它,这个是使用Vscode创建项目后,插件给我们创建的俩个json文件,主要包含一些配置信息。下面才是项目的核心内容。
fspx.ino文件是用于Arduino编译的,大家熟悉的LOOP函数就在这,为了使项目代码结构明了,耦合度底,通过hwjm、motive、yk三个文件来对红外解编码、运动控制、遥控进行操作,这样有俩点好处,(1)条理明晰(2)有问题容易更改。
while (hwjm_ms==0)
{
hwjm_ms();
}
switch (msjm)
{
case 1:
while (1)
{
zyms();
}
break;
case 2:
while (1)
{
ykms();
}
break;
}
以上是主程序代码,只有10几行,某个功能出现问题,只需到某个功能函数里面进行更改即可,无需到处找。
首先,我们来看一下红外遥控。
红外遥控是有俩个组件,分别是红外发射器与红外接受装置,红外发射器通过发送指令,红外接受装置按照相关协议进行解码,从而读取内容。通过控制系统识别相关指令,执行相关代码。如果我们拿到陌生的红外遥控的话,我们可以通过以下代码,来实现红外编码的读取。
#include "IRremote.h"
IRrecv irrecv(hw);
decode_results jg;
void setup() {
Serial.begin(9600);
irrecv.enableIRIn();
}
void loop() {
Serial.println(irrecv.decode(&jg));
}
在本次项目中,就是通过这种模式去读取到每个按键的红外编码。
static int ydjm=0;
static int msjm=0;
在红外编码的.h文件中,创建了俩个全局变量,这样,在其他文件中将无法创建同名变量,并且只需导入相关头文件,即可实现变量名称的更改,至于这俩个变量的使用,在后文中解释。
enum jm{
FF30CF, FF18E7, FF7A85, FF10EF,
FF5AA5, FF629D, FF4AB5, FFE21D,
FFA857, FF906F, FF0C30, FFB04F,
FF02FD, FFC23D, FFA25D, FF22DD,
FFE01F, FF6897
}jm2;
//依次为红外遥控数字1、2、3、4、6、Mode、8、静音、vol-\vol+、RPT\U/SD、<<、>>,开关建、暂停建、EQ、0
红外解码通过switch case实现,switch后的条件只能使用枚举或整数。上面是枚举内容,如果你的红外遥控器与图4的一样,可以直接使用,节省时间(至于为什么没有按照遥控顺序来,当然是写的过程中,发现之前设计的按键不太符合使用习惯,进行了更改).
图4 红外遥控
在这里,可以将枚举更改为String类型的数组,但是,更改后只能使用if-else if-else来实现解码功能,但是在这里不推荐。
解释一下原因。如果我们程序的分支很少,那么if-else if分支语句是优先选择。但是如果是分支较多,switch的运行速度比if分支的要快。Switch会为每一个创建一个指针空间,直接根据指针进行读取。而if分支语句只能从头读取,直到找到。既然有指针,那么其就需要占用一定的空间,所以如果分支很少,大可不必使用,浪费空间。这里举个例子:假如你朋友邀请你到他家玩,你朋友告诉你他家在5楼,如果5楼只有俩户人家,你只需要随机敲一户的门即可知道你朋友家在哪,而如果5楼有20户,显然无法直接敲开,只能找物业或管理层去查询。前者相对于if-else if ,你只有实验一次即可,效率高。但是,20户,你至少要实验一次,而且一次就是你朋友加到概率是5%,显然效率不高,如果遇到暴脾气的户主,少不了一顿批。
在motive文件中,主要定义了各种运动模式,向左,向右(一开始是向前向后,后来想起,螃蟹只能横着走)、螃蟹臂的运动、螃蟹钳的运动。
#include "motive.h"
#include "Arduino.h"
#include "hwjm.h"
// 定义舵机对象
static Servo L0;
static Servo L1;
static Servo R0;
static Servo R1;
static Servo M0;
static Servo M1;
#define ang 5//此处数值决定各关节打开的角度,更改只需更改数字
// 各模块子函数
// 运动
void zy() {
digitalWrite(in1, LOW);
digitalWrite(in2, HIGH);
digitalWrite(in3, LOW);
digitalWrite(in4, HIGH);
ydjm=0;
}
这里提示大家,如果使用分模块开发,一定调用Arduino.h库,这样才可以使用一些arduino单片机的关键字,如digitalWrite的使用,否则会报错(不要问我怎么知道的)。
在本部分,通过宏定义定义了一个ang,至于这个的作用,后面代码就可以明白了。
这里首先列举一个左移的代码。左移我们控制的是螃蟹的移动,而螃蟹的移动是通过电机实现的,很多人会想到使用PWM控制,没错,如果只有左移,pwm可以完美解决,但是你没有见过只会向左走的螃蟹吧,所以还需要考虑右移。PWM无论如何更改占空比,都无法使得电机反转,只能更改转速(也就是螃蟹的移动速度),所以我们引入了L298N,通过对IN1、IN2、IN3、IN4给予不同电平而实现电流的反转,实现左右自由移动。至于结尾为什么有ydjm=0,大家可以联合上下文,猜一下。这里我们并没有对使能端进行使能,因为我们不需要调节螃蟹运动的速度。
//螃蟹左夹子,默认为舵机的获得角度为0-45度,0度为钳子完全夹住,45度全开
void xj_L1_K() {
if (L1.read() > 45) {
L1.write(45);
}
else {
L1.write(L1.read()+ang);
}
ydjm=0;
}
上面列举的这几行代码,大家通过注释应该已经明白它的功能了,这里主要说明俩个问题,首先是螃蟹钳子的控制。大家都吃过螃蟹,螃蟹的钳子开合是有限制的,不是任意角度的控制,所以,我在这里增加了角度控制,在这里,大家应该是看到了ang这个变量,也就是在开始时候,那个宏定义。我们可以想一下,不管是螃蟹的钳子还是螃蟹臂的运动,都是慢慢增加的,不是一下到位的,你在遥控的时候,如果只按了一下,结果直接一直张开,直到机械结构不在允许运动才停止,显然不符合我们的逻辑与实际功能。ang定义了这些机构每次运行的角度(也就是舵机转动角度),因为每一个机构都需要,但是如果需要调整,那不费老半天劲了,所以定义一个宏变量,只需更改一个数字,全局更改,如果有特殊的,只需特殊处理即可。其次,说明一下,这里为了保户舵机与机械外壳,大家可以增加一个蜂鸣器,当超过使用范围后,直接报警,提示操作者。当然,这个项目空间有限,只能放弃了。
void hwjm_ms(){
char jm1=irrecv.decode(&jg);
switch (jm1)
{
//自由运动模式
case FF30CF:
msjm=1;
break;
//遥控模式
case FF7A85:
msjm=2;
break;
}
}
这段代码是模式确认的一段,大家看到了我们之前代码中的全局变量了,我们通过解码,来获取相关指令,再对全局变量命名,从而在yk函数里面直接对数字进行对比,再寻找相关功能。解释一下,为什么要解码转变为数字,你想,如果你运行结束后,下次还要调用,但是在判断条件里面,都是固定的,那么我们只能实现单次遥控,只能单次运动,这不是开发了一只傻螃蟹。所以,这也可以解释,上面左移程序里面有一个赋值为0的步骤。
void loop() {
while (hwjm_ms==0)
{
hwjm_ms();
}
switch (msjm)
{
case 1:
while (1)
{
zyms();
}
break;
case 2:
while (1)
{
ykms();
}
break;
}
}
这段代码就是loop函数代码,看着很短,没有那么乱,但是麻雀虽小,五脏俱全。首先读取模式选择,活动模式(也就是遥控模式还是自由模式),确定模式后,除非重启或复位,否则将处于固定模式中。确定模式后,调用相关函数,解码红外编码,调用运动函数,执行动作。
这就是这个项目的相关内容,本文仅仅对相关内容进行简单介绍与关键代码说明,具体代码,已经在gitee仓库开源(git@gitee.com:luyuan125/arduinodome.git),https://gitee.com/luyuan125/arduino-dome
仓库地址如上,已经开源,后续有意思的项目代码将会统一上传至仓库,大家可以关注一下。
本代码是根据项目的实际情况进行调试的,如果大家使用,需根据自己项目的情况进行参数调整与更改。做为开发小白,本文只记录一下我开发的思路,如有问题,请大佬批评指正。
(因项目外观保密性,无法公开望大家谅解)