TinkerNode 制作电器功率记录仪
userHead Zoologist 2020-09-30 10:55:10

今天,我们使用“焦耳”作为能量或功的单位。1 焦耳能量相等于 1 牛顿力的作用点在力的方向上移动 1 米距离所做的功, 1 焦 = 1 瓦·秒。这是为了纪念詹姆斯·普雷斯科特·焦耳(James Prescott Joule)。他英国著名的物理学家,为了表彰他在热学、热力学和电方面的贡献,皇家学会授予他最高荣誉的科普利奖章(CopleyMedal)。后人为了纪念他,把能量或功的单位命名为“焦耳”,简称“焦”;并用焦耳姓氏的第一个字母“J”来标记热量以及“功”的物理量。 

1847 年,焦耳做了迄今认为是设计思想最巧妙的实验:他在量热器里装了水,中间安上带有叶片的转轴,然后让下降重物带动叶片旋转,由于叶片和水的磨擦,水和量热器都变热了。根据重物下落的高度,可以算出转化的机械功;根据量热器内水的升高的温度,就可以计算水的内能的升高值。把两数进行比较就可以求出热功当量的准确值来。焦耳还用鲸鱼油代替水来作实验,测得了热功当量的平均值为 423.9 千克米/千卡。接着又用水银来代替水,不断改进实验方法,直到1878年。这时距他开始进行这一工作将近四十年了,他已前后用各种方法进行了四百多次的实验。 焦耳准确地测定了热功当量,进一步证明了能的转化和守恒定律是客观真理。这一定律的确定,宣告了制造“永动机”的幻想彻底破灭。【参考1】 

随着时代的发展,越来越多家用电器走入日常生活,常见的有电冰箱洗衣机,还有扫地机来承担清洁地板的工作。而这一切都是通过电力驱动的。家用电器都有标注消耗功率的铭牌,但是工作时并不会一直处于满负荷的状态。因此如果想知道一个设备工作时需要的能耗情况就需要功率计来进行测试。这次制作的就是使用 TinkerNode 配合 HP-9800 来实现记录功率消耗情况。 

HP-9800 是宏品电子科技有限公司推出的一款用电检测仪,能够在面板上实时显示当前设备的用电情况,同时提供给了一个 USB 接口将测量结果传输到外部。

projectImage

这次使用 TinkerNode 配合 USBHost MINI,通过 HP-9800 的 USB 接口来实现读取和记录耗电情况。同时 TinkerNode 开发板提供了一个特别的功能:板载一个 4MB 的存储空间,这意味可以通过这个板子方便的实现数据的记录功能。记录完毕之后直接插入电脑即可被识别为一个U盘可以方便读取。HP-9800 USB 输出方式是串口,从 Windows 设备管理器中根据设备 PID 和 VID 可以看到是通过 CH340 USB 串口芯片完成转接的。这是我们这次试验的基本原理。 

接下来介绍实现的硬件部分: 

首先,为了让 USB Host Mini 能在 TinkerNode 上使用,需要实现一个转接板的功能,这个转接板无需任何元件,只是将对应的引脚连接起来:

projectImage
projectImage
projectImage

上述工程可以在【参考2】下载到。 

制作PCB之后焊接排插,即可使用。特别需要注意的是USBHost Mini可能需要Rework【参考3】

projectImage
projectImage

软件方面,比较特别的地方有下面 4 个: 

1. 为了实现对 CH340 的串口通讯,使用了【参考4】给出的库,配合 USB Host 很容易读取到串口数据; 

2. 我们使用板载的 SET 按钮触发记录的事件,这个按钮对应 D3 Pin

projectImage

3. 通过板载的彩灯提示当前正在进行记录, 对应是一个 WS2812 的彩灯。上电之后我们使用红色表示正在记录中,绿色表示没有记录。 

projectImage

4. HP-9800 的采用问答形式,例如,USBHOST 端发送 0x01,0x03,0x00,0x00,0x00,0x14,0x45,0xC5 命令,设备回应数据如下 01 03 28 41 41 D3 42 44 96 EF 3E 71 2D 61 43 0000 48 42 00 00 80 3F 51 37 9A 43 E1 AB C6 3B 64 F2 7A 37 75 C3 5D 40 00 01 0001 A4 F5 解析如下:

projectImage

上面的数据是 4 Byte 的 Float ,可以在 http://lostphp.com/hexconvert/ 进行转换。 

