体感台灯/zh

来自Microduino Wikipedia
跳转至: 导航搜索

概述

  • 项目名称:Microduino体感台灯
  • 目的:通过不同姿态改变台灯颜色
  • 难度:中
  • 耗时:2小时
  • 制作者:
  • 简介:

体感台灯是一个集多种功能于一体的综合系统,是自动控制理论与动力学理论及技术相结合的研究课题,其关键问题是在通过使用Microduino姿态传感器(Microduino-10DOF)获取姿态数据,经过量化成颜色值(0-255)的同时,再通过Microduino-nRF24将体感获取的数据传递给彩灯,点亮彩灯。彩灯采用单总线WS2812彩灯(Microduino-Lamp),每个灯内置控制IC芯片,能自由、简单控制。本次教程我们使用Microduino产品模块快速搭建一个在一定范围内可以用。玩家可以迅速上手,并且看到体感台灯在体感遥控器位置改变后台灯中Microduino-Lamp的颜色和亮度变换的效果。玩家们可以在制作结束后,继续更深一步的智能控制部分的研究。

材料清单

  • Microduino设备
模块 数量 功能
Microduino-Core/zh 1 核心板
Microduino-USBTTL/zh 1 下载程序
Microduino-BM/zh 1 电源管理
Microduino-nRF24/zh 1 无线通信
Microduino-Lamp/zh 1 彩灯
Microduino-10DOF/zh 1 矢量传感器
  • 其他设备
模块 数量 功能
Micro-USB线/zh 1 下载程序
锂电池 1 供电
灯罩 1 台灯灯罩
高脚杯 1 台灯骨架

实验原理

  • 主要传感器

Microduino-Lamp/zh 首先介绍一下彩灯点亮的部分,我们使用Microduino-Lamp模块作为发光设备,该模块级联了6盏RGB3色彩灯,并可以使用相应的库函数控制每盏彩灯的颜色,为了清晰说明彩灯的控制方法,用一个简单的程序举例

打开Arduino IDE,在文件(File)—>示例(Examples)—>_99_LCD_NeoPixel目录下,点开例程strandtest strandtest示例分析:

#include <Adafruit_NeoPixel.h> 
#define PIN 6 //定义控制引脚
// 参数 1 = strip中彩灯的数目 
// 参数 2 = 引脚号
// 参数 3 = 彩灯类型, 可多选(前两个中选一个,后两个中选一个): 
//   NEO_RGB     Pixels are wired for RGB bitstream 
//   NEO_GRB     Pixels are wired for GRB bitstream 
//   NEO_KHZ400  400 KHz bitstream (e.g. FLORA pixels) 
//   NEO_KHZ800  800 KHz bitstream (e.g. High Density LED strip) 
Adafruit_NeoPixel strip = Adafruit_NeoPixel(60, PIN, NEO_GRB + NEO_KHZ800); 
void setup() 
{ 
    strip.begin(); 
    strip.show(); //初始化所有彩灯都为灭
} 
void loop() 
{ 
    // 点亮彩灯的方法 
    colorWipe(strip.Color(255, 0, 0), 50); // 点亮红色
    colorWipe(strip.Color(0, 255, 0), 50); // 点亮绿色
    colorWipe(strip.Color(0, 0, 255), 50); // 点亮蓝色
    rainbow(20); 
    rainbowCycle(20); 
} 
//用“c”所代表的颜色依次点亮各盏彩灯,每点亮一盏等“wait”秒
void colorWipe(uint32_t c, uint8_t wait) 
{ 
    for(uint16_t i = 0; i < strip.numPixels(); i++)  //依次点亮
    { 
       strip.setPixelColor(i, c); //这个函数用于把第i盏灯用“c”所指颜色点亮
        strip.show(); //这个函数会将setPixelColor函数所写入的控制信息显示
                        //出来,也就是靠它点亮LAMP模块 
        delay(wait); 
    } 
} 

