MiniQ 2WD Plus

概述

miniQ 2WD Plus是一款基于Atmega32u4单片机设计的机器人小车控制板,完全兼容Arduino Leonardo以及各种sheild扩展板接口。

小伙伴们可以在上面搭建各种传感器模块,更难得的是,模块还留有很多焊接口,再也不用自己去买万能板了!

miniQ 2WD Plus即可以作为桌面机器人小车的主控制器,搭建2轮自平衡机器人,也可以做为miniQ 2WD的端口扩展板,通过板子之间的Gadgeteer接口进行通信,怎么样?神奇吧!!!

性能描述

  • 主控芯片: Atmega 32u4
  • 加速度计芯片:ADXL345
  • 陀螺仪芯片: ITG3205
  • 数字IO口:23(其中D17是RX指示灯)
  • 5V数字/模拟口最大允许电流:40 mA
  • 兼容标准Arduino接口
  • 支持USB程序下载
  • 7路PWM通道
  • 具有1个xbee接口(Serial1)
  • 具有1个单总线RGB灯
  • 具有1个9g舵机安装孔
  • 具有万用板功能
  • 可直接安装到MiniQ 2WD和MiniQ 桌面机器人底盘上

管脚定义

上面的图片显示miniQ 2wd Plus控制器上所有的功能和扩展接口说明,其中包括:

  • 电源:使用USB供电或电源供电。
  • RGB灯珠:内置驱动IC-WS2811,采用单线式数据线控制
  • 模拟输入接口:完全兼容Leonardo
  • 数字接口:完全兼容Leonardo
  • 复位按键:采用微动独立按键,手感更舒适
  • 2个Gadgteer接口:用于连接各种Gadgteer接口的传感器,也可以用于连接miniQ 2WD进行通信控制
  • 加速度计芯片:采用ADXL345
  • 陀螺仪芯片: 采用ITG3205
  • Xbee接口: 用于无线数据传输
  • 预留TowerPro SG90舵机安装孔
  • 预留UART接口

控制器应用

使用教程

首先我们先单独学习上层板的应用。

加速度计&陀螺仪

MiniQ 2WD Plus集成加速度计,陀螺仪功能。加速度计&陀螺仪数据的读取是通过I2C获取的,输出的数据是相对于X,Y,Z轴的,如果想要获取相对于坐标系的角度值,则要进行换算。当然,由于加速度计过于灵敏,相信很多小伙伴也遇到这样的困难,模块一点儿小抖动,数据的拨动幅度就很大,这里用一个简单的例子,用陀螺仪的数据进行角度修正,并采用软件滤波器滤波,得到相对稳定的角度数据。实验结果可以通过串口助手观察到。左侧数据代表通过加速度计计算得出的角度,右侧为结合加速度计和陀螺仪最终得出的小车倾斜角度,为了尽量避免负数,我们假定水平放置的MiniQ 2WD Plus上层板角度为90°,使得得到的数据范围在0~180°之间。当然,你也可以修改显示的数据,以及角度范围。

样例代码

/***************************************************
 MiniQ 2WD plus (With Stainless Steel Probe)
 <https://www.dfrobot.com.cn/goods-1074.html>

 ***************************************************
 This example show how to use ADXL345 and ITG3205 sensor.

 Created 2016-1-15
 By Andy zhou <Andy.zhou@dfrobot.com>
 version:V1.0
 GNU Lesser General Public License.
 See <https://www.gnu.org/licenses/> for details.
 All above must be included in any redistribution
 ****************************************************/

/***********Notice and Trouble shooting***************
 1.Connection and Diagram can be found here
 <https://wiki.dfrobot.com.cn/_SKU_DFR0302_MiniQ_2WD_Plus>
 2.This code is tested on Arduino Uno, Leonardo, Mega boards.

 ****************************************************/
#include <Wire.h>
#include <math.h>
#define DEVICE (0x53)    //ADXL345 device address
#define TO_READ (6)        //num of bytes we are going to read each time (two bytes for each axis)

//ITG3205
#define ITGAddress   0x68    //ITG3205的I2C地址(AD0->gnd)
#define G_SMPLRT_DIV 0x15    //设置采样率的寄存器
#define G_DLPF_FS 0x16     //设置量程、低通滤波带宽、时钟频率的寄存器
#define G_INT_CFG 0x17     //设置中断的寄存器
#define G_PWR_MGM 0x3E    //设置电源管理的寄存器

float xGyro, yGyro, zGyro;      //存放角速度值,温度
int buff[6];                  //存放寄存器高低位值,X、Y、Z轴共6个
byte buff1[6];
// 陀螺仪传感器误差修正的偏移量
int g_offx = -35;
int g_offy = -9;
int g_offz = -30;

 //******卡尔曼参数************
float Gyro_y;        //Y轴陀螺仪数据暂存
float Angle_gy;      //由角速度计算的倾斜角度
float Accel_x;             //X轴加速度值暂存
float Angle_ax;      //由加速度计算的倾斜角度
float Angle;         //小车最终倾斜角度
//uchar value;                 //角度正负极性标记
float  Q_angle=0.001;
float  Q_gyro=0.003;
float  R_angle=0.5;
float  dt=0.01;                          //dt为kalman滤波器采样时间;
char   C_0 = 1;
float  Q_bias, Angle_err;
float  PCt_0, PCt_1, E;
float  K_0, K_1, t_0, t_1;
float  Pdot[4] ={0,0,0,0};
float  PP[2][2] = { { 1, 0 },{ 0, 1 } };

void writeRegister(int deviceAddress, byte address, byte val){
  Wire.beginTransmission(deviceAddress);
  Wire.write(address);
  Wire.write(val);
  Wire.endTransmission();
}

void readRegister(int deviceAddress, byte address) {
  Wire.beginTransmission(deviceAddress);
  Wire.write(address);
  Wire.endTransmission();
  Wire.beginTransmission(deviceAddress);
  Wire.requestFrom(deviceAddress, 6);

  int i = 0;
  while(Wire.available())
  { buff[i++] = Wire.read(); }
  Wire.endTransmission();
}

 /*****************************************
 * ITG3205
 * G_SMPLRT_DIV:采样率 = 125Hz
 * G_DLPF_FS:+ - 2000度/秒、低通滤波器5HZ、内部采样率1kHz
 * G_INT_CFG:没有中断
 * G_PWR_MGM:电源管理设定:无复位、无睡眠模式、无待机模式、内部振荡器
   ******************************************/
void initGyro(){
  writeRegister(ITGAddress, G_SMPLRT_DIV, 0x07); //设置采样率
  writeRegister(ITGAddress, G_DLPF_FS, 0x1E); //设置量程、低通滤波带宽、内部采样率
  writeRegister(ITGAddress, G_INT_CFG, 0x00); //设置中断(默认值)
  writeRegister(ITGAddress, G_PWR_MGM, 0x00);    //设置电源管理(默认值)

   //Turning on the ADXL345
   writeTo(DEVICE, 0x2D, 0);
   writeTo(DEVICE, 0x2D, 16);
   writeTo(DEVICE, 0x2D, 8);
}

float getGyroValues(){
  readRegister(ITGAddress, 0x1D); //读取陀螺仪ITG3205的数据
  xGyro = ((buff[0] << 8) | buff[1]) + g_offx;
  yGyro = ((buff[2] << 8) | buff[3]) + g_offy;
  zGyro = ((buff[4] << 8) | buff[5]) + g_offz;

  return xGyro;
}

float getadxvalues(){
  int regAddress = 0x32;    //first axis-acceleration-data register on the ADXL345
  int x, y, z;

 readFrom(DEVICE, regAddress, TO_READ, buff1); //read the acceleration data from the ADXL345
  //each axis reading comes in 10 bit resolution, ie 2 bytes.  Least Significat Byte first!!
  //thus we are converting both bytes in to one int
 x = (((int)buff1[1]) << 8) | buff1[0];
 y = (((int)buff1[3])<< 8) | buff1[2];
 z = (((int)buff1[5]) << 8) | buff1[4];

 float Rx = x * 0.0039 + 0.035;
 float Ry = y * 0.0039 + 0.035;
 float Rz = z * 0.0039 + 0.040;
// Rx = abs(Rx);
 float R = sqrt( Rx*Rx + Ry*Ry + Rz*Rz );
 float Axr = acos(Rx/R) * 57.2958;

 return Axr;
}


