Nybble程序注意事项
(1)串口监视器换行问题
程序框图
(1)WriteInstinct工程框架
主框图
下面是代码块与上述程序流程图中功能对应:
writeConst()函数向片上EEPROM写数据(a)(具体的代码在(a)文件夹中)
既然要分析writeConst()这个函数的代码,那么我们直接把这个函数里面的语句掏出来放在setup()函数里面,把其余的代码(妨碍我们的代码)全部删掉(后续都是这样处理的),后续只对setup()函数和loop()函数进行处理,OpenCat.h和Instinct.h里的代码语句并不会造成影响,当需要时我们把它拿出来放到setup()或者loop()函数中:
首先,程序运行到11行的for循环之后,将会在片上EEPROM里面把音符数据存储进去,存储结构如下图,假设excel表格每行10个共102行零4个即1024个单元格为片上EEPROM的1024字节:
以下为存储语句在EEPROM里的存储过程
存储完成之后,继续一个大的for循环,存储接下来的数据:
以下为存储语句在EEPROM里的存储过程(仅仅以for循环的第一次循环为例,即i=0时):
提示音相关函数及其使用(b)(具体的代码在(b)文件夹中):
以下3个函数是专门为蜂鸣器设计发声的函数,注释都非常清晰,大家先仔细看代码和注释,比较容易看懂的,然后可以在setup()函数和loop()函数里面随便调用更改参数查看效果:
saveSkillInfoFromProgmemToOnboardEeprom()函数从Progmem中复制运动技能数据到板载EEPROM(c)(具体的代码在(c)文件夹中)
首先我们先来看一下整体框架,然后再具体看代码:
同样的,我们还是为了方便起见,将saveSkillInfoFromProgmemToOnboardEeprom()函数里的语句掏出来,不对其余两个文件做处理(不影响我们看代码,我们只看setup()和loop()函数):
这里的逻辑框架基本已经看明白,也就是说程序代码将会把技能的名字长度、技能名和类型信息存入片上的EEPROM里面,然后如果我们选择更新本能,那么将会调用EEPROMWriteInt()和copyDataFromPgmToI2cEeprom()函数把本能的数据存储在I2C EEPROM里面同时将数据所在I2C EEPROM里的地址存入片上EEPROM便于索引,代码语句基本上都能够看懂。
上面的程序里面只剩下两个函数我们只知道功能而不知道具体代码是怎么执行的:
EEPROMWriteInt(SKILLS + skillAddressShift, i2cEepromAddress);
copyDataFromPgmToI2cEeprom(i2cEepromAddress, (unsigned int) progmemPointer[s]);
EEPROMWriteInt()函数:在EEPROM中写入Int类型数据。©DataFromPgmToI2cEeprom()函数:从pgm中拷贝数据到I2C EEPROM(d)(具体的代码在(d)文件夹中)
这里两个函数的功能都已经介绍过了,接下来我们去看看两个函数具体的代码和执行过程(一样的,把这两个函数放在setup()函数中):
这个EEPROMWriteInt()函数还是比较好理解的,就是把一个2字节的int类型数据(一共16位)拆分成了两个byte类型的数据lowByte和highByte。
highByte存储地址的高位,lowByte存储地址的低位。>>或<<是一个移位操作。
假设一个地址位256即:0000 0001 0000 0000则高位为 1 低位为0。第一行的代码使得该数字与0xFF进行逻辑运算 0000 0000 1111 1111 进行与运算,可以看到低位变成了0
然后进行高位运算,首先左移8位 0000 0000 0000 0001,然后跟0xFF进行与运算
0000 0000 1111 1111可以看到,最终高位得1
因此最后将highByte存为1,低位lowByte存为0。
读取的时候是一个逆过程,大家可以琢磨琢磨,不再赘述。
大家可以尝试在任意的地方存储一个int类型数据试一试,看能不能正常读写,注意地址位0~1022,每个数据占两个字节,不要让两个数据发生重叠存储:
然后我们具体看copyDataFromPgmToI2cEeprom()这个函数:这个函数负责将程序中的步态数据一个一个拷贝到板载的I2C EEPROM里,是WriteInstinct.ino里面可以说是最重要的地方,看懂了这里,基本上这个工程已经看懂了一大半。
IIC EEPROM是逐页存储的,因此每存储一页之后,都需要重新开启一次新的存储,写入存储地址的高位和低位。
assignSkillAddressToOnboardEeprom()函数:给新技在片上EEPROM分配数据地址:(e)(具体的代码在(e)文件夹中)
以下为具体的代码操作过程:
然后,从主程序框图看,就是开始初始化舵机驱动芯片PCA9685,然后控制小猫复位了。
初始化舵机驱动芯片PCA9685是比较容易懂的,控制小猫复位涉及到字符串指令“rest”的命令解析,数据加载和舵机控制,我们放在nybble工程里面详细讲解。下面loop函数里面有同样的功能,也放在nybble工程讲解。接下来我们这个工程主要还剩下一个校准mpu6050的代码没有看,接下来,我们主要弄懂这部分代码:
校准mpu6050:
mpu6050的6轴读取数据是有原始误差的,因此我们需要通过设置校准数据来平衡掉这些误差:
通过下面的图片可知,offset设置的值是不能直接拿原始数据设置的,作者在程序中把数据除以8得到的商拿来设置。
好了,这些知识知道了之后,我们去看具体的代码和流程:
看流程图里面,主要有两个函数meansensors()和calibration(),还有一个存储数据的语句比较重要,下面我们依次读代码:
meansensors()函数功能比较简单,就是读取mpu6050的数据,一共读取1100组,抛弃掉前100组数据,将剩下的1000组6轴数据读取后求平均值,最终将6轴平均值数据存储在agMean[6]数组之中。
好了,至此我们基本将WriteInstinct这个工程全部看完(除字符串命令接受和解析执行这一块),所有用到的函数我们都了解它的功能,同时重要的代码我们也看了它们的含义。我们上述涉及到的代码,即便有些语句没有明白的话,肯定也是短短几句,可以再仔细看看。
(2) nybble.ino工程框架:
主框图
红外遥控命令与串口指令转换成字符串命令并执行相应命令。
setup()函数初始化
下面是流程图中功能与代码对应解析(对应于流程图中的setup()函数初始化操作):
loop函数简介:
对于主框架,我们大致认识了一下,setup函数里面的初始化代码比较简单,我们已经进行了注释和详解。loop函数的框架我们也已经认识了一下。接下来,我们详细看loop函数里面的代码具体是怎么执行的。
loop函数框架(已将loop函数里面有用的代码抽出)(f)(具体的代码在(f)文件夹中)
下方为代码与框架中流程对应解析:
接收字符串命令:
(1)checkBodyMotion()自主命令(后附例程,具体代码在(g)文件夹中)
理解了checkBodyMotion()函数之后,我们可以只保留这部分代码,看看checkBodyMotion()函数自主控制命令的具体效果,可以将命令的信息输出,打开串口监视器查看:
(2)红外接收转化命令(后附例程,具体代码在(h)文件夹中)
红外接收指令,然后转化成字符串这个比较简单,通过traslateIR()函数将红外接收到的信号转成字符串,然后再通过c_str()函数将字符串转成字符数组然后赋给newCmd,由下面的语句解析执行。
我们来具体看一下translateIr()函数,具体的解析串口指令执行的函数和语句我们在后面具体再看。
下面就是例程显示,当我们遥控器按下之后,就能在串口监视器输出相应的指令信息,由于当小猫在接收到指令后,有时候是运行一系列的姿势,而中间的姿势并没有赋值给newCmd而是直接通过名字的字符串来加载数据调用的,因此中间的姿势调用我们看不到,只能看到红外遥控转换成的字符串指令,已经达到了我们的目的(当有的红外遥控器键值在translateIR函数中没有对应的话,将会直接输出键值,方便我们修改程序):
(3)通过串口接收命令(后附例程,具体代码在(i)文件夹中)
通过串口接受命令,如果命令模式不为k,则都可以直接执行(解析命令并执行先不介绍),如果命令模式为k,则接着获取命令。
我们可以在例程中看到,loop函数中只剩下了串口接收字符串指令的代码,同时在程序后面又加入了输出串口命令信息的代码语句(当我们输入一个字符串指令之后,它会将我们的代码信息输出):
字符串命令解析并加载数据进行执行(k模式)(例如:当已经得到命令new_cmd=wk之后)
具体的根据命令加载数据的流程(motion.loadBySkillName(newCmd)具体的执行方式):
我们详细看一下lookupAddressByName()这个函数:
再详细看一下loadDataByOnboardEepromAddress()这个函数:
然后,我们再具体看loadDataFromI2cEeprom和loadDataFromProgmem这两个函数是如何从I2C EEPROM和PROGMEM里加载数据的:
从I2C EEPROM加载数据:
从PROGMEM加载数据:
transform函数和behavior函数:
为了方便控制小猫能够一次运动到某个帧(posture),作者定义了transform和behavior这两个函数,这个在通过红外遥控控制小猫的时候,由于有些动作就是几个posture之间的来回切换,因此我们能够经常看到这两个函数的调用。
posture和posture串主要通过transform和behavior两个函数完成,我们具体去看一下这两个函数。
transform函数:
behavior函数(behavior函数其实质就是根据不同的命令多次调用transform函数切换姿势,先将命令字符串做成一个指针,能够存储多个命令,然后一个一个命令拿出来执行):
字符串命令解析并执行(非k模式):
(4)小猫通过超声波与障碍物保持距离一定
我们可以调用原来的函数,通过new_Cmd加载gait的角度数据来让小猫运动。
由于字符串命令解析是根据标志位,newCmdIdx和具体字符串3者进行判断的。
标志位负责运动功能,newCmdIdx负责找到new_Cmd的数据地址的作用(只有当new_Cmd与last_Cmd不同时,才加载一次),new_Cmd负责改变运动状态。
因此我们想要让小猫调用原作者的功能块进行运动,那么仅需要通过合适的改变这3个全局变量即可。
记得newCmdIdx只有当new_Cmd与last_Cmd不同时,才置为非0,否则每次都根据new_Cmd加载数据,数据指针都指向开头,则每次都只是将第一个舵机转到第一个角度数据的位置,并不能运动。因此,当命令不同时,我们要把newCmdIdx置为非0,当加载了角度数据之后,马上再置为0。
如下为该3个变量的改变使得小猫前进
通过一个for循环,把整帧(16个关节角度写入舵机),虽然gait的帧数为8,但是部分舵机没有接,因此并不影响整体运动,仅影响未连接的4个舵机。
再通过一个for循环,将整个前进步态的43帧舵机角度数据全部循环一遍,则小猫刚好走了一步。
将其封装成为一个函数,通过参数传递运动的步数。
则封装好的函数如下:
默认参数为1,即默认一次走一步。
void qianjin(byte steps=1);
void houtui(byte steps=1);
void zuozhuan(byte steps=1);
void youzhuan(byte steps=1);
然后我们再将超声波测距的代码也封装成一个函数:
接着就是逻辑判断了,如果距离障碍物远了,那就前进一步,如果距离障碍物近了,就后退一步,如果距离障碍物在允许范围内,就静止不动。
只需要将下面红框里面的代码取消注释,然后将原本else里面执行的代码全部注释掉即可:
学习例程
(1) 控制RGB全彩二极管
RGB灯,又叫全彩二极管。首先我们要知道RGB LED的主要原理。RGB LED灯是通过混合红色、绿色和蓝色这三种基本颜色来发出不同的颜色。物理课上我们学过颜料三原色和色光三原色。就是通过混合这三种原色,按照一定的配比,从而得到任意颜色的颜料或者显示效果。所以它实际上由3个独立的LED组成,红色、绿色和蓝色包装在一个盒子里。这就是为什么它有4个引脚,3种颜色中的每一种都有一种引线,而RGB LED类型则有一种共用阴极或阳极。在本文中,我们这里使用的是共阴极。
如下图所示,我们可以在小喵的说明文档中找到这3个灯所连接的IO口,因此我们只需要控制IO口使得3个灯以任意的方式接通即可点亮这个RGB灯。
既然要控制3个灯以不同的配比来显示颜色,那么我们不妨从控制一个灯开始学习。从图中可以看到Red,green,blue这3个灯是接在8,9,10这3个IO口上的,因此我们随便找一个8号口,看它上面接的是什么颜色的灯。
高中课本上我们学过,如果想要一个led灯亮起来,他的正负极之间必须有电压(即电势差),因此我们只需要在正极将电平(逻辑电路中表示0和1,高电平通常为+5v或者+3v,具体依据自己使用的开发板)拉高,即让正极为+5v,负极由于已经接了GND,因此为0v,这样led灯的两端就有电势差了,即可点亮led,由于+5v的电压太高,因此直接通+5v的电压会烧坏led灯,因此我们串联一个电阻。
将上述代码烧录进板子即可看到如下现象:
如果我们想让其他两种颜色的灯点亮,则只需改变那个8号IO口为9或者10即可。
如果我们想让两个灯同时点亮,然后看他们混合之后的颜色,只需要将两个或3个灯同时点亮即可。
这样我们就两两组合可以发出另外3种颜色的灯光了,如果我们将3个灯都点亮的话,就可以组合出白色。
这样的话,我们的RGB灯才只能发出7种颜色的灯光,全彩二极管可以发出其它颜色的灯光吗?答案是肯定的!在我们上述的组合中,3种灯光的配比都是1:1的,如果我们让红色亮0.5,让蓝色全亮,那么该怎么做呢?事实上,arduino板子上面的3, 5, 6, 9, 10和11。将模拟值(PWM波)输出到管脚。可用于在不同的光线亮度调节发光二极管亮度或以不同的速度驱动马达。调用analogWrite()后,该引脚将产生一个指定占空比的稳定方波,直到下一次调用analogWrite()(或在同一引脚调用digitalRead()或digitalWrite())。 PWM的信号频率约为490赫兹。
analogWrite(pin, value)。Value为0~255。
那么,如果我们想要灯光闪烁,该怎么办呢?闪烁其实就是灯在打开和关闭之间的周期性切换,只需要在一个小周期里面连续开关灯即可实现。
(2) 控制蜂鸣器发声
下图为蜂鸣器的实物图片,在我们的小猫身上也能清晰的看到,小猫身上的蜂鸣器形状为扁的正方体,上面带一个小孔,这样发出的声音就能传播出来。
蜂鸣器是一个会发声的模组,在生活中也是比较常见的,能发出BB声音的大都是蜂鸣器。外观为下图所示,蜂鸣器发声的原理就是通过机械震荡产生的。接通电源后,振荡器产生的音频信号电流通过电磁线圈,使电磁线圈产生磁场,振动膜片在电磁线圈和磁铁的相互作用下,周期性地振动发声。这个原理我们初中或者高中物理课上大都学过。
在单片机应用的设计上,很多方案都会用到蜂鸣器,大部分都是使用蜂鸣器来做提示或报警,比如按键按下、开始工作、工作结束或是故障等等。
蜂鸣器主要分为两类:有源蜂鸣器和无源蜂鸣器。
有绿色电路板的一种是无源蜂鸣器,没有电路板而用黑胶封闭的一种是有源蜂鸣器。
无源这里的“源”不是指电源,而是指震荡源。也就是说,有源蜂鸣器内部带震荡源,所以只要一通电就会叫。而无源内部不带震荡源,所以如果用直流信号无法令其鸣叫,必须给一个一定频率的脉冲信号蜂鸣器才会发声。
有源蜂鸣器和无源蜂鸣器各有优缺点:
无缘蜂鸣器价格便宜,同时可以给定各种频率的脉冲信号,可以发出多来米发索拉西等各种音调,但是控制稍微复杂,直流电无法直接工作,必须要交流信号才行。改变单片机引脚输出波形的频率,就可以调整控制蜂鸣器音调,产生各种不同音色、音调的声音。
改变输出电平的高低电平占空比,则可以控制蜂鸣器的声音大小。
有源蜂鸣器控制简单,只要通电就会发出声响,但是音调不容易改变,由于内置振荡器,同时成本也更高。通过改变蜂鸣器两端电压,可以勉强改变蜂鸣器的音调,但不够灵活。
我们小猫上的蜂鸣器为有源蜂鸣器,只要一通电,就会发声。
注意:小猫身上的蜂鸣器固定焊接在单片机的5号IO口,因此,我们必须通过5号IO口来控制。
(3) 控制蜂鸣器发出不同声调并组合音乐
能BB的蜂鸣器就是好蜂鸣器,所以让它来段《两只老虎》的音乐确实有些勉为其难了。实现是可以的,音质是不咋样。但是我们还是想着手实现一下。怎么办呢?
arduino里面内置了非常多的基本的函数,供我们调用,我们可以通过调用tone函数来勉强改变声音的音调,有源蜂鸣器通过改变蜂鸣器两端的电压也能稍微改变蜂鸣器的音调,小猫中的喵叫函数就是通过这样的操作实现的。
具体我们可以看作者原本的程序,根据第一节的知识,我们知道analogWrite函数可以通过改变pwm波高电平占空比从而改变输出端的有效电压,可以看到下方红色圆圈圈到的语句,在一个for循环里面,amp不断增大,蜂鸣器两端的电压不断升高,因此我们听到的喵叫才有声调变化。
而如果我们想自己实现不同的音调,就不用这么麻烦,arduino内置的tone函数解决了这个问题。
tone()函数可以产生固定频率的PWM信号来驱动扬声器发声。发声时间长度和声调都可以通过参数控制。定义发声时间长度有两种方法,第一种是通过tone()函数的参数来定义发声时长,另一种是使用noTone()函数来停止发声。如果在使用tone()函数时没有定义发声时间长度,那么除非通过noTone()函数来停止声音,否则Arduino将会一直通过tone()函数产生声音信号。
语法
tone(pin, frequency)
tone(pin, frequency, duration)
参数
pin: 发声引脚(该引脚连接蜂鸣器)
frequency: 发声频率(单位:赫兹) – 无符号整数型
duration: 发声时长(单位:微秒,此参数为可选参数) – 无符号长整型
我们在调用tone函数的时候,最好带上duration参数,这样控制起来更加方便。
通过百度,我们了解到简谱的音调和机械振动频率之间有如下的对应关系。
这样,我们就可以使用tone函数来发出低中高3个音阶的各个音调了,已经听了好几遍,音色还是比较难听的。
既然我们现在都能发出各种音调了,那演奏音乐不是简单的很嘛?
只需要搜到两只老虎的简谱即可。
以下为具体程序,将简谱存储在了lh[16]数组里面
听着确实有些难听,真正的一首优美的音乐还需要对于节拍和高低音有着完美的控制,因此具体想要奏出好听的音乐还是需要好好研究的。
(4) 用超声波测量前方障碍距离
由于超声波指向性强,能量消耗缓慢,在介质中传播的距离较远,因而经常用于距离的测量。超声波测距的原理是利用超声波发射器向某一方向发射超声波,超声波在空气中传播,遇到障碍物就立即被反射回来,接收器收到反射波后,根据发射和反射的时长t,就能计算出障碍物的距离,即:s=340× t / 2 。
声波在空气中的传播速度为340m/s,测距精度为厘米级,若要准确测量,还需要根据环境温度进行修正。
模块工作原理
(1)TRIP引脚内部经10K电阻上拉,测量时将TRIP引脚拉低,然后给一个10us以上的脉冲信号。
(2)模块自动发送8个40khz的方波,自动检测是否有信号返回;
(3)有信号返回时,通过Echo引脚输出高电平,高电平的持续的时间就是超声波从发射到返回的时间。
因此我们只需要给Trig一个10us的高电平,然后检测echo引脚的高电平持续时间即可。对持续时间做相应的数据处理即可得到障碍物距离。
为什么拿pulseIn得到的数据除以58呢?首先音速在空气中传播速度343米/秒,那么音速走1厘米需要多久呢?经过换算为29.15微秒,也就是说每29.15微秒,声音传播1cm。那么我们用检测到的声音传播总时间/29.15即可知道声音传播了多少cm,由于这个距离实际上是所求距离的两倍(声波来回距离),因此我们还需再除以2即pulseIn/(29.15*2)=pulseIn/58.3约等于pulseIn/58。
我们将上述程序烧录进板子即可再串口监视器得到数据回传:(理论上来说,为了防止抖动,我们需要把不合理的数据剔除,以保证稳定性)
(5)用板载红外遥控接收头接收遥控器信号
在日常生活中我们会接触到各式各样的遥控器,电视机、空调、机顶盒等都有专用的遥控器,很多智能手机也在软硬件上对红外遥控做了支持,可以集中遥控绝大部分家用电器。本篇介绍红外遥控相关原理及应用,并且找到每个按键的键值。
1. 红外遥控原理
红外遥控主要由红外发射和红外接收两部分组成。
红外发射和接收的信号其实都是一连串的二进制脉冲码,高低电平按照一定的时间规律变换来传递相应的信息。为了使其在无线传输过程中免受其他信号的干扰,通常都将信号调制在特定的载波频率上(38K红外载波信号),通过红外发射二极管发射出去,而红外接收端则要将信号进行解调处理,还原成二进制脉冲码进行处理。
这些模块有3个引脚用于VOUT、VDD和地,因此在电路中非常容易使用它们。
2.找到每个遥控器按键的代码
在这部分中,我们要在Arduino和IR发送器、接收器之间建立连接。为此,我们首先需要知道遥控器上每个按钮的代码。通过按下每个按钮,特定信号发送到接收器并将显示在串行监视器窗口中。
以下为具体代码和注释:
需要注意的是,我们的小猫主板上红外接收头的信号线是焊上去的(在4号IO口),因此没办法改变,当我们自己DIY的时候,把信号线signal接到其他IO口的话,只需要改变第二行的宏定义即可,将4改成自己接的IO口数字)。
当我们将这个程序成功上传到板子上之后,打开串口监视器,然后我们就可以按遥控器的每一个按钮,看每一个按钮对应的键值具体时多少,我们可以使用十进制数(DEC)也可以使用十六进制数(HEX)。记得在十六进制数字前面加上0X,这样就表示这个数字是十六进制数了。
当我们得到每一个按键的键值之后(当然可以不得到所有的按键键值,我们只要找到需要用到的按键数量的键值即可),接下来就可以通过判断键值,进行相应的操作了。
(6) 通过红外遥控器控制RGB灯的颜色
既然我们已经得到了红外遥控器的按键键值,那么我们可以通过判断键值来做相应的操作,具体看下面程序,可以通过遥控器的1、2、3这3个按键改变RBG灯的3中原色,按其他任意按键关闭灯光。
(7) 学会向片上eeprom读写数据
在这里,我们通过两个程序将读写这个操作分开来,一个程序写入数据,另一个程序读出数据。至于为什么这么做,我们要测试一下断电之后数据是否还在,而断电之后我们只能读取数据,不能先写再读。
下面是写入一个字符:
然后,我们从这里读出来:
这里如果想要写入一个byte类型呢?我们只需要把’I’改为byte变量或者数字常量即可。
写入一个byte数字(为什么是byte而不是int呢?因为eeprom一次只能存储一个字节的数据,如果想要存入int类型变量(占两个字节),我们还需要具体写一个函数对数据进行处理):
将byte数据读出来并输出到串口:
打开串口即可看到结果。
(8) 学会向板载eeprom读写数据
AT24C32D存储芯片是Atmel的两线制串行EEPROM芯片,容量为4096 x 8bits,即32k bits
采用I2C通讯接口,我们板子上集成这颗芯片主要是存储小猫的步态(gait)和姿势(posture)数据,具体的数据存储结构我们可以在上述程序框图部分看到。这里我们直接看程序和注释,我们从芯片的0地址处读取5个字节数据,然后再读取5个字节数据,可以看到在第二次读取5个字节数据的时候,自动接着上面的5个字节继续读取了,也就是说,重复持续的读取数据时,我们不需要总是写地址,地址会自动下移。
由于我们的小猫身上的iic接口eeprom里面已经写入了数据,因此我们先学习读取数据来看看:
下载程序之后,打开串口可以成功的看到,我们读到了第一个技能的具体信息,跟Instinct.h文件中的数据一模一样,这就为接下来我们直接读取iic接口的eeprom数据进行步态控制做好了准备。
然后我们看写入数据的具体代码和注释:
打开串口,我们可以看到成功输出了存储的字符数组:
(9) 控制一个舵机(通过PWM波让舵机转起来)
舵机驱动原理
控制信号由接收机的通道进入信号调制芯片,获得直流偏置电压。它内部有一个基准电路,产生周期为20ms,宽度为1.5ms的基准信号,将获得的直流偏置电压与电位器的电压比较,获得电压差输出。最后,电压差的正负输出到电机驱动芯片决定电机的正反转。当电机转速一定时,通过级联减速齿轮带动电位器旋转,使得电压差为0,电机停止转动。
舵机的控制一般需要一个20ms左右的时基脉冲,该脉冲的高电平部分一般为0.5ms-2.5ms范围内的角度控制脉冲部分,总间隔为2ms。以180度角度伺服为例,那么对应的控制关系是这样的:
0.5ms--------------0度;
1.0ms------------45度;
1.5ms------------90度;
2.0ms-----------135度;
2.5ms-----------180度;
我们的小猫是通过PCA9685这块芯片来驱动舵机的,这块芯片采用iic通信接口和arduino连接,可以通过iic通信只用4根线就可以同时驱动16路舵机,这样我们就不需要通过arduino的IO口来驱动舵机了,省下了非常多的IO口可以给其他设备使用。
先看一下淘宝上的PCA9685模块吧:
这是一块常用的舵机驱动模块,下面是与arduino uno的iic通讯接口接线方式:
Arduino uno PCA9685
Gnd GND
+5V VCC
A5 SCL
A4 SDA
而我们通过PCA9685控制舵机的时候,pca9685设备的iic地址的分配是通过模块右上方的短接焊盘来确定的,一般情况下用默认地址0x40即可,仅当与其他设备地址冲突的时候,我们需要改变其地址。在我们的小猫主板上,PCA9685这个芯片集成在上面了,因此不需要接线,地址也按照默认地址即可。
PCA9685的PWM波控制精度是12位的也就是最大值位4096,即把一个周期的波分成4096分,我们通过设置高电平占比的最小值和最大值来控制舵机信号线的高电平宽度。
具体舵机工作的脉冲频率可以根据舵机厂商的手册使用,从作者给的例程中,我们可以看到舵机工作在60HZ的频率下,同时他把高电平占空比最小值和最大值设置位150和600.
我们可以得到具体角度映射:
150--------------0度;
262------------45度;
375------------90度;
487-----------135度;
600-----------180度;
通过PWM波让舵机转起来
在了解了舵机的驱动原理之后,我们可以通过更改PWM波的高电平占空比来控制舵机转动了。
通过角度0-180度让舵机转起来
刚刚我们是通过改变高电平占空比从150-600来让舵机从0°转到180°,然后再转回来,这对于我们过去学习的角度不太友好,那么我们能不能直接按照度数来旋转,把具体的pwm高电平占空比封起来呢?当然可以,我们需要做一个0-180度和150-600这两组数据的映射:
我们只需要将第一组数据0-180映射到第二组数据0-450,然后再在第二组数据加150基数即可。
在这里,我们将这个角度映射过程封成一个函数(当然,我们也可以把设置pwm波的语句也封装进去,这样就更加简洁了):
串口输入角度值,舵机旋转过去
既然我们已经会控制舵机旋转了,那么我们可以使舵机根据我们的具体指令进行工作,就像在校准舵机的时候一样,当在串口监视器输入m8 6发送之后,8号关节上连接的舵机就旋转到6度的位置。可能我们会有这样的疑问,既然舵机旋转角度是0-180度,那为什么作者的程序里面角度是-90~90度呢?这跟我们前面所用到的pwm跟角度数据映射是一个道理,我们完全可以把作者的舵机角度加90度,就可以将-90到90度映射到0-180度了。
我们现在尝试一下作者的思路,从串口输入一个角度数据,舵机旋转过去然后停在那里,为了便于学习,我们就不用作者的m指令,我们仅玩一个舵机做引导。
(10)用作者的步态角度数据来控制小猫前进
当我们自己写程序来控制小猫的时候,由于目前还不会自己规划轨迹生成舵机角度,因此,我们只能先用作者的舵机数据来控制。首先,我们先来学习直接把舵机角度数据存入变量来控制小猫,这简单许多,等我们了解了原理过后,再来从iic eeprom里面读取舵机角度数据来进行控制在。需要用到哪些数据呢?
1.前进的步态数据(很容易的能从Instinct.h文件中找到,我们复制出来做相应处理即可)
2.旋转方向数据(很容易的我们能在OpenCat.h文件中找到,可以复制过来做成公式用,也可以手动使用,我们这里先手动使用方便理解)
3.舵机调准数据,这个数据我们通过c命令从窗口能够查看的到,我们将其复制出来,做一个数组存储舵机的校准数据。
4.舵机角度映射,由于作者的舵机角度存储范围是-90~90度,而我们控制舵机的时候舵机角度是0-180度,因此我们需要将作者的角度数据加90.这样就把舵机角度从-90~90度映射到了0-180度。
5.关节映射表,我们以前驱动舵机的时候,直接输入了舵机再PCA9685芯片上的接口号,而这个接口号码和关节号码是不一致的,因此作者做了一个关节映射表(数组),通过数组的元素顺序将舵机接口号码映射到了关节号码上。(在OpenCat.h文件中很容易找到关节映射表这个数组)。
好了,这下我们就把需要的数据给完全找到了,那么直接看我们写好的代码和注释
好了,这下我们就正式学会了如何让小猫前进,虽然没有头和尾巴的运动,平衡性比较差,但是至少他已经会走路了。
但是,通过仔细的观察,我发现虽然小猫确实是在前进,但是舵机角度跟原版的好像不太一样,走路的时候晃动比较大,其实是当我把balance的姿势写入程序发现小猫站立并不理想-舵机角度不准才发现的。
然后我去仔细看了下作者的舵机设置,恍然大悟,原来作者用的是150度的舵机,难怪,因此,我们需要把舵机参数设置的和作者一样才行。
看下面设置好的程序
这下看来,前进的步态就已经调试完美了。
那么当我们把后退,左转,右转的功能按照程序里面都加入进去,那是不是就可以控制小猫前后左右走了?
答案是显而易见的,说干就干,我们先把前后左右的步态调试好,先试试走路正常不正常。
程序烧录之后,我们发现小猫完美运行,但是下面的编译提示显示我们内存占用太高,这也是为什么作者把步态数据存储到eeprom或者flash里的原因,不用定义变量存储数据,内存就不会占用太多,现在我们先不管这个,下一节我们学习如何将小猫的前后左右步态封装起来,通过红外遥控来控制小猫前后左右行走。
(11)用红外遥控器控制小猫前后左右
我们前面已经学习过红外遥控如何接收信号,也学过如何通过遥控器来控制RGB灯的颜色,那么接下来需要做的,就是将前后左右走封装成函数,如果接到红外信号,就调用函数即可。
由于内存不足,因此我把右转的步态帧数据一共注释掉了7帧才通过编译,注意注释的时候不要连片注释,隔行注释效果比较好。这个到时候看了运动学逆解就知道为什么了。
最好的办法还是不要把这些数据定义成全局变量,而是从iic eeprom里读取数据,我们可以每次读取一帧的数据,这样内存占用就不会那么大了。
(12)红外遥控串联姿势POSTURE
之前有详细介绍过,通过写入新姿势和用Nybble.ino串联姿势,并实现机器猫爬楼梯。现在我们可以考虑通过第(11)章介绍的方法来封装函数,并实现姿势串联。我们先来看两个姿势:
我们现在要把这两个姿势串起来。下面是封装的函数:
这里需要注意的是,前三个关节(脖子、脑袋、尾巴)需要单独语句执行,不能放到balance[]中进行16次循环。否则,猫只会抖动,把delay增加再多也没用。原因可能是当执行完前三个有效关节之后,后面1个无效位和4个无效关节会错误。执行了前三个关节之后,再用8次循环实现后八个关节的具体动作。
在void loop()函数里,我们需要在执行的函数cc()后面加上一句cmd=0,使机器猫执行完一次封装函数里的行为之后能够回到cmd=0定义的状态(在这里是站立平衡):
还有一点值得注意:
第一个动作后的delay,是收到信号和动作一之间的时间间隔;动作2之后的delay是动作1和动作2之间的间隔,以此类推。
(13)读取MPU6050的倾角数据
模组介绍:
首先,我们来简单的看一下小猫身上用的IMU单元,型号为mpu6050淘宝上能轻松搜索的到。加速度计主要是测量物体运动的加速度,陀螺仪主要测量物体转动的角速度。
MPU6050 IMU在单芯片上集成了一个3轴加速度计和一个3轴陀螺仪。因此,我们可以通过陀螺仪测得3个加速度计输出和3个陀螺仪输出。
该模组是一种基于MEMS(微机电系统)技术的传感器,它将加速度计和陀螺仪嵌入到一块芯片中,芯片使用的I2C通信协议。
MPU-6050 对陀螺仪和加速度计用ADC(数字模拟信号转换器),将其测量的模拟量转化 为可输出的数字量。为了精确跟踪快速和慢速的运动,传感器的测量范围都是用户可控的, 陀螺仪可测范围为±250,±500,±1000,±2000°/秒(dps),加速度计可测范围为±2,±4, ±8,±16g。
我们这里先不涉及到更改测量范围的操作。
单位换算
如需把原始的 16 位数据用重力加速度单位 g 表示,需要注意传感器的量程。默认测量范围是 ±2g,一共是 4g 的宽度,16 位数据的最大读数为 65536,除 4 就是 16384,即 1g 加速度对应的数值。打印前把数据除以 16384 即可得到对应单位为 g 的数值,角速度的单位转换也同理。
为了控制小猫的运动,我们需要得到小猫的姿态角,那么先来了解一下姿态角:
姿态角:
姿态角在控制,机械领域广泛使用;
这里,简单了解一下:
什么是姿态角(Euler角)?
yaw,pitch,roll的识别
工作原理:
加速度计采用压电效应的工作原理,就像上面的图片一样,在一个立方体的盒子里面有一个小球,盒子的四壁是用压电晶体材料,当盒子倾斜时,由于重力的作用,球就会向倾斜的方向移动,当小球碰到墙壁就会产生压电电流。盒子中有上下、左右、前后三对相对的墙壁,每一对墙对应于三维空间中的一个轴:X轴、Y轴、Z轴。根据压电壁产生的电流,我们就可以确定倾角的方向和大小。
现有的mpu6050模组集成了dmp(数据运动处理器),该单元可以将传感器产生的模拟信号处理成我们需要的数字信号,这大大减轻了单片机的运算量,使得我们可以非常方便的直接从模块上面读取获得加速度计和陀螺仪的6轴数据,即3轴的加速度和3轴角速度。
如下图我们可以清晰的看到,当小猫身体绕着y轴倾斜时,由于重力加速度将会在x轴和z轴产生两个加速度分量,我们从这个传感器里面能够一次性读出3轴加速度,因此,通过反三角函数便能够很轻松的计算出小猫绕着y轴的倾角。
具体计算方法为下式:
同样的方法,我们可以计算的到小猫绕着x轴的倾角。如下为具体的程序:
至此,我们已经可以获得小猫的倾角了,但是这样获取的倾角是非常粗糙的,有着多方面的误差,同时由于小猫运动的时候,自身是有一个加减速过程,因此会在传感器里产生一个除重力加速度g以外的另一个加速度,会让计算结果产生较大的误差。同时传感器制造出来的时候也有着多方面的误差,需要我们使用的时候进行校准和标定,之后使用数据的时候还要进行多种滤波算法提高数据的准确度,这方面有一些算法比较复杂,在这儿不做介绍。
由于我们从串口输出的数据仅仅能够看到角度数据的变化,直观性不够强大,因此我们可以通过数据可视化,能够实时将小猫的倾角与processing软件里的3维模型进行对接,有一个更直观的感受。
可以通过上图搜索到的文章进行具体设置,里面需要的文件都可以在Open cat文件夹中具体找得到。
(14)板载IIC eeprom里的舵机运动数据从哪来?运动学正逆解!
正运动学,就是通过我们驱动的舵机角度,求取小猫机器人的足端坐标。主要用于确定小猫足端的运动范围。
逆运动学,已知小猫的足端坐标,反过来求取关节舵机的转角。在多数情况下,我们通常用运动学逆解,首先将足端的运动轨迹曲线规划好,然后将曲线离散化处理,通常就是在上面均匀的找出一组点,来代替曲线,然后找到每个点的坐标,根据坐标最后算出舵机转角。
为了得到逆解的准确舵机角度数据,我将这只小猫的运动3维简图做了出来,方便我们学习。
可以看到小猫的足端中心的轨迹已经用草图画了出来-下方的那两个灰色闭环曲线。
下图是简化了的运动简图:
我们拿一条腿(左前腿)举例,
以下是θ1的计算方法(当我们把角度通过反三角函数计算出来以后,会发现是弧度值,因此需要将进行转换):
以下是θ2的计算方法:
然后,我们通过程序实现,来输出这一组角度,为了使用方便,我们输出的角度格式还是按照作者原来的格式输出,即每帧8个数据。
因为目前我们只是以一条腿举例,因此只有8号关节和12号关节的角度数据,在输出的时候只有两个数据,为了得到每帧8个角度数据,因此,可以把作者balance的数据进行拆分,补齐我们另外缺少的每帧6个数据,这样就得到了完整的步态数据。
当我们上传之后,会发现只有左前腿是在做我们规划的步态,其余腿保持balance的状态。
通过这个程序,我们就可以得到输出的步态帧的数据,具体数据打开串口就能看得到。
得到这一组数据之后,我们将其复制下来,然后找到第(10)节那里的用作者的步态角度数据控制小猫前进,替代原本的wk步态数据,记得改一下帧数,我们规划的步态是36帧的。
下载进去之后,我们便可以看到效果,但是目前就只有这一条腿能动,那么当我们把其他3条腿一起弄好,是不是就可以走了呢?显然是不是的,这里需要思考一下小猫是如何走路的,小猫走路的时候一般是对角步态,也就是说对角的两条腿是同步的,其实前腿比后退更早出脚更好,大家可以趴在地上试一试。通过反复的调试最终输出正确的数据格式及顺序!!
然后我们把新的步态导入,发现走的非常不错!
完美,至此,我们现在可以独立的设计一个步态,并且根据小猫脚的行走轨迹通过一些三角函数公式结算出来小猫运动的关节角度数据,从而通过这些数据来控制小猫。那么我们是不是早就想设计一些自己独有的步态呢?
(15)自己规划轨迹做新的步态和姿势
我们先来蹲起和前后左右扭一扭身体。为了实现这个步态,我们首先要规划好足端轨迹,这是一条铅锤直线,足端从直线的中点往下到最下端端点,然后再上来一直到最上端端点,然后再回到线段中点,这样的一个过程如果4条腿同步来做,就成了蹲起的步态。
那么我们将这条50mm的线段分成10端,把抽出来的所有的点依照顺序写入坐标数组,然后输出来各个关节的舵机数据。
蹲起角度数据解算(算法都是一样的):
一样的,将复制出来的数据进行替代,然后将duiqi.ino这个程序上传之后就可看到步态效果:
既然有蹲起了,那扭身的步态是不是很容易就得到了?扭身就是前面蹲下,后面起来,或者左边蹲下,右边起来,这样就是扭身的动作,所以,我们只需要改变前后左右的舵机关节数据的顺序即可:
扭身角度数据解算(算法都是一样的):
扭身动作的程序:
(16)从头到尾做自己的超声波避障程序
如果我们想要做出超声波避障的程序,那么主要需要超声波测距这个功能,同时还需要小猫能够前后左右行走的步态都要做出来。
既然我们在上一节里已经实现了前进这个步态,那么后退是不是也就能实现了?这个就像放电影的时候一帧一帧的图片一样,只要我们倒序播放,那小猫就是在后退了。
那么既然我们前进后退都能实现了,那么是不是也就能实现左右转弯了?
答案是肯定的,在这里要大致讲一下转弯的原理:
转弯主要分为两种方法:
1.差速转弯,包括汽车这些交通工具大都用的差速转弯,就是左右两边*转速不一样实现转弯的,里面都安装的有差速器。
当左边*走得快,右边*走得慢,就会右转。
当左边*走的慢,右边*走得快,就会左转。
2.通过改变左右两边驱动力来实现转弯,当左右两边驱动力大小或方向相反时,将会产生一个力矩,当我们改变小猫左右驱动力的方向时候,将会产生一个力偶矩,同时旋转方向是小猫四条腿的几何中心。
在这里,我们先通过第一种方式实现转弯,这种方式其实非常好理解,让右边两条腿走得快,左边两条腿走得慢,这样就完成了左转,同样的,让左边两条腿走得快,右边两条腿走得慢就能完成右转了。原理非常简单。
那么如何让左右两条腿走路快慢不一样呢?需要让他们走路的轨迹横向长度有所区别,这样走的步幅度就不一样了。因此,我们还需要再规划一个轨迹,学了前面的轨迹规划,现在再规划一条轨迹解算出舵机角度是不是很轻松了呢?
如下,这是新规划的小椭圆的轨迹,与大椭圆相比,长轴缩短了一半。这样的话,当我们让左边的腿走大椭圆轨迹,右边的腿走小椭圆轨迹,左边的腿走路速度就会更快,而右边的慢,就会向左转。依照我们之前学习的逆解程序和算法,我们同时把大椭圆轨迹和小椭圆轨迹离散化之后,将计算出来的舵机角度依次左右腿交错输出,这样就得到了左右转弯的数据。
然后,我们就可以对输出的数据进行相应的测试,可以看到文件夹里面有下方红色圆圈圈到的程序,只需要在程序里将舵机数据依次替换成上面逆解输出的两组数据,就可以看到左右转弯的现象。
由于每一步下去左右两边的步子只是差了18mm,因此相对于整个机身来说,转弯半径会很大,也就是说要走很多步才能转个弯。
因此,我们再规划一下轨迹,将这个椭圆横向压缩,压缩成一条直线,如下图这样压缩:
这样的话,就相当于是原地踏步了。也就是说当转弯的时候,一边的两条腿是正常前进,另一边为原地踏步,这个其实不用再次画图,只需要把原先小椭圆轨迹离散化之后的点的横坐标改成第一个点的横坐标即可。具体的数据获取可以通过nijie-zhuanwan-turbo这个程序获取
再次测试之后,发现效果非常棒,转弯半径基本在20cm以内,转个弯不用跑太久,因此我们可以把这组输出角度记下来,当作最终的转弯数据。
下面为测试程序(可以在(16)文件夹中找得到):
然后,我们尝试将前后左右函数封装起来,这样做避障和距离上的保持就非常方便了。
如下为封装好的函数,loop()函数里面默认为距离保持的程序,(超声波避障的被注释掉了)。如果你想切换到超声波避障的模式,可以把loop()函数里面现有的代码注释掉,然后将目前注释的代码给取消注释就可以了。
这个玩法目前还是比较多样的,我在程序里面仅仅是给出了向右避障的功能,那么我们是否可以改成向左避障呢?当然可以,这个具体要视情况而定。
那么我们可不可以自己判断是向右避障还是向左避障呢?当然可以,现在我们在探测前方障碍的时候,仅仅探测了正前方的障碍物,左右方向的障碍物是没有探测的,我们可以通过转动头部舵机,让小猫可以左右看看,哪边没有障碍物就往哪边走。这个大家可以自己做试试。
做点儿更好玩的