void rainbow(uint8_t wait) //彩虹显示
{ 
    uint16_t i, j; 
 
    for(j = 0; j < 256; j++)  //渐变255种颜色
    { 
      for(i = 0; i < strip.numPixels(); i++) //依次点亮彩灯,间隔wait毫秒
        { 
            strip.setPixelColor(i, Wheel((i + j) & 255)); 
        } 
        strip.show(); 
        delay(wait); 
    } 
} 
// 与上面的函数稍有区别,添加了彩虹的循环
void rainbowCycle(uint8_t wait) 
{ 
    uint16_t i, j; 
 
    for(j = 0; j < 256 * 5; j++) //彩虹循环5次
    { 
        for(i = 0; i < strip.numPixels(); i++) 
        { 
            strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));  //为了循环而添加的数学变换
        } 
        strip.show(); 
        delay(wait); 
    } 
} 

// 输入0-255任意一个数得到对应的唯一的一种颜色 
// 颜色会从红-绿->蓝->红依次渐变循环 
uint32_t Wheel(byte WheelPos) 
{ 
    if(WheelPos < 85) 
    { 
        return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0); 
    } 
    else if(WheelPos < 170) 
    { 
        //因为WheelPos * 3在85到170的情况下会超过255,因此要先自减85 
        WheelPos -= 85; 
        return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3); 
    } 
    else 
    { 
        //因为WheelPos * 3在170以上的情况下会超过255,因此要先自减170 
        WheelPos -= 170; 
        return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3); 
    } 

Microduino-10DOF/zh Microduino-10DOF模块集成了四种传感器,分别是三轴加速度+三轴陀螺仪传感器(MPU6050)、磁场强度传感器(HMC5883L)、数字气压传感器(BMP180),通过I2C进行通信。 MPU6050是其中最主要的姿态传感器,内部集成了三轴加速度计和三轴陀螺仪,不仅消除了组合加速度计和陀螺仪时容易出现的对准误差,而且内置了可编程的低通滤波器,控制程序通过配置适当频率的低通滤波器,可以滤掉高频振动。在本套课程中,我们通过使用Microduino-10DOF模块中的MPU6050传感器来计算X, Y, Z轴的角度,这里我们采用加速度进行积分运算的方法来计算角度,这种方法比较简便,但精读不是非常准确

文档

调试过程

  • 下载程序

将Microduino-Core与Microduino-USBTTL叠加(无上下顺序),通过USB数据与电脑连接起来。

Download1.jpg

打开相应程序代码。 打开方式一:找到该程序的物理位置,”小球控制彩灯/ mpu_ws2812/ mpu_ws2812_rec/ mpu_ws2812_rec.ino”,双击即可。 打开方式二:双击Microduino的logo,打开Microduino软件(可用其他方式打开),在点击【文件】【新建】之后,在课件中选择自己要下载的程序文档。课件中程序文档所在的位置为”小球控制彩灯/ mpu_ws2812/ mpu_ws2812_rec/ mpu_ws2812_rec.ino”

点击【工具】,在板选项里面选择板卡(Microduino-Core),在处理器选项里面选择处理器(Atmega328p@16M,5V),再在端口选项里面选择正确的端口号,然后直接烧录程序。

Dl3.jpg
  • 控制板搭建

搭建硬件电路,将用到的设备叠加起来(无上下顺序)

  • Microduino-BM
  • Microduino-Lamp
  • Microduino-Core
  • Mircoduino-nRF24(无天线)
  • Microduino-USBTTL
  • 电池
  • 体感控制器搭建

将Microduino-Core(另一个)与Microduino-USBTTL叠加,无上下顺序,通过USB数据线与电脑连接起来

Download1.jpg

打开相应程序代码。 打开方式一:找到该程序的物理位置,”小球控制彩灯/ mpu_ws2812/ mpu_ws2812_rec/ mpu_ws2812_send.ino”,双击即可 打开方式二:双击Microduino的logo,打开Microduino软件(可用其他方式打开),在点击【文件】【新建】之后,在课件中选择自己要下载的程序文档。课件中程序文档所在的位置为”小球控制彩灯/ mpu_ws2812/ mpu_ws2812_rec/ mpu_ws2812_send.ino”