有了读取的硬件和通讯格式,我们就可以取得测量的结果并且保存在文件中。 

完整代码:

代码
#include "FS.h"
#include "FFat.h"
 
// USB Host CH341 库
#include "cdc_ch34x.h"
 
#include <DFRobot_NeoPixel.h>
 
class CH34XAsyncOper : public CDCAsyncOper
{
  public:
    uint8_t OnInit(CH34X *pch34x);
};
 
// 初始化 CH341 USB 转 UART芯片
uint8_t CH34XAsyncOper::OnInit(CH34X *pch34x)
{
  uint8_t rcode;
  LINE_CODING lc;
 
  // 通讯波特率 9600
  lc.dwDTERate = 9600;
  lc.bCharFormat = 0;
  lc.bParityType = 0;
  lc.bDataBits = 8;
  lc.bFlowControl = 0;
 
  rcode = pch34x->SetLineCoding(&lc);
 
  if (rcode)
    ErrorMessage<uint8_t>(PSTR("SetLineCoding"), rcode);
 
  return rcode;
}
 
USB Usb;
CH34XAsyncOper AsyncOper;
CH34X Ch34x(&Usb, &AsyncOper);
 
// 定义发给设备的读取命令
uint8_t readCMD[]={0x01,0x03,0x00,0x00,0x00,0x14,0x45,0xC5};
 
// 定时取得数据的计时器
unsigned long DataElsp=0;
 
// 按键计时器,只有超过特定时间才可以再次按下这样能够避免抖动
unsigned long ButtonElsp=0;
 
// 记录当前灯色,RED 表示正在记录,绿色表示未记录
byte CurrentColor;
 
// 文件名变量
String filePath = "/log.csv";
 
 
// 检查给定的文件名是否存在
bool checkFile(fs::FS &fs, const char * path) {
  Serial.printf("Check file: %s\r\n", path);
  File file = fs.open(path);
  if (!file || file.isDirectory()) {
    return false;
  }
  return true;
}
 
// 写入 message 到 path 给定的文件中, mode 是写入模式,有新建和追加两种模式
void writeFile(fs::FS &fs, const char * path, const char * message,char* mode) {
  // 新建的情况下显示文件名
  if (mode==FILE_WRITE) {
    Serial.printf("Writing file: %s\r\n", path); }
   
  //尝试打开文件,这样判断文件是否存在
  File file = fs.open(path, mode);
  if (!file) {
    Serial.println("- failed to open file for writing");
    return;
  }
   
  // 串口输出写入的数据
  file.print(message);
}
 
// 将 Data 给出的缓冲区中的第 num 个字符串转化为浮点数
float BufferToFloat(uint8_t num,uint8_t *Data){
  float   value;
  uint32_t *pValue; 
   
   pValue=(uint32_t *)&value;
  *pValue=((uint32_t) Data[3+num*4]) | 
          ((uint32_t) Data[4+num*4]<<8)| 
          ((uint32_t) Data[5+num*4]<<16)| 
          ((uint32_t) Data[6+num*4]<<24);
           
  return value;             
}
 
void setup() {
  Serial.begin(115200);
 
  // 打开 U盘
  if (!FFat.begin()) {
    Serial.println("FFat Mount Failed");
        FFat.format();
            Serial.println("Format");
    return;
  }
 
  RGB_LED.begin();
  // 低亮度 
  RGB_LED.setBrightness(DARK);
  // 初始为绿色
  CurrentColor=GREEN;
  RGB_LED.setColor(GREEN);
  RGB_LED.show();
  RGB_LED.show();
   
  // 设置 SET 按钮对应的 GPIO 为输入
  pinMode(D3,INPUT);
 
  if (Usb.Init() == -1)
    Serial.println("OSC did not start.");
 
  delay( 200 );
  Serial.println("Start....");
}
 