float kalmanUpdate(float Accel,float Gyro){
   Angle+=(Gyro - Q_bias) * dt; //先验估计

   Pdot[0]=Q_angle - PP[0][1] - PP[1][0]; // Pk-先验估计误差协方差的微分
   Pdot[1]=- PP[1][1];
   Pdot[2]=- PP[1][1];
   Pdot[3]=Q_gyro;
   PP[0][0] += Pdot[0] * dt;   // Pk-先验估计误差协方差微分的积分
   PP[0][1] += Pdot[1] * dt;   // =先验估计误差协方差
   PP[1][0] += Pdot[2] * dt;
   PP[1][1] += Pdot[3] * dt;

   Angle_err = Accel - Angle;        //zk-先验估计

   PCt_0 = C_0 * PP[0][0];
   PCt_1 = C_0 * PP[1][0];

   E = R_angle + C_0 * PCt_0;

   K_0 = PCt_0 / E;
   K_1 = PCt_1 / E;

   t_0 = PCt_0;
   t_1 = C_0 * PP[0][1];

   PP[0][0] -= K_0 * t_0;                 //后验估计误差协方差
   PP[0][1] -= K_0 * t_1;
   PP[1][0] -= K_1 * t_0;
   PP[1][1] -= K_1 * t_1;

   Angle        += K_0 * Angle_err;         //后验估计
   Q_bias        += K_1 * Angle_err;         //后验估计
   Gyro_y   = Gyro - Q_bias;         //输出值(后验估计)的微分=角速度
}

void setup(){
  Serial.begin(9600);
  Wire.begin();
  initGyro();
  delay(50);
}


void loop(){
  float Accel_x = getadxvalues();
  float Gyro_x = getGyroValues()/14.375;

  kalmanUpdate( Accel_x, Gyro_x );
  Angle = Angle + (((Accel_x-Angle)*0.5 + Gyro_y)*0.001);
  Serial.print(Accel_x);
  Serial.print("   ");
  Serial.println(Angle);
}

//---------------- Functions
//Writes val to address register on device
void writeTo(int device, byte address, byte val) {
  Wire.beginTransmission(device); //start transmission to device
  Wire.write(address);        // send register address
  Wire.write(val);        // send value to write
  Wire.endTransmission(); //end transmission
}

//reads num bytes starting from address register on device in to buff array
void readFrom(int device, byte address, int num, byte buff[]){
 Wire.beginTransmission(device); //start transmission to device
 Wire.write(address);        //sends address to read from
 Wire.endTransmission(); //end transmission
 Wire.beginTransmission(device); //start transmission to device
 Wire.requestFrom(device, num);    // request 6 bytes from device

 int i = 0;
 while(Wire.available()){    //device may send less than requested (abnormal)
   buff[i] = Wire.read(); // receive a byte
   i++;
 }
 Wire.endTransmission(); //end transmission
}

RGB控制

MiniQ 2WD plus 内置一个RGB灯珠WS2812B-4,其内置驱动IC-WS2811,采用单线通信方式控制。
样例展示灯珠多种颜色的变换、控制灯珠闪烁。实际上每个像素点都可显示三基色(RGB),每种颜色更可实现256级亮度显示,完成16777216种颜色的全真色彩显示,你可以在代码中修改三基色的数值来改变灯光色彩。

样例代码
    /***************************************************
     MiniQ 2WD plus (With Stainless Steel Probe)
     <https://www.dfrobot.com.cn/goods-1074.html>

     ***************************************************
     This example show how to control RGB LED.

     Created 2016-1-15
     By Andy zhou <Andy.zhou@dfrobot.com>
     version:V1.0

     GNU Lesser General Public License.
     See <https://www.gnu.org/licenses/> for details.
     All above must be included in any redistribution
     ****************************************************/

    /***********Notice and Trouble shooting***************
     1.Connection and Diagram can be found here
     <https://wiki.dfrobot.com.cn/_SKU_DFR0302_MiniQ_2WD_Plus>
     2.This code is tested on Arduino Uno, Leonardo, Mega boards.
     ****************************************************/
    #include <Adafruit_NeoPixel.h>

    #define PIN 4

    Adafruit_NeoPixel strip = Adafruit_NeoPixel(150, PIN, NEO_GRB + NEO_KHZ800);

    void setup() {
      strip.begin();
      strip.show(); // Initialize all pixels to 'off'
    }

    void loop() {
      // Some example procedures showing how to display to the pixels:
      colorWipe(strip.Color(255, 0, 0), 50); // Red
      colorWipe(strip.Color(0, 255, 0), 50); // Green
      colorWipe(strip.Color(0, 0, 255), 50); // Blue
      // Send a theater pixel chase in...
      theaterChase(strip.Color(127, 127, 127), 50); // White
      theaterChase(strip.Color(127,   0,   0), 50); // Red
      theaterChase(strip.Color(  0,   0, 127), 50); // Blue

      rainbow(20);
     // rainbowCycle(20);
     // theaterChaseRainbow(50);
    }

    // Fill the dots one after the other with a color
    void colorWipe(uint32_t c, uint8_t wait) {
      for(uint16_t i=0; i<strip.numPixels(); i++) {
          strip.setPixelColor(i, c);
          strip.show();
          delay(wait);
      }
    }

    void rainbow(uint8_t wait) {
      uint16_t i, j;
      for(j=0; j<256; j++) {
        for(i=0; i<strip.numPixels(); i++){
          strip.setPixelColor(i, Wheel((i+j) & 255));
        }
        strip.show();
        delay(wait);
      }
    }

    // Slightly different, this makes the rainbow equally distributed throughout
    void rainbowCycle(uint8_t wait) {
      uint16_t i, j;
      for(j=0; j<256*5; j++) { // 5 cycles of all colors on wheel
        for(i=0; i< strip.numPixels(); i++) {
          strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
        }
        strip.show();
        delay(wait);
      }
    }

    //Theatre-style crawling lights.
    void theaterChase(uint32_t c, uint8_t wait) {
      for (int j=0; j<10; j++) {  //do 10 cycles of chasing
        for (int q=0; q < 3; q++) {
          for (int i=0; i < strip.numPixels(); i=i+3) {
            strip.setPixelColor(i+q, c);    //turn every third pixel on
          }
          strip.show();
          delay(wait);
          for (int i=0; i < strip.numPixels(); i=i+3) {
            strip.setPixelColor(i+q, 0);        //turn every third pixel off
          }
        }
      }
    }

    //Theatre-style crawling lights with rainbow effect
    void theaterChaseRainbow(uint8_t wait) {
      for (int j=0; j < 256; j++) {     // cycle all 256 colors in the wheel
        for (int q=0; q < 3; q++) {
            for (int i=0; i < strip.numPixels(); i=i+3) {
              strip.setPixelColor(i+q, Wheel( (i+j) % 255));    //turn every third pixel on
            }
            strip.show();
            delay(wait);
            for (int i=0; i < strip.numPixels(); i=i+3) {
              strip.setPixelColor(i+q, 0);        //turn every third pixel off
            }
        }
      }
    }

    // Input a value 0 to 255 to get a color value.
    // The colours are a transition r - g - b - back to r.
    uint32_t Wheel(byte WheelPos) {
      if(WheelPos < 85){
       return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
      } else if(WheelPos < 170) {
       WheelPos -= 85;
       return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
      } else {
       WheelPos -= 170;
       return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
      }
    }

综合应用

miniQ 2WD PLUS & miniQ 2WD

MiniQ 2WD与PLUS上层板是天生的一对。MiniQ 2WD PLUS 和 MiniQ 2WD采用I2C通信,通过Gadgteer连接上下板。MiniQ 2WD PLUS作为I2C主机,MiniQ 2WD作为I2C从机,MiniQ 2WD PLUS通过无线接收命令数据,然后解析,通过I2C发送命令MiniQ 2WD小车底板,作出相应的动作命令。