点击【工具】,在板选项里面选择板卡(Microduino-Core),在处理器选项里面选择处理器(Atmega328p@16M,5V),再在端口选项里面选择正确的端口号,然后直接烧录程序。

Dl3.jpg

搭建硬件电路,将用到的设备叠加起来(无上下顺序)

  • Microduino-Core
  • Microduino-USBTTL
  • Microduino-nRF24
  • Microduino-BM
  • Microduino-10DOF

构建外壳的3D模型

采用3D打印机打印出模型,确定尺寸是否合适

  • 整体调试

在台灯和体感遥控器的BM模块中接上电池,打开BM上的电源开关,若没有电池,直接插上MicroUSB就可以给电池充电。 注意:BM更换电池后若不能启动电源,需要插上USB启动BM。 滚动小球,观察台灯颜色变化。

注意问题

  • 发送和接收端共用一个USBTTL模块,只是下载程序能用到。
  • BM更换电池后若不能启动电源,需要插上USB启动BM。
  • 不要用灯光直接照射眼睛。

程序说明

  • 接收程序说明(“mpu_ws2812_rec.ino”)
/* 
NRF的原理为SPI,对外共有四个接口 
SDO:主设备数据输出、从设备数据输入 
SDI:主设备数据输入、从设备输出 
SDLK:时钟信号(只能由主设备控制) 
CS:设备的使能信号(控制数据输入输出),有主设备控制  [程序中需要定义] 
*/ 
 
//rf======================================= 
#include <RF24Network.h> 
#include <RF24.h> 
#include <SPI.h> 
 
// nRF24L01(+) radio attached using Getting Started board 
RF24 radio(9, 10);  //定义NRF的控制端口ce,cs(9管脚为SPI片选信号端,10为的开启端) 
RF24Network network(radio); 
const uint16_t this_node = 1;    //设置本机ID 
const uint16_t other_node = 0; //设置发送端ID 
 
#include <Adafruit_NeoPixel.h> 
 
#define PIN A0   //定义A0端口为控制Microduino-Lamp的端口 
 
Adafruit_NeoPixel strip = Adafruit_NeoPixel(20, PIN, NEO_GRB + NEO_KHZ800); 
 
//-------------------------------- 
struct send_a    //发送 
{ 
    uint32_t node_ms;        //节点运行时间 
}; 
 
unsigned long last_sent = 0;    //定时器 
 
//-------------------------------- 
struct receive_a    //接收数据信息 
{ 
    uint32_t ms; 
    uint32_t rf_x; 
    uint32_t rf_y; 
    uint32_t rf_z; 
}; 
 
unsigned long clock;    //主机运行时间 
int tem_xuan = 0;        //主机请求时序 
 
//---------------------------- 
boolean node_STA = false; 
 
int Angel_accX, Angel_accY, Angel_accZ; 
 
unsigned long safe_ms = millis(); 
 
void setup() 
{ 
    Serial.begin(115200);  //设置串口波特率,用于和电脑进行串口通信 
 
    strip.begin(); 
    strip.show(); // Initialize all pixels to 'off' 
 
    //nRF============================== 
    SPI.begin();        //初始化SPI总线 
    radio.begin(); 
    network.begin(/*channel*/ 70, /*node address*/ this_node); 
 
    Serial.println("===========start==========="); 
} 
 
// 主循环////////////////////////////////////////////////////////////////////////// 
void loop() 
{ 
    //=============================================================== 
    nRF(); 
 
    /* 
     * 判断从开始发送到最后的时间是否大于从开始发送数据到现在的时间,如果是将现在的时间赋值给safe_ms 
     * 如果对后一次发送时间与运行到现在的时间大于间隔时间,控制灯光为红色 
     */ 
    //=============================================================== 
    if(safe_ms > millis()) safe_ms = millis(); 
    if(millis() - safe_ms > 2000) 
    { 
        colorWipe(strip.Color(0, 0, 0), 50); 
    } 
 
} 
 
 
/* 
 * 定时更新数据,如果有心的数据传来,将数据去字头,获取数据信息 
 */ 