void loop() {
  // 距离上一次按键时间大于2秒
  if (millis()-ButtonElsp>2000UL) {
      if (digitalRead(D3)==LOW) {
          // 如果当前是绿灯,那么切换为红色,并且创建一个文件
       if (CurrentColor==GREEN) {
          Serial.println("Switch to Red");
          CurrentColor=RED;
          RGB_LED.setColor(CurrentColor);
          RGB_LED.show();
          RGB_LED.show();
           
          // 尝试创建文件名
          for(int i = 1; i < 20; i++){
                  char fileNameNum[4];
                  sprintf(fileNameNum,"%04d",i);
                  String nowFileName  = (char *)fileNameNum;
                  filePath = "/log"+nowFileName+".csv";
                  // 如果这个文件名不存在,那么就创建之
                  if (!checkFile(FFat, filePath.c_str() )) {
                          Serial.println("Create a log.csv file");
                          // 写入第一行
                          writeFile(FFat, filePath.c_str(), "Time(MS),Current(A),Voltage(V),Power(W)\r\n",FILE_WRITE);          
                          Serial.print(filePath.c_str());
                          Serial.println(" cteated!");
                          break;
                  }
          }
          Serial.println("Log begin...");
       } else
            //如果当前是红色,那么切换为绿灯
       {
        Serial.println("Switch to Green");
        CurrentColor=GREEN;
        RGB_LED.setColor(CurrentColor);
        RGB_LED.show();
        RGB_LED.show();        
        Serial.println("It is paused now.");
        }
     }
     // 按键重新计时 
     ButtonElsp=millis(); 
  }
 
  char s[20];
  uint8_t Buffer[64];
   
  Usb.Task();
 
  if (( Usb.getUsbTaskState() == USB_STATE_RUNNING ) &&
     (CurrentColor==RED) && (millis()-DataElsp>300UL)) {
      if (Ch34x.isReady()) {
          Serial.println("CH34X:Ready!");
 
          // 将命令拷贝到 Buffer 中
          for (uint8_t i=0;i<sizeof(readCMD);i++) {
              Buffer[i]=readCMD[i];
            }
          // 发送读取命令  
          Ch34x.SndData(sizeof(readCMD), Buffer);
           
          Serial.println("RDCMD");
          delay(2000);
           
          // 接收
          uint16_t size = sizeof(Buffer);
          Ch34x.RcvData(&size, Buffer);
          if (size > 0) {
            // 将接收到的数据写入文件
                // 写入时间信息,单位是 100ms
            itoa(millis() /100,s,10);      
            writeFile(FFat, filePath.c_str(), s,FILE_APPEND);
            writeFile(FFat, filePath.c_str(), "," ,FILE_APPEND);
            Serial.print("Time(s):");Serial.println(s);
                // 写入电流信息        
            dtostrf(BufferToFloat(1,Buffer),1,3,s);
            writeFile(FFat, filePath.c_str(), s ,FILE_APPEND);
            writeFile(FFat, filePath.c_str(), "," ,FILE_APPEND);
            Serial.print("Current(A):");Serial.println(s);
                // 写入电压信息  
            dtostrf(BufferToFloat(2,Buffer),1,3,s);
            writeFile(FFat, filePath.c_str(), s ,FILE_APPEND);
            writeFile(FFat, filePath.c_str(), "," ,FILE_APPEND);
            Serial.print("Voltage(V):");Serial.println(s);
                // 写入功率
            dtostrf(BufferToFloat(0,Buffer),1,3,s);
            writeFile(FFat, filePath.c_str(), s ,FILE_APPEND);
            writeFile(FFat, filePath.c_str(), "\r\n" ,FILE_APPEND);
            Serial.print("Power(W):");Serial.println(s);    
             
            // 重置取得数据的定时器
            DataElsp=millis();   
             
            } //if (size > 0)
            else {
              Serial.println("received 0");
              }
          } //if (Ch34x.isReady())
          else {
              Serial.println("CH34X:not ready.");
          }
      }; //stop
}

使用时,上电后 LED 为绿灯,按下 SET 按钮后开始记录数据,LED 为红色。记录结束后插入电脑显示为一个U盘,其中可以看到记录数据的文件。工作的视频,使用 USB 供电,此外还可以直接通过外部供电输入,使用 2 节 18650 串联。 

HP-9800 是家用级别的功率计,如果有需要特别精确的测量设备功率还需要选择更高级的设备。

参考:

2. 立创 EDA 设计的,按道理可以直接开源分享,但是他们平台限制必须有10个以上元件才可以直接分享,所以这里只能提供直接下载。

附件
icon Project_DFRobot TinkerNode NB-IoT-USB Host_2020-05-16_20-29-55.zip 17KB 下载


关于作者 

Zoologist 来自 Arduino 中文社区,爱好使用 Arduino 制作各种有趣的东西。

联系我们
联系邮箱:makercarnival@hotmail.com
官方微信公众号:创客嘉年华
官方微博:上海创客嘉年华
© Copyright Zhiwei Robotics Corp. All Rights Reserved
code 上海创客嘉年华