这里特别说明一下在“运动功能”中,有“巡线模式”、“寻光模式”、“壁障模式”、“遥控模式”,其中的巡线模式中的传感器参数是可以调节的,也就是说,小车可以根据每次使用的巡线场景不同而进行实地调节的。具体操作如下,在进入到巡线模式后,小车并没有直接进入巡线,而是要先进行调节,调节按钮采用底层小车前端的KEY1、KEY2、KEY3三个按钮控制,按下KEY1,对第一个巡线传感器调节(从小车的左侧往右数,第一个巡线传感器),然后KEY2、KEY3是对传感器的值进行调节,不断按下KEY2,直到RGB灯变为红色,(如果接着按KEY3,RGB灯又会变为绿色)按下KEY1确定继续调节第二个。注意:中间那个传感器变为红色后,按下KEY3让它变为绿色,因为我们中间要用来巡黑线的。就这样一次调节完五个传感器。**当然,我们在调节的时候,是将小车放在线上的。**当五个传感器调节完成后,继续按下KEY1键,小车退出调节模式,而进入正常的巡线模式,如果想再次调节,则再次按下KEY1键,依照前面说的进行调节。遥控模式下使用的遥控器是MiniQ 2WD附带的红外遥控器,使用时“开关”、“VOL+”、“VOL—”、“上一曲”,“下一曲”键分别代表“停止”、“前进”、“后退”、“左转”、“右转”。

下面是样例代码,整个例程还需要直插“LCD12864显示屏扩展板”作为显示器,并使用该显示器扩展版上的集成摇杆进行操作。该样例展示了MiniQ 2WD常用功能:蜂鸣、灯光、巡线、寻光、红外壁障,遥控功能。通过该样例可以更 直观的感受产品的强大以及学习产品的使用方法
注意:程序分两部分,须分别烧录在miniQ 2WD Plus上层板和下层小车中。