void nRF() 
{ 
    network.update(); 
    // Is there anything ready for us? 
    while ( network.available() ) 
    { 
        // If so, grab it and print it out 
        RF24NetworkHeader header; 
        receive_a rec; 
        network.read(header, &rec, sizeof(rec)); 
 
        clock = rec.ms;        //接收主机运行时间赋值 
        Angel_accX = rec.rf_x;        //接收请求时序赋值 
        Angel_accY = rec.rf_y; 
        Angel_accZ = rec.rf_z; 
 
        Serial.print(Angel_accX); 
        Serial.print(","); 
        Serial.print(Angel_accY); 
        Serial.print(","); 
        Serial.print(Angel_accZ); 
        Serial.println(""); 
 
        colorWipe(strip.Color(Angel_accX, Angel_accY, Angel_accZ), 10); 
 
        { 
            //Serial.print("Sending..."); 
            send_a sen = 
            { 
                millis() 
            };    //把这些数据发送出去,对应前面的发送数组 
 
            RF24NetworkHeader header(0); 
            boolean ok = network.write(header, &sen, sizeof(sen)); 
 
        } 
 
        safe_ms = millis(); 
    } 
} 
 
/* 
 * 定义colorWipe函数,将获取的信息改编成Microduino-Lamp灯光的三色值,用灯光显示出来 
 */ 
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); 
    } 
} 
  • 发送程序说明(“mpu_ws2812_send.ino”)

主函数

#include <I2Cdev.h> 
#include <MPU6050.h> 
#include <Wire.h>//添加必须的库文件 
#include "mpu.h" 
#include "nrf.h" 
 
//调用RF24Network、RF24、SPI库文件 
#include <RF24Network.h> 
#include <RF24.h> 
#include <SPI.h> 
 
void setup() 
{ 
    Wire.begin(); 
    Serial.begin(115200);   //设置串口波特率,用于和电脑进行串口通信 
 
    Wire.begin(); 
 
    accelgyro.initialize(); 
 
    SPI.begin();        //初始化SPI总线 
    radio.begin(); 
    network.begin(/*channel*/ nRF_channal, /*node address*/ this_node); 
} 
 
void loop() 
{ 
    getMPU(); 
    nrf_send(); 
} 
/* 
 * Microduino-10DOF/zh模块为IIC接口 
 * 主要由磁场强度传感器+数字气压传感器+三轴加速器、三轴陀螺仪传感器组成 
 * 这里用到的是三轴加速器、三轴陀螺仪传感器 
 * 获取的数值为:三轴的位置,从而就算处三轴的加速度 
 */ 
 
#include <I2Cdev.h> 
#include <MPU6050.h> 
#include <Wire.h>//添加必须的库文件 
 
//====一下三个定义了陀螺仪的偏差=========== 
#define Gx_offset -3.06 
#define Gy_offset 1.01 
#define Gz_offset -0.88 
 
//DEBUG----------- 
#define Serial_DEBUG 
 
//=================== 
MPU6050 accelgyro; 
 
int16_t ax, ay, az; 
int16_t gx, gy, gz; //存储原始数据 
float Ax, Ay, Az; //单位 g(9.8m/s^2) 
 
int Angel_accX, Angel_accY, Angel_accZ; //存储加速度计算出的角度 
int Angel_accX_send, Angel_accY_send, Angel_accZ_send; //存储加速度计算出的角度 
 