miniQ 2WD PLUS样例代码

    /***************************************************
     MiniQ 2WD plus (With Stainless Steel Probe)
     <https://www.dfrobot.com.cn/goods-1074.html>

     ***************************************************
     This example show you how to use LCD and button to control miniq_2wd.

     Created 2016-1-15
     By Andy zhou <Andy.zhou@dfrobot.com>
     version:V1.0

     GNU Lesser General Public License.
     See <https://www.gnu.org/licenses/> for details.
     All above must be included in any redistribution
     ****************************************************/

    /***********Notice and Trouble shooting***************
     1.Connection and Diagram can be found here
     <https://wiki.dfrobot.com.cn/_SKU_DFR0302_MiniQ_2WD_Plus>
     2.This code is tested on miniQ plus.
     ****************************************************/

    #include<Wire.h>
    #include<JLX12864G.h>

    JLX12864G lcd(8,9,10,11,13);

    long TimeNum=0;
    char SendCommandData[]={'S','M','L','A','E','O','R','#'};//
    char xReadData=0;
    int Key_Num=0;
    int Key_Up=0,Key_Down=0,Key_Left=0,Key_Right=0,Key_Ok=0;
    byte CheckData1[]={1,3,5};
    byte CheckData2[]={1,3,5,7};
    char StartData[]={
    0x40,0x42,0x42,0x42,0x42,0xFE,0x42,0x42,0x42,0x42,0xFE,0x42,0x42,0x42,0x42,0x00,/*--  文字:  开  --*/
    0x00,0x40,0x20,0x10,0x0C,0x03,0x00,0x00,0x00,0x00,0x7F,0x00,0x00,0x00,0x00,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x10,0x90,0x70,0x1F,0x12,0xF0,0x00,0x20,0x70,0x28,0x27,0x22,0x28,0x70,0x20,0x00,/*--  文字:  始  --*/
    0x40,0x21,0x12,0x0C,0x06,0x09,0x30,0x00,0x7F,0x21,0x21,0x21,0x21,0x7F,0x00,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    };
    char StopData[]={
    0x20,0x30,0xAC,0x63,0x10,0x00,0x08,0x48,0x48,0x48,0x7F,0x48,0x48,0x48,0x08,0x00,/*--  文字:  结  --*/
    0x22,0x23,0x22,0x12,0x12,0x00,0x00,0x7E,0x22,0x22,0x22,0x22,0x22,0x7E,0x00,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x04,0x04,0xF4,0x94,0x94,0x94,0x94,0xFF,0x94,0x94,0x94,0x94,0xF4,0x04,0x04,0x00,/*--  文字:  束  --*/
    0x20,0x20,0x11,0x10,0x08,0x06,0x01,0xFF,0x02,0x04,0x08,0x08,0x11,0x30,0x10,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    };
    char dfrobotlogo[]={
    0x08,0xF8,0xF8,0x08,0x18,0xF0,0xE0,0x00,0x08,0x0F,0x0F,0x08,0x0C,0x07,0x03,0x00,
    0x08,0xF8,0xF8,0x88,0xC8,0xC8,0x18,0x10,0x08,0x0F,0x0F,0x08,0x01,0x01,0x00,0x00,
    0x08,0xF8,0xF8,0xC8,0xC8,0x78,0x30,0x00,0x08,0x0F,0x0F,0x08,0x03,0x0F,0x0C,0x08,
    0xE0,0xF0,0x18,0x08,0x18,0xF0,0xE0,0x00,0x03,0x07,0x0C,0x08,0x0C,0x07,0x03,0x00,
    0x08,0xF8,0xF8,0x48,0x48,0xF8,0xB0,0x00,0x08,0x0F,0x0F,0x08,0x08,0x0F,0x07,0x00,
    0xE0,0xF0,0x18,0x08,0x18,0xF0,0xE0,0x00,0x03,0x07,0x0C,0x08,0x0C,0x07,0x03,0x00,
    0x18,0x18,0x08,0xF8,0xF8,0x08,0x18,0x18,0x00,0x00,0x08,0x0F,0x0F,0x08,0x00,0x00,
    };
    char check[]={0xFF,0xFE,0xFC,0xF8,0xF0,0xE0,0xC0,0x80,0xFF,0x7F,0x3F,0x1F,0x0F,0x07,0x03,0x01,};    //It is a function switch indicator
    char Spacebar[]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,};
    char FullPower[]={0xFF,0x01,0xFD,0xFD,0xFD,0xFD,0x01,0xFF,0xFF,0x80,0xBF,0xBF,0xBF,0xBF,0x80,0xFF,};//It is a full power indicator
    char HalfPower[]={0xFF,0x01,0x81,0x81,0x81,0x81,0x01,0xFF,0xFF,0x80,0xBF,0xBF,0xBF,0xBF,0x80,0xFF,};//
    char LowPower[]={0xFF,0x01,0x01,0x01,0x01,0x01,0x01,0xFF,0xFF,0x80,0xB8,0xB8,0xB8,0xB8,0x80,0xFF,};
    char ShortagePower[]={0xFF,0x01,0x01,0x01,0x01,0x01,0x01,0xFF,0xFF,0x80,0x80,0x80,0x80,0x80,0x80,0xFF,};
    char PatrolLineModel[]={
    0x40,0x42,0x4C,0xC0,0x40,0xA0,0x18,0x07,0x62,0x98,0x07,0x62,0x98,0x07,0x02,0x00,/*--  文字:  巡  --*/
    0x80,0x40,0x20,0x1F,0x20,0x40,0x43,0x4C,0x40,0x41,0x4E,0x44,0x41,0x4E,0x44,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x40,0x60,0x58,0xC7,0x62,0x00,0x90,0x90,0x90,0xFF,0x90,0x92,0x9C,0x94,0x80,0x00,/*--  文字:  线  --*/
    0x20,0x22,0x23,0x12,0x12,0x12,0x20,0x20,0x10,0x13,0x0C,0x14,0x22,0x40,0xF8,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x10,0xD0,0xFF,0x50,0x90,0x04,0xF4,0x54,0x5F,0x54,0x54,0x5F,0xF4,0x04,0x00,0x00,/*--  文字:  模  --*/
    0x03,0x00,0xFF,0x00,0x00,0x84,0x85,0x45,0x35,0x0F,0x15,0x25,0x65,0xC4,0x44,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x00,0x08,0x88,0x88,0x88,0x88,0x88,0x08,0xFF,0x08,0x09,0x0E,0x0A,0x08,0x00,0x00,/*--  文字:  式  --*/
    0x00,0x20,0x60,0x30,0x1F,0x10,0x08,0x08,0x00,0x07,0x18,0x20,0x40,0x80,0x70,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    };
    char SearchLightModel[]={
    0x00,0x00,0x42,0x4A,0x4A,0x4A,0x4A,0x4A,0x4A,0x4A,0xCA,0x4A,0x7E,0x00,0x00,0x00,/*--  文字:  寻  --*/
    0x00,0x01,0x01,0x01,0x05,0x39,0x11,0x01,0x41,0x81,0x7F,0x01,0x01,0x01,0x01,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x00,0x40,0x42,0x44,0x5C,0xC8,0x40,0x7F,0x40,0xC0,0x50,0x4E,0x44,0x60,0x40,0x00,/*--  文字:  光  --*/
    0x00,0x80,0x40,0x20,0x18,0x07,0x00,0x00,0x00,0x3F,0x40,0x40,0x40,0x40,0x78,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x10,0xD0,0xFF,0x50,0x90,0x04,0xF4,0x54,0x5F,0x54,0x54,0x5F,0xF4,0x04,0x00,0x00,/*--  文字:  模  --*/
    0x03,0x00,0xFF,0x00,0x00,0x84,0x85,0x45,0x35,0x0F,0x15,0x25,0x65,0xC4,0x44,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x00,0x08,0x88,0x88,0x88,0x88,0x88,0x08,0xFF,0x08,0x09,0x0E,0x0A,0x08,0x00,0x00,/*--  文字:  式  --*/
    0x00,0x20,0x60,0x30,0x1F,0x10,0x08,0x08,0x00,0x07,0x18,0x20,0x40,0x80,0x70,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    };
    char ObstacleAvoidanceModel[]={
    0x40,0x42,0xCC,0x00,0xFE,0x92,0x92,0x9E,0x40,0x4C,0x55,0xE6,0x5C,0x44,0x40,0x00,/*--  文字:  避  --*/
    0x40,0x20,0x1F,0x24,0x43,0x4F,0x48,0x4F,0x40,0x42,0x42,0x5F,0x42,0x42,0x40,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x00,0xFE,0x22,0x5A,0x86,0x10,0xD2,0x56,0x5A,0x53,0x52,0x5A,0xD6,0x12,0x10,0x00,/*--  文字:  障  --*/
    0x00,0xFF,0x02,0x04,0x13,0x10,0x17,0x15,0x15,0xFD,0x15,0x15,0x17,0x10,0x10,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x10,0xD0,0xFF,0x50,0x90,0x04,0xF4,0x54,0x5F,0x54,0x54,0x5F,0xF4,0x04,0x00,0x00,/*--  文字:  模  --*/
    0x03,0x00,0xFF,0x00,0x00,0x84,0x85,0x45,0x35,0x0F,0x15,0x25,0x65,0xC4,0x44,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x00,0x08,0x88,0x88,0x88,0x88,0x88,0x08,0xFF,0x08,0x09,0x0E,0x0A,0x08,0x00,0x00,/*--  文字:  式  --*/
    0x00,0x20,0x60,0x30,0x1F,0x10,0x08,0x08,0x00,0x07,0x18,0x20,0x40,0x80,0x70,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    };
    char RemoteControlModel[]={
    0x40,0x42,0xCC,0x00,0x00,0x4A,0x32,0x26,0x2A,0xE1,0x31,0x29,0x25,0x00,0x00,0x00,/*--  文字:  遥  --*/
    0x40,0x20,0x1F,0x20,0x40,0x41,0x5D,0x51,0x51,0x5F,0x51,0x51,0x5D,0x41,0x40,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x08,0x08,0x08,0xFF,0x88,0x48,0x00,0x98,0x48,0x28,0x0A,0x2C,0x48,0xD8,0x08,0x00,/*--  文字:  控  --*/
    0x02,0x42,0x81,0x7F,0x00,0x00,0x40,0x42,0x42,0x42,0x7E,0x42,0x42,0x42,0x40,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x10,0xD0,0xFF,0x50,0x90,0x04,0xF4,0x54,0x5F,0x54,0x54,0x5F,0xF4,0x04,0x00,0x00,/*--  文字:  模  --*/
    0x03,0x00,0xFF,0x00,0x00,0x84,0x85,0x45,0x35,0x0F,0x15,0x25,0x65,0xC4,0x44,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x00,0x08,0x88,0x88,0x88,0x88,0x88,0x08,0xFF,0x08,0x09,0x0E,0x0A,0x08,0x00,0x00,/*--  文字:  式  --*/
    0x00,0x20,0x60,0x30,0x1F,0x10,0x08,0x08,0x00,0x07,0x18,0x20,0x40,0x80,0x70,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    };
    char MusicFunction[]={
    0x40,0x40,0x44,0x44,0x4C,0x74,0x44,0x45,0x46,0x64,0x5C,0x44,0x44,0x44,0x40,0x00,/*--  文字:  音  --*/
    0x00,0x00,0x00,0xFF,0x49,0x49,0x49,0x49,0x49,0x49,0x49,0xFF,0x00,0x00,0x00,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x00,0x00,0x40,0xFC,0x44,0x44,0x44,0x46,0xFA,0x42,0x43,0x43,0x42,0x40,0x00,0x00,/*--  文字:  乐  --*/
    0x00,0x20,0x18,0x0C,0x07,0x12,0x20,0x40,0x3F,0x00,0x00,0x02,0x0C,0x38,0x10,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x00,0x04,0x04,0x04,0xFC,0x04,0x14,0x14,0x10,0x90,0x7F,0x10,0x10,0xF0,0x00,0x00,/*--  文字:  功  --*/
    0x04,0x0C,0x04,0x04,0x03,0x42,0x22,0x11,0x0C,0x23,0x20,0x60,0x20,0x1F,0x00,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x10,0xB8,0x97,0x92,0x90,0x94,0xB8,0x10,0x00,0x7F,0x48,0x48,0x44,0x74,0x20,0x00,/*--  文字:  能  --*/
    0x00,0xFF,0x0A,0x0A,0x4A,0x8A,0x7F,0x00,0x00,0x3F,0x44,0x44,0x42,0x72,0x20,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    };
    char LightsFunction[]={
    0x80,0x70,0x00,0xFF,0x40,0x30,0x00,0x04,0x04,0x04,0x04,0xFC,0x04,0x04,0x04,0x00,/*--  文字:  灯  --*/
    0x40,0x30,0x0C,0x03,0x02,0x04,0x08,0x00,0x00,0x20,0x40,0x3F,0x00,0x00,0x00,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x00,0x40,0x42,0x44,0x5C,0xC8,0x40,0x7F,0x40,0xC0,0x50,0x4E,0x44,0x60,0x40,0x00,/*--  文字:  光  --*/
    0x00,0x80,0x40,0x20,0x18,0x07,0x00,0x00,0x00,0x3F,0x40,0x40,0x40,0x40,0x78,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x00,0x04,0x04,0x04,0xFC,0x04,0x14,0x14,0x10,0x90,0x7F,0x10,0x10,0xF0,0x00,0x00,/*--  文字:  功  --*/
    0x04,0x0C,0x04,0x04,0x03,0x42,0x22,0x11,0x0C,0x23,0x20,0x60,0x20,0x1F,0x00,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x10,0xB8,0x97,0x92,0x90,0x94,0xB8,0x10,0x00,0x7F,0x48,0x48,0x44,0x74,0x20,0x00,/*--  文字:  能  --*/
    0x00,0xFF,0x0A,0x0A,0x4A,0x8A,0x7F,0x00,0x00,0x3F,0x44,0x44,0x42,0x72,0x20,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    };
    char MovementFunction[]={
    0x40,0x41,0xCE,0x04,0x00,0x20,0x22,0xA2,0x62,0x22,0xA2,0x22,0x22,0x22,0x20,0x00,/*--  文字:  运  --*/
    0x40,0x20,0x1F,0x20,0x28,0x4C,0x4A,0x49,0x48,0x4C,0x44,0x45,0x5E,0x4C,0x40,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x20,0x24,0x24,0xE4,0x24,0x24,0x24,0x20,0x10,0x10,0xFF,0x10,0x10,0xF0,0x00,0x00,/*--  文字:  动  --*/
    0x08,0x1C,0x0B,0x08,0x0C,0x05,0x4E,0x24,0x10,0x0C,0x03,0x20,0x40,0x3F,0x00,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x00,0x04,0x04,0x04,0xFC,0x04,0x14,0x14,0x10,0x90,0x7F,0x10,0x10,0xF0,0x00,0x00,/*--  文字:  功  --*/
    0x04,0x0C,0x04,0x04,0x03,0x42,0x22,0x11,0x0C,0x23,0x20,0x60,0x20,0x1F,0x00,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x10,0xB8,0x97,0x92,0x90,0x94,0xB8,0x10,0x00,0x7F,0x48,0x48,0x44,0x74,0x20,0x00,/*--  文字:  能  --*/
    0x00,0xFF,0x0A,0x0A,0x4A,0x8A,0x7F,0x00,0x00,0x3F,0x44,0x44,0x42,0x72,0x20,0x00,/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    };

    void setup(){
      pinMode(7,OUTPUT);
      lcd.init();
      lcd.clear();
      Wire.begin();
      Serial1.begin(57600);
    }

    void loop(){
     int n=0;
      lcd.graphic_8x16(3,2,dfrobotlogo,7);
      lcd.string_5x7(5,5,"miniQ 2WD V3");
      lcd.string_5x7(6,15,"www.dfrobot.com");
      lcd.string_5x7(8,1,"Please press any key!");
      digitalWrite(7,HIGH);
      while(Key_Num==0){
       Key_Scan();
      }
      lcd.clear();
      FunctionMenu();
      while(1){//Enter the cycle does not return
        Key_Scan();
        Key_Deal();
        if(Key_Down==4){
        Key_Down=1;
       }
       ReadPowerData();
      }
    }

    void FunctionMenu(void){
      lcd.graphic_16x16(1,9,MusicFunction,4);
      lcd.graphic_16x16(3,9,LightsFunction,4);
      lcd.graphic_16x16(5,9,MovementFunction,4);
    }

    void MusicFunctionMode(void){
      lcd.graphic_16x16(1,29,MusicFunction,4);
    }

    void LightFunctionMode(void){
      lcd.graphic_16x16(1,29,LightsFunction,4);
    }

    void MovementFunctionMode(void){
      lcd.graphic_16x16(1,9,PatrolLineModel,4);
      lcd.graphic_16x16(3,9,SearchLightModel,4);
      lcd.graphic_16x16(5,9,ObstacleAvoidanceModel,4);
      lcd.graphic_16x16(7,9,RemoteControlModel,4);
    }

    void Key_Scan(void){
      int analog = analogRead(A0);
      if(analog>=950){
        Key_Num=0;
        return;
      }else{
         Key_Num=1;
        if(analog>=750&&analog<950){//Press up and key_num=1
          delay(2);
          if(analog>=750&&analog<950){
            while(analog<950)
             analog = analogRead(A0);
             Key_Up++;
          }
        }else if(analog>=550&&analog<750){//Press right and key_num=2
          delay(2);
          if(analog>=550&&analog<750){
            while(analog<950)
             analog = analogRead(A0);
            Key_Right=1;
          }
        }else if(analog>=350&&analog<550){//Press down and key_num=3
          delay(2);
          if(analog>=350&&analog<550){
            while(analog<950)
             analog = analogRead(A0);
           Key_Down++;
          }
        }else if(analog>=150&&analog<350){//Press ok and key_num=4
          delay(2);
          if(analog>=150&&analog<350){
            while(analog<950)
             analog = analogRead(A0);
           Key_Ok=1;
          }
        }else if(analog<150){//Press left and key_num=5
          delay(2);
          if(analog<150){
            while(analog<950)
             analog = analogRead(A0);
             Key_Left=1;
          }
        }
      }
    }

    void Key_Deal(void){
      if(Key_Down==1){
        Lcd_Check_Graphic(1,0);
        if(Key_Ok==1){//进入音乐功能界面
          lcd.clear();
          MusicFunctionMode();
          IICWritData(SendCommandData[1]);
          while(1){
            Key_Scan();
            Key_Ok=0;
            if(Key_Left==1){
              Key_Left=0;
              Key_Down=1;
              IICWritData(SendCommandData[0]);
              goto Back_One_Level;//跳回功能界面
            }
            ReadPowerData();
          }
        }
      }
      if(Key_Down==2){
        Lcd_Check_Graphic(3,0);
        if(Key_Ok==1){//进入灯光功能界面
          lcd.clear();
          LightFunctionMode();
          IICWritData(SendCommandData[2]);
          while(1){
            Key_Scan();
            Key_Ok=0;
            if(Key_Left==1){
              Key_Left=0;
              Key_Down=2;
              IICWritData(SendCommandData[0]);
              goto Back_One_Level;//跳回功能界面
            }
            ReadPowerData();
          }
        }
      }
      if(Key_Down==3){
        Lcd_Check_Graphic(5,0);
        if(Key_Ok==1){//进入运动功能界面
          Key_Ok=0;
          Key_Down=1;
          lcd.clear();
          MovementFunctionMode();
          while(1){
            Key_Scan();
            if(Key_Left==1){
              Key_Left=0;
              Key_Down=3;
              goto Back_One_Level;
            }
            if(Key_Down==1){
                Lcd_Check_Graphic(1,1);
                if(Key_Ok==1){
                  Key_Ok=0;
                  Key_Up=0;
                  lcd.clear();
                  lcd.graphic_16x16(1,29,PatrolLineModel,4);//巡线模式
                 // lcd.graphic_16x16(3,1,StartData,2);
                  IICWritData(SendCommandData[3]);
                  //send line data
                  while(1){
                    Key_Scan();
                    if(Key_Left==1){
                      Key_Ok=0;
                      Key_Left=0;
                      Key_Down=1;
                      IICWritData(SendCommandData[0]);
                      goto Back_Two_Level_Move;//跳回到运动功能界面
                    }
                    ReadPowerData();
                  }
                  Back_Two_Level_Move://运动功能界面
                    lcd.clear();
                    MovementFunctionMode();
                }
            }
            if(Key_Down==2){
              Lcd_Check_Graphic(3,1);
              if(Key_Ok==1){
                Key_Ok=0;
                lcd.clear();
                lcd.graphic_16x16(1,29,SearchLightModel,4);
                IICWritData(SendCommandData[4]);
                //send light data
                while(1){
                  Key_Scan();
                  if(Key_Left==1){
                     Key_Ok=0;
                     Key_Left=0;
                     Key_Down=2;
                     IICWritData(SendCommandData[0]);
                     goto Back_Two_Level_Move;//跳回到运动功能界面
                  }
                  ReadPowerData();
                }
              }
            }
            if(Key_Down==3){
              Lcd_Check_Graphic(5,1);
              if(Key_Ok==1){
                Key_Ok=0;
                lcd.clear();
                lcd.graphic_16x16(1,29,ObstacleAvoidanceModel,4);
                IICWritData(SendCommandData[5]);
                //send OBSTACLEAVOIDANCE data
                while(1){
                  Key_Scan();
                  if(Key_Left==1){
                     Key_Ok=0;
                     Key_Left=0;
                     Key_Down=3;
                     IICWritData(SendCommandData[0]);
                     goto Back_Two_Level_Move;//跳回到运动功能界面
                  }
                  ReadPowerData();
                }
              }
            }
            if(Key_Down==4) {
              Lcd_Check_Graphic(7,1);
              if(Key_Ok==1){
                Key_Ok=0;
                lcd.clear();
                lcd.graphic_16x16(1,29,RemoteControlModel,4);
                IICWritData(SendCommandData[6]);
                //send Remote data
                while(1){
                  Key_Scan();
                  if(Key_Left==1){
                     Key_Ok=0;
                     Key_Left=0;
                     Key_Down=4;
                     IICWritData(SendCommandData[0]);
                     goto Back_Two_Level_Move;//跳回到运动功能界面
                  }
                  SerialReadWriteData();
                  ReadPowerData();
                }
              }
            }
            if(Key_Down==5)
              Key_Down=1;
           ReadPowerData();
          }
          Back_One_Level://功能界面,如果功能跳转函数放在if的外面,lcd显示会闪烁
             lcd.clear();
             FunctionMenu();
        }
      }
    }

    void SerialReadWriteData(void){
      if(Serial1.available()){
        char SerialData = Serial1.read();
        switch(SerialData){
          case 'w':
            IICWritData('w');
            break;
         case 'a':
            IICWritData('a');
            break;
         case 's':
            IICWritData('s');
            break;
         case 'd':
            IICWritData('d');
            break;
         default:
           IICWritData('S');
           break;
        }
      }
    }

    void Lcd_Check_Graphic(byte CheckNum,char RowsNum){//The check of indicator switch function
      if(RowsNum==0){
        for(int i=0;i<3;i++){
          if(CheckData1[i]==CheckNum)   lcd.graphic_8x16(CheckNum,1,check,1);
          else  lcd.graphic_8x16(CheckData1[i],1,Spacebar,1);
        }
      }else if(RowsNum==1){
        for(int j=0;j<4;j++){
        if(CheckData2[j]==CheckNum)
            lcd.graphic_8x16(CheckNum,1,check,1);
        else
            lcd.graphic_8x16(CheckData2[j],1,Spacebar,1);
        }
      }
    }

    void IICWritData(char data){
      Wire.beginTransmission(4); //发送数据到设备号为4的从机
      Wire.write(data);              // 发送变量x中的一个字节
      Wire.endTransmission();    // 停止发送
    }

    void IICReadData(void){
      Wire.requestFrom(4, 1);    //通知4号从机上传1个字节
      while(Wire.available()>0){     // 当主机接收到从机数据时
        xReadData = Wire.read(); //接收一个字节赋值给c
      }
    }

    //读取电池电量函数
    void ReadPowerData(void){
        TimeNum++;
        if(TimeNum==10){
          TimeNum=0;
          IICReadData();
          switch(xReadData){
            case'3':{
              lcd.graphic_8x16(1,121,FullPower,1);
              digitalWrite(7,HIGH);
              break;
            }
            case'2':{
              lcd.graphic_8x16(1,121,HalfPower,1);
              digitalWrite(7,HIGH);
              break;
            }
            case'1':{
              lcd.graphic_8x16(1,121,LowPower,1);
              digitalWrite(7,HIGH);
              break;
            }
            case'0':{
              lcd.graphic_8x16(1,121,ShortagePower,1);
              digitalWrite(7,LOW);
              break;
            }
          }
        }
    }

miniQ 2WD样例代码

    /***************************************************
     MiniQ 2WD plus (With Stainless Steel Probe)
     <https://www.dfrobot.com.cn/goods-1074.html>

     ***************************************************
     This example show you how to use LCD and button to control miniq_2wd.

     Created 2016-1-15
     By Andy zhou <Andy.zhou@dfrobot.com>
     version:V1.0

     GNU Lesser General Public License.
     See <https://www.gnu.org/licenses/> for details.
     All above must be included in any redistribution
     ****************************************************/

    /***********Notice and Trouble shooting***************
     1.Connection and Diagram can be found here
     <https://wiki.dfrobot.com.cn/_SKU_DFR0302_MiniQ_2WD_Plus>
     2.This code is tested on miniQ.
     ****************************************************/

    #include <Wire.h>
    #include <Adafruit_NeoPixel.h>
    #include <IRremote.h>
    #if defined(ARDUINO) && ARDUINO >= 100
    #define printByte(args)  write(args);
    #else
    #define printByte(args)  print(args,BYTE);
    #endif
    uint8_t empty[8] = {0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1f};
    uint8_t heart[8] = {0x0,0xa,0x1f,0x1f,0xe,0x4,0x0,0x0};
    #define address 0x1e

    int length;
    #define RGB_ws2812 10
    Adafruit_NeoPixel strip = Adafruit_NeoPixel(60, RGB_ws2812, NEO_GRB + NEO_KHZ800);

    #define EN1 6//右侧电机使能端
    #define IN1 7//右侧电机方向端
    #define EN2 5//左侧电机使能端
    #define IN2 12//左侧电机方向端

    #define FORW 0//前进
    #define BACK 1//后退

    #define IR_IN  17//红外接收
    #define L_IR 13//左红外发送
    #define R_IR 8//右红外发送

    #define BUZZER 16//蜂鸣器

    #define Vr   5//参考电压值

    #define NTD0 -1
    #define NTD1 294
    #define NTD2 330
    #define NTD3 350
    #define NTD4 393
    #define NTD5 441
    #define NTD6 495
    #define NTD7 556

    #define NTDL1 147
    #define NTDL2 165
    #define NTDL3 175
    #define NTDL4 196
    #define NTDL5 221
    #define NTDL6 248
    #define NTDL7 278

    #define NTDH1 589
    #define NTDH2 661
    #define NTDH3 700
    #define NTDH4 786
    #define NTDH5 882
    #define NTDH6 990
    #define NTDH7 112
    //列出全部D调的频率
    #define WHOLE 1
    #define HALF 0.5
    #define QUARTER 0.25
    #define EIGHTH 0.25
    #define SIXTEENTH 0.625
    //列出所有节拍
    int tune1[]=                 //根据简谱列出各频率
    {
      NTDH3,NTDH3,NTDH2,NTDH3,NTD6,NTDH3,NTD6,NTD5,
      NTDH2,NTDH1,NTDH2,NTDH3,NTDH2,NTDH1,
      NTD6,NTDH3,NTDH2,NTDH3,NTDH2,NTDH1,NTD7,
      NTD6,NTD5,NTD6,NTD7,NTD6,NTD5
    };
    float durt1[]= //根据简谱列出各节拍
    {
      0.5,0.5,1,0.5,1,1.5,1,1,
      0.5,0.5,1,0.5,1,2,
      1,1,0.5,0.5,1,0.5,1,
      0.5,0.5,1,0.5,1,2
    };

    int tonepin=16;   //得用16号接口
    char xReadWriteData='S';
    int   count;//对返回的脉冲进行计数
    float data[7]={0X00,0X00,0X00,0X00,0x00,0xff,0x00};//存储8个通道ad转换的值
    char  value[5]={0x00,0x00,0x00,0x00,0x00};//五个寻线传感器的电压值
    char  key_1=0x00,key_2=0x00,key_3=0x00;//按键的计数值
    //计数里程
    int    count_r=0,count_l=0;//对左右轮返回的脉冲进行计数
    //遥控参数
    IRrecv irrecv(IR_IN);
    decode_results results;
    int SpeedNumL=100;
    int SpeedNumR=100;
    //控制电机转动
    void Motor_Control(int M1_DIR,int M1_EN,int M2_DIR,int M2_EN){
      //////////M1////////////////////////
      if(M1_DIR==FORW)//M1方向为FORW
          digitalWrite(IN1,FORW); //向前
      else
        digitalWrite(IN1,BACK);//向后
      if(M1_EN==0) //M1速度为0
          analogWrite(EN1,LOW);//置低,停止
      else
        analogWrite(EN1,M1_EN);//设置给定的数值

      ///////////M2//////////////////////
      if(M2_DIR==FORW) //M2方向为FORW
          digitalWrite(IN2,FORW);//向前
      else
        digitalWrite(IN2,BACK);//向后
      if(M2_EN==0) //M2速度为0
          analogWrite(EN2,LOW);//置低,停止
      else//否则
      analogWrite(EN2,M2_EN);//设置给定的数值
    }

    //避障
    void L_Send40KHZ(void){//左发射管发射频率为40kHZ脉冲
      int i;
      for(i=0;i<24;i++){
        digitalWrite(L_IR,LOW);//设置左发射管为高电平
        delayMicroseconds(8);//延时
        digitalWrite(L_IR,HIGH);//设置左发射管为低电平
        delayMicroseconds(8);//延时
      }
    }

    void R_Send40KHZ(void){//右发射管发射频率为40kHZ脉冲
      int i;
      for(i=0;i<24;i++){
        digitalWrite(R_IR,LOW);//设置右发射管为高电平
        delayMicroseconds(8);//延时
        digitalWrite(R_IR,HIGH);//设置右发射管为低电平
        delayMicroseconds(8);//延时
      }
    }

    //计数里程
    void LEFT(void){
     //左侧车轮返回脉冲计数
     //if(++count_l=100)
        count_l++;
    }

    void RIGHT(void){
     //右侧车轮返回脉冲计数
    // if(++count_r=100)
        count_r++;
    }

    void pcint0_init(void){//引脚变化中断初始化
      PCICR = 0X01;//使能第0组引脚变化中断
      PCMSK0 = 0X01;//使能第0组的第0个引脚变化中断
    }

    ISR(PCINT0_vect){//PB0引脚变化中断
      count++;//计数接收到的脉冲
    }

    void Obstacle_Avoidance(void){//避障子函数
      char i;
      count=0;
      for(i=0;i<20;i++){//左发射管发射20个脉冲
        L_Send40KHZ();
        delayMicroseconds(600);
      }
      if(count>20){//接收超过10个脉冲,判断有障碍物
          Motor_Control(BACK,SpeedNumR,BACK,SpeedNumL);//后退
          delay(50);//延时
          Motor_Control(BACK,SpeedNumR,FORW,SpeedNumL);//右转
          delay(50);//延时
      }else{
          Motor_Control(FORW,SpeedNumR,FORW,SpeedNumL);//前进
      }

      count=0;
      for(i=0;i<20;i++){//右发射管发射20个脉冲
        R_Send40KHZ();
        delayMicroseconds(600);
      }
      if(count>20){
          Motor_Control(BACK,SpeedNumR,BACK,SpeedNumL);//后退
          delay(50);//延时
          Motor_Control(FORW,SpeedNumR,BACK,SpeedNumL);//左转
          delay(50);//延时
      }else{
          Motor_Control(FORW,SpeedNumR,FORW,SpeedNumL);//前进
      }

    }

    //读取模拟端口电压值
    void Read_Value(void){
      int i;
      for(i=0;i<7;i++){
        data[i]=analogRead(i);//读取模拟i口电压值
        data[i]= ((data[i]*Vr)/1024); //转换成模拟值
      }
    }

    //寻线
    void value_adjust(unsigned char num){//调整寻线传感器的值
      if(num==1){//调节第一个寻线传感器
        if(data[0]>value[0]){
          colorWipe(strip.Color(0, 255, 0), 5); // Red
        }else{
          colorWipe(strip.Color(255, 0, 0), 1); // Green
        }
      }
      if(num==2){//调节第二个寻线传感器
        if(data[1]>value[1]){
          colorWipe(strip.Color(0, 255, 0), 1); // Red
        }else{
          colorWipe(strip.Color(255, 0, 0), 1); // Green
        }
      }
      if(num==3){//调节第三个寻线传感器
        if(data[2]>value[2]){
          colorWipe(strip.Color(0, 255, 0), 1); // Red
        }else{
          colorWipe(strip.Color(255, 0, 0), 1); // Green
        }
      }
      if(num==4){//调节第四个寻线传感器
        if(data[3]>value[3]){
          colorWipe(strip.Color(0, 255, 0), 1); // Red
        }else{
          colorWipe(strip.Color(255, 0, 0), 1); // Green
        }
      }
      if(num==5){//调节第五个寻线传感器
        if(data[4]>value[4]){
          colorWipe(strip.Color(0, 255, 0), 1); // Red
        }else{
          colorWipe(strip.Color(255, 0, 0), 1); // Green
        }
      }
    }

    void huntline_deal(void){
     if(data[0]>(value[0]-1)&&data[1]>(value[1]-1)&&data[2]<(value[2])&&data[3]>(value[3]-1)&&data[4]>(value[4]-1))//测一下实际值
        Motor_Control(FORW,100,FORW,100);//前进
      else if(data[0]>(value[0]-1)&&data[1]>(value[1]-1)&&data[2]<(value[2]-1)&&data[3]<(value[3]-1)&&data[4]>(value[4]-1))
        Motor_Control(BACK,20,FORW,100);//右转
      else if(data[0]>(value[0]-1)&&data[1]>(value[1]-1)&&data[2]>(value[2]-1)&&data[3]<(value[3]-1)&&data[4]>(value[4]-1))
        Motor_Control(BACK,100,FORW,100);//快速右转
      else if(data[0]>(value[0]-1)&&data[1]>(value[1]-1)&&data[2]>(value[2]-1)&&data[3]<(value[3]-1)&&data[4]<(value[4]-1))
        Motor_Control(BACK,100,FORW,100);//快速右转
      else if(data[0]>(value[0]-1)&&data[1]>(value[1]-1)&&data[2]>(value[2]-1)&&data[3]>(value[3]-1)&&data[4]<(value[4]-1))
        Motor_Control(BACK,100,FORW,100);//快速右转
      else if(data[0]>(value[0]-1)&&data[1]<(value[1]-1)&&data[2]<(value[2]-1)&&data[3]>(value[3]-1)&&data[4]>(value[4]-1))
        Motor_Control(FORW,100,BACK,20);//左转
      else if(data[0]>(value[0]-1)&&data[1]<(value[1]-1)&&data[2]>(value[2]-1)&&data[3]>(value[3]-1)&&data[4]>(value[4]-1))
        Motor_Control(FORW,100,BACK,100);//快速左转
      else if(data[0]<(value[0]-1)&&data[1]<(value[1]-1)&&data[2]>(value[2]-1)&&data[3]>(value[3]-1)&&data[4]>(value[4]-1))
        Motor_Control(FORW,100,BACK,100);//快速左转
      else if(data[0]<(value[0]-1)&&data[1]>(value[1]-1)&&data[2]>(value[2]-1)&&data[3]>(value[3]-1)&&data[4]>(value[4]-1))
        Motor_Control(FORW,100,BACK,100);//快速左转
    }

    //寻光
    void hunt_light(void){
      if (data[5]>3.5)   //根据实际的实验环境进行测量
        Motor_Control(BACK,SpeedNumR,FORW,SpeedNumL);//右转
      else if (data[5]< 1.5)
        Motor_Control(FORW,SpeedNumR,BACK,SpeedNumL);//
      else
        Motor_Control(FORW,0,FORW,0);//停止
    }
    //遥控功能函数
    void dump(decode_results *results){
      if(results->value==0x00fd00ff){
        Motor_Control(FORW,0,FORW,0);//停止
      }
      if(results->value==0x00fd807f){
        Motor_Control(FORW,100,FORW,100);//前进
      }
      if(results->value==0x00fd906f){
        Motor_Control(BACK,100,BACK,100);//后退
      }
      if(results->value==0x00fd20df){
        Motor_Control(FORW,100,BACK,100);//左转
      }
      if(results->value==0x00fd609f){
        Motor_Control(BACK,100,FORW,100);//右转
      }
    }

    //蜂鸣器声音
    void buzzer(void){//蜂鸣器发出一种声音
      digitalWrite(BUZZER,HIGH);//置高,蜂鸣器响
      delay(1);
      digitalWrite(BUZZER,LOW);//置低,蜂鸣器不响
      delay(10);
    }

    //按键扫描
    void key_scan(void){
      if(data[6]>4.50&&data[6]<6.00)//没有按键按下
        return;//返回
      else{
        if(data[6]>=0.00&&data[6]<0.80){//按键1按下
          delay(10);//延时消抖
          if(data[6]>=0.00&&data[6]<0.80){//按键1确实按下
            buzzer();//蜂鸣器响
            while(data[6]>=0.00&&data[6]<0.80)
             Read_Value();
            key_1++;//按键1计数
            if(key_1>=1&&key_1<=5)
              value_adjust(key_1);//寻线传感器的值调整
          }
        }
        else if(data[6]>=0.80&&data[6]<3){//按键2按下
          delay(10);//延时消抖
          if(data[6]>=0.80&&data[6]<3){
            buzzer();//蜂鸣器响
            while(data[6]>=0.50&&data[6]<3)
             Read_Value();
            if(key_1>=1&&key_1<=5){//按键1的值在1~5之间
              value[key_1-1]++;//传感器的分辨轨迹的界限值加加(调整)
              value_adjust(key_1);//跟实际值对比
            }else{
              key_2++;//key2计数
            }
          }
        }else if(data[6]>=3&&data[6]<4){//按键3按下
          delay(10);//延时消抖
          if(data[6]>=3&&data[6]<4){
            buzzer();//蜂鸣器响
            while(data[6]>=3&&data[6]<4)
              Read_Value();
              if(key_1>=1&&key_1<=5){//按键1的值在1~5之间
               value[key_1-1]--;//传感器的分辨轨迹的界限值减减(调整)
               value_adjust(key_1);//跟实际值对比
              }else{
               key_3++;//key3计数
            }
          }
        }
      }
    }

    void key_deal(){
      if(key_1==6){//寻线
        huntline_deal();//调用寻线处理子函数
        key_2=0x00;
        key_3=0x00;
      }else if(key_1 == 7){
         key_1 = 0;
         Motor_Control(FORW,0,FORW,0);//停止
      }
    }

    //低电压检测
    void low_voltage_check(void){
        float voltage_num=analogRead(A9);
        voltage_num=(15*voltage_num)/2048;
       if(voltage_num<4.0||voltage_num>7.0){
         while(1){
           voltage_num=analogRead(A9);
            voltage_num=(15*voltage_num)/2048;
            if(voltage_num>=4.0&&voltage_num<=7.0)
              break;
            buzzer(); //蜂鸣器响
            colorWipe(strip.Color(0, 255, 0), 1); // Red
            colorWipe(strip.Color(0, 0, 0), 1);
         }
       }
    }

    void Velocity_function(void){//速度处理函数
      if(count_l>count_r){
        SpeedNumL=SpeedNumL-1;
        SpeedNumR=SpeedNumR+1;
      }else if(count_l<count_r){
        SpeedNumL=SpeedNumL+1;
        SpeedNumR=SpeedNumR-1;
      }
      count_l=0;
      count_r=0;
    }

    void colorWipe(uint32_t c, uint8_t wait){
      for(uint16_t i=0; i<strip.numPixels(); i++){
          strip.setPixelColor(i, c);
          strip.show();
          delay(wait);
      }
    }

    void setup(){
      pinMode(5,OUTPUT);
      pinMode(6,OUTPUT);
      pinMode(7,OUTPUT);
      pinMode(12,OUTPUT);

      pinMode(8,OUTPUT);
      pinMode(10,OUTPUT);
      pinMode(13,OUTPUT);
      pinMode(14,OUTPUT);
      pinMode(16,OUTPUT);
      pinMode(17,INPUT);
      irrecv.enableIRIn();
      strip.begin();
      strip.show();

      length=sizeof(tune1)/sizeof(tune1[0]);   //计算长度

      Wire.begin(4);// 加入 i2c 总线,设置从机地址为 #4
      Wire.onReceive(receiveEvent);//注册接收到主机字符的事件
      Wire.onRequest(requestEvent);// 注册主机通知从机上传数据的事件

      attachInterrupt(2,RIGHT,FALLING);
      attachInterrupt(3,LEFT,FALLING);
      Motor_Control(FORW,0,FORW,0);
    }

    void loop(){
      switch(xReadWriteData){
        case'S':
          Motor_Control(FORW,0,FORW,0);
          break;
        case'M':
          MusicFunction();
          break;
        case'L':
          RGBFunction();
          break;
        case'A':
          LineFunction();
          break;
        case'E':
          LightFuntion();
          break;
        case'O':
          ObstacleFunction();
          break;
        case'R':
          RemoteFunction();
          break;
        case'w':
          Motor_Control(FORW,SpeedNumR,FORW,SpeedNumL);//前进
          break;
        case'a':
          Motor_Control(FORW,SpeedNumR,BACK,SpeedNumL);//左转
          break;
        case's':
          Motor_Control(BACK,SpeedNumR,BACK,SpeedNumL);//后退
          break;
        case'd':
          Motor_Control(BACK,SpeedNumR,FORW,SpeedNumL);//右转
          break;
      }
      Velocity_function();
    //  low_voltage_check();
      colorWipe(strip.Color(0, 0, 0), 1);
    }

    // 当从机接收到主机字符,执行该事件
    void receiveEvent(int howMany){
      while( Wire.available()>1){ // 循环执行,直到数据包只剩下最后一个字符
        char c = Wire.read(); // 作为字符接收字节
      }
       //接收主机发送的数据包中的最后一个字节
      xReadWriteData = Wire.read();    // 作为整数接收字节
    }

    //当主机通知从机上传数据,执行该事件
    void requestEvent(){// 响应主机的通知,向主机发送一个字节数据
      float analog = analogRead(A9);
      analog =(15*analog)/2048;
      if(analog>4.7)
        Wire.write( '3');
      else if(analog>4.3)
        Wire.write( '2');
      else if(analog>3.9)
        Wire.write( '1');
      else
        Wire.write( '0');
    }

    uint32_t Wheel(byte WheelPos){
      if(WheelPos < 85){
       return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
      } else if(WheelPos < 170){
       WheelPos -= 85;
       return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
      }else {
       WheelPos -= 170;
       return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
      }
    }

    void rainbow(uint8_t wait){
      uint16_t i, j;
      for(j=0; j<256; j++){
        for(i=0; i<strip.numPixels(); i++){
          strip.setPixelColor(i, Wheel((i+j) & 255));
        }
        strip.show();
        delay(wait);
      }
    }

    // Slightly different, this makes the rainbow equally distributed throughout
    void rainbowCycle(uint8_t wait) {
      uint16_t i, j;
      for(j=0; j<256*5; j++) { // 5 cycles of all colors on wheel
        for(i=0; i< strip.numPixels(); i++) {
          strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
        }
        strip.show();
        delay(wait);
      }
    }

    void MusicFunction(void){
      for(int x=0;x<length;x++){
        tone(tonepin,tune1[x]);
        delay(350*durt1[x]);   //这里用来根据节拍调节延时,500这个指数可以自己调整,在该音乐中,我发现用500比较合适。
        noTone(tonepin);
      }
    }

    void RGBFunction(void){
      rainbow(5);
      rainbowCycle(5);
    }

    void LineFunction(void){
      //buzzer();//蜂鸣器响
      Read_Value();
      key_scan();
      key_deal();
    }

    void LightFuntion(void){
       Read_Value();
       hunt_light();//调用寻光子函数
       Velocity_function();//速度处理函数
    }

    void ObstacleFunction(void){
      pcint0_init();//引脚变化中断初始化
      sei();     //全局中断使能
      Obstacle_Avoidance();
      Velocity_function();
    }
    void RemoteFunction(void){
      Motor_Control(FORW,0,FORW,0);//run motor
      while(1){
       if(irrecv.decode(&results)){
          dump(&results);
          irrecv.resume();
       }
      }
    }

这是“miniQ 2WD Plus + miniQ 2wd + Blelink + 蓝牙4.0 无线手柄”的典型应用。组建自己的遥控车,怎么样,很高档大气吧~

注:蓝牙4.0 无线手柄的数据传输格式,请参照蓝牙4.0 无线手柄的手柄BLE透传协议。

开发资料

miniQ 2wd plus原理图
miniQ 2wd + miniQ 2wd plus程序
<File:nextredirectltr.png>购买 MiniQ 2WD Plus (SKU:DFR0302)