void getMPU() 
{ 
    accelgyro.getMotion6(&ax, &ay, &az, &gx, &gy, &gz); //获取三个轴的加速度和角速度 
 
    //======一下三行是对加速度进行量化,得出单位为g的加速度值 
    Ax = ax / 16384.00; 
    Ay = ay / 16384.00; 
    Az = az / 16384.00; 
    //==========以下三行是用加速度计算三个轴和水平面坐标系之间的夹角 
    Angel_accX = atan(Ax / sqrt(Az * Az + Ay * Ay)) * 180 / 3.14; 
    Angel_accY = atan(Ay / sqrt(Ax * Ax + Az * Az)) * 180 / 3.14; 
    Angel_accZ = atan(Az / sqrt(Ax * Ax + Ay * Ay)) * 180 / 3.14; 
 
    Angel_accX_send = map(abs(Angel_accX), 0, 90, 0, 255); 
    Angel_accY_send = map(abs(Angel_accY), 0, 90, 0, 255); 
    Angel_accZ_send = map(abs(Angel_accZ), 0, 90, 0, 255); 
 
    //============================== 
#ifdef Serial_DEBUG 
    delay(5);//这个用来控制采样速度 
 
    Serial.print("    X:"); 
    Serial.print(Angel_accX); 
    Serial.print("    Y:"); 
    Serial.print(Angel_accY); 
    Serial.print("    Z:"); 
    Serial.println(Angel_accZ); 
#endif 
} 
#include "Arduino.h" 
 
/* 
NRF的原理为SPI,对外共有四个接口 
SDO:主设备数据输出、从设备数据输入 
SDI:主设备数据输入、从设备输出 
SDLK:时钟信号(只能由主设备控制) 
CS:设备的使能信号(控制数据输入输出),有主设备控制  [程序中需要定义] 
*/ 
 
//定义使用NRF需要用的到的库文件 
#include <RF24Network.h> 
#include <RF24.h> 
#include <SPI.h> 
 
 
// nRF24L01(+) radio attached using Getting Started board 
RF24 radio(9, 10);   //定义NRF的控制端口ce,cs(9管脚为SPI片选信号端,10为的开启端) 
RF24Network network(radio); 
 
const uint16_t this_node = 0;    //设置本机ID 
const uint16_t other_node = 1; 
 //设置接收端ID 
 
//nRF------------- 
#define interval_debug  2000    //节点查错间隔 
#define nRF_channal 70    //定义传送信道(0~125) 
 
//-------------------------------- 
struct send_a    //定义发送数据变量 
{ 
    uint32_t ms; 
    uint32_t rf_x; 
    uint32_t rf_y; 
    uint32_t rf_z; 
}; 
 
struct receive_a    //接收 
{ 
    uint32_t node_ms; 
}; 
 
//-------------------------------- 
unsigned long node_clock, node_clock_debug, node_clock_cache = 0;        //节点运行时间、节点响应检查时间、节点时间缓存 
 
//debug-------------------------- 
boolean node_clock_error = false;    //节点响应状态 
unsigned long time_debug = 0;        //定时器 
 
 
//====================================== 
 
/* 
 * 定义vodebug函数,判断最后一次数据发送的时间到现在是否大于间隔时间 
 * 如果大于判断运行有错误,并且将开始运行到现在的时间赋值给time_debug 
 */ 
void vodebug() 
{ 
    if(millis() - time_debug > interval_debug) 
    { 
        node_clock_error = boolean(node_clock == node_clock_debug);        //一定时间内,节点返回的运行时间若不变则有问题 
 
        node_clock_debug = node_clock; 
 
        time_debug = millis(); 
    } 
} 
 
/* 
 * 定义nrf_send函数,用于发送数据 
 * 将数据加字头+地址打包发生发送 
 */ 
void nrf_send() 
{ 
#ifdef Serial_DEBUG 
    Serial.print("Sending..."); 
#endif 
 
    send_a sen = 
    { 
        millis(), Angel_accX_send, Angel_accY_send, Angel_accZ_send 
    };        //把这些数据发送出去,对应前面的发送数组 
    RF24NetworkHeader header(other_node); 
    if (network.write(header, &sen, sizeof(sen))) 
    { 
#ifdef Serial_DEBUG 
        Serial.print("Is ok."); 
#endif 
 
        delay(50); 
        network.update(); 
        // If it's time to send a message, send it! 
        while ( network.available() ) 
        { 
            // If so, grab it and print it out 
            RF24NetworkHeader header; 
            receive_a rec; 
            network.read(header, &rec, sizeof(rec)); 
 
            node_clock = rec.node_ms;        //运行时间赋值 
        } 
    } 
#ifdef Serial_DEBUG 
    else 
        Serial.print("Is failed."); 
 
    Serial.println(""); 
#endif 
} 

视频