修理电饭锅的故障

我的松下电饭锅经过四年的使用出现了一个问题:每次做饭的时候伴随着蒸汽会从排气孔向外喷水,一方面这让会让电饭煲每次都脏兮兮的,另一方面做出来的米饭下面有焦糊的情况。

网上搜索了一下,有人遇到同样的现象,解释是:在这种智能电饭煲的锅盖上,有着负责感应水汽的传感器(打开之后是有三组线,一个地,一个应该是温度传感器,还有一个不认识的线)。当出现带着水的蒸汽达到锅盖的时候,传感器会通知处理器,这时候处理器会让电饭锅加热部分断电(继电器),这也就是为什么我们在正常使用的情况下经常听到的“啪,啪”的声音。停止加热后,水汽不会溢出,然后会再次加热,这个过程反复进行,直到电饭锅中的水已经被米饭之类的完全吸收。米饭在没有液态水的状态下会达到105度(之前看到的一个原理介绍,具体的品牌有可能设定不同)。然后整个蒸饭的过程就结束了,新鲜的米饭即可出锅。

拆解了电饭锅对几根线进行了测量,发现确实有一个断掉了。

psb1

因为锅盖和传感器是一体的,只好求助于万能的淘宝。还真找到了卖家,145一套。卖家刚开始得知我是自己维修,不肯卖,后来商量了很久才同意卖给我。

psb2

拿到手就开始了安装。基本上都是塑料件,安装并没有难度,也没有出现因为尺寸差别导致的不好安装

底座:

psb3

正面

psb4

内胆,中间包裹的是一层保温材料,特地摸了一下,肯定不是石棉……话说前一段刚知道我们经常用的热宝,中间居然用石棉隔热保温,太TMD有创造力了

psb5

布线

psb6

煮饭测试,没问题,只是有轻微的水蒸气的痕迹。

psb7

于是顺利收工,不用担心喷的到处都是。

最后顺便说两句:

1.我的电饭锅是松下的,拆开之后看起来做工不错,一方面是改包裹的都包起来了,另一方面是他的各种机构件标准度很高。有做机械的朋友可以解释一下;

2.未来的维修基本上就是更换各种配件了。所谓的“芯片级”维修,除了RD工程师为了研究问题才会去干,恐怕不会有人去干,一方面是成本的问题,更换配件要远比检查省时省力(维修人力成本很高);另一方面是设计的问题,都会尽量模块话,这样对于生成以及降低成本很有帮助,可以做到外包或者使用标准件。

3.说起来电饭煲/电饭锅,很多人都会提起来当年的“三角牌”。但是这个牌子早已经不存在了。历史大约是,刚开始这是一个国有企业的牌子,后来因为质量很好口碑不错,就开始了贴牌,再后来就是卖牌子,相当于授权经营的意思吧,于是就没落了。即使现在看到“三角牌”也早就不是最初我们认识的那个品牌了。当时我用的是“美的”的电饭锅,用了一年就出了问题,总结经验是:不要买“美的” 品牌超过200的东西。为了买新的电饭锅,当时做了很多功课,最后的结论是:还是日本品牌最靠谱。如果你对生活品质有追求,不妨还是考虑买一个好点的日系的电饭锅吧。我所能知道是:你购买一个日本的产品,自己能用的爽,这个品牌在国内产生的价值比给日本的要多。即便日本人真的用这些钱去买子弹造大炮那也是孙子才需要考虑的事情。如果这笔钱捐给了gov,你是否能有孙子都是一个问题。

就是这样。

Arduino 使用I2C的1602 LCD

很多时候我们需要显示简单的字符,1602LCD就是很好的选择。通常Arduino 使用1602 LCD需要连接很多线路,需要占用很多个IO口,而是用I2C版本的1602即可省去这样的繁杂。I2C版本只需要VCC(3.3和5都可以,亮度上有些差别),GND,还有SDA(连接A4)和 SCL(连接A5)。

下面就是一个使用I2C在1602上显示的例子:

20131206613

对应的程序和库在这里

Hw

在使用的时候,你可以遇到如下问题:

1.编译不通过。请检查编译器的版本,建议使用 1.0 以上的,因为这是未来的方向。如果还有问题,请检测引用库的方式。如果你将 LiquidCrystal_I2C 放在了Library下面,可以用 #include 这样的方式来引用,或者像我代码中,直接将LiquidCrystal_I2C.cpp 和 LiquidCrystal_I2C.h 放在源文件同样的目录下,你就可以使用 #include “LiquidCrystal_I2C.h”。此外,如果出现编译错误提示在 LiquidCrystal_I2C lcd(0x27,16,2); 这行,就是没有正确引用到LiquidCrystal_I2C.h这个头文件导致的。你可以按照上述检查一下,然后重启编译器试试。

2.烧写之后无显示,首先请检查地址是否正确(地址不正确的情况下最上面可能有方块,下面看起来是空白行),最直接的方法是询问卖家,此外还可以像我之前的一篇文章提到的烧入一个软件来判定地址。其次要检查背后的电位器是否正常,避免实际上已经有数值了只是对比度不正确你看不到而已。如果仍然不行,就需要重新寻找库。这个差别有可能是如下硬件顺序导致的。

1602

可以看到 P0- P3分别对应 RS/RW/CS/P ,另外,P4-P7对应DB4-7。还有一种计是颠倒过来 P0-P3对应DB4-7的。如果存在差别,库文件初始化方式传输Command方式都需要修改的。剩下的就是检查Pin是否有电平输出,如果没有那就是I2C模块坏掉了。

当然,如果你在上面花费了超过8个小时仍然无法解决,我的建议是赶快更换一篇,这个东西不需要研究那么久,你的1602有可能压根就是坏掉的(非常不幸的是我就是因为这个问题花费了无数个夜晚…..)

我最后购买到OK的1602是在 http://arduinochina.taobao.com/?spm=a1z10.1.w1002-2071319798.3.RT0Fkv (Arduino CHINA)

Arduino 扫描I2C设备的程序

Arduino IO端口有限,使用I2C来进行扩展是一个很好的选择(A4-SDA, A5-SCL)。可以通过下面的程序来完成对I2C,以便确定设备地址。

程序来自 http://www.geek-workshop.com/thread-287-1-1.html ,修正了其中的编译Bug和程序输出结果上的bug,实测工作正常。

Capture

//Below program comes from http://www.geek-workshop.com/thread-287-1-1.html
//According to that page I have fiexed some bugs :
// 1. " rc = twi_writeTo(addr, &data, 0, 1);"
// 2. addr = addr<<1;  This line should be removed
// I have tested below code with my I2C LCD.

#include "Wire.h"
extern "C" {
#include "utility/twi.h"  // from Wire library, so we can do bus scanning
}

byte start_address = 1;
byte end_address = 127;

// Scan the I2C bus between addresses from_addr and to_addr.
// On each address, call the callback function with the address and result.
// If result==0, address was found, otherwise, address wasn't found
// (can use result to potentially get other status on the I2C bus, see twi.c)
// Assumes Wire.begin() has already been called
void scanI2CBus(byte from_addr, byte to_addr, 
                void(*callback)(byte address, byte result) ) 
{
  byte rc;
  byte data = 0; // not used, just an address to feed to twi_writeTo()
  for( byte addr = from_addr; addr <= to_addr; addr++ ) {
    rc = twi_writeTo(addr,&data,0,1,1);
    if(rc==0) callback( addr, rc );
  }
}

// Called when address is found in scanI2CBus()
// Feel free to change this as needed
// (like adding I2C comm code to figure out what kind of I2C device is there)
void scanFunc( byte addr, byte result ) {
  Serial.print("addr: ");
  Serial.print(addr,DEC);
  //ZT Debug addr = addr<<1;
  Serial.print("\t HEX: 0x");
  Serial.print(addr,HEX);
  Serial.println( (result==0) ? "\t found!":"   ");
//  Serial.print( (addr%4) ? "\t":"\n");
}

// standard Arduino setup()
void setup()
{
    pinMode(13,OUTPUT);
    Wire.begin();

    Serial.begin(9600);
    Serial.println("--- I2C Bus Scanner Test ---");
    Serial.print("starting scanning of I2C bus from ");
    Serial.print(start_address,DEC);
    Serial.print(" to ");
    Serial.print(end_address,DEC);
    Serial.println("...");
    Serial.println();

    // start the scan, will call "scanFunc()" on result from each address
    scanI2CBus( start_address, end_address, scanFunc );

    Serial.print("\n");
    Serial.println("--- I2C Bus Scanner Complete ---");

    digitalWrite(13,HIGH); delay(500); digitalWrite(13,LOW); delay(500);
    digitalWrite(13,HIGH); delay(500); digitalWrite(13,LOW); delay(500);
    digitalWrite(13,HIGH); delay(500); digitalWrite(13,LOW); delay(500);
}

void loop() 
{
}

 

PuttyTel的辅助工具

工作需要通过串口来进行Debug。之前工作环境是Windows XP,一直使用一款软件(我也不清楚具体应该叫做什么名字,因为我下载时它的名字是“好用的串口工具.exe”),如同他的名字一样,确实很好用。但是令人郁闷的是自从切换到了Windows7 64位系统下,这个工具工作不再正常。于是又开始寻找起来新的工具。要求是:不能是盗版,必须支持 Windows 7 64Bit,灵活最好不需要安装,能够将Log记录到文本文件中,使用上必须简单………..

后来用了一段时间的 Putty 发现功能非常强大。美中不足是每次打开操作复杂,首先,运行之后会弹出询问权限的窗口,其次需要手动输入com号,而我使用的是USB串口,经常因为插拔而导致端口的变化,于是每次还要去设备管理器查看当前的端口,在打开设备管理器还会弹出权限窗口……..真的很麻烦。

Putty是开源软件,下载代码后试图修改去掉限制。再后来发现Putty一起发布的有个功能单一的PuttyTel,可以通过命令行方式进行端口的指定和调用,权衡安全性和复杂性最后就编写这个工具软件。所谓安全性,指的是如果我自己编译发布Putty,非常可能在代码中加入后门。如果你是直接使用标准版就不会有此之虞。在这个神奇的xx,上述的担忧绝非杞人忧天,在2012年曾经发生过这样的事情,后面有空的时候我会简单介绍一下。

使用方法非常简单:使用时将这个文件和PuttyTel放在同一个目录下,然后运行之后双击当前端口列表中出现的需要串口即可立即调用起来。

源程序在Source目录下。特别注意这是Delphi XE4 下面Build通过的,并没有使用XE4的新特性,如果你使用其他版本Delphi需要修改对应的头文件。

spc

SerialPortChooser

参考:

1.Putty的官方网站(因为安全问题请不要使用国内的Putty软件,为了方便我在EXE目录下放置了一个官网上下载的PuttyTel,你可以通过比对MD5进行验证)
http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html

2.关于Putty命令行调用方式来自
http://tartarus.org/~simon/putty-snapshots/htmldoc/Chapter3.html
例如:-serial com4 -sercfg 115200,8,n,1,N

做了一个太阳罐

使用了一块太阳能电池(黑色部分),一块普通充电电池(自己做的电池盒,手艺生疏了),配合QX5252芯片。选用的是焊接好的QX5252电路板,在淘宝上搜索 “太阳能串灯 圣诞灯 灯具用充电升压电路板 太阳能LED驱动器 独家” 可以找到这个卖家。美中不足的是这家卖的太阳能电池都是苏联风格的—-功能不差,外观粗糙。太阳能电池比较小,就这样平放,晒一天估计能让我这个LED工作4小时左右。

俯视:
x1

阳光下充电中:
2

环境暗下来之后LED会自动点亮:
3

QX5252典型应用的电路图(来自 QX5252 SPEC):

sche

多说两句QX5252是专门设计为LED草坪灯的,它的DC升压之后应该是脉冲形式的供电,我买的电路板上多了一个陶片电容应该就是为了让电压输出平稳一些(具体如何我没用使用示波器查看过)。因此,如果你想把这个芯片当作一个DC升压电路来用的话,有可能会出现问题。比如:那种随着时间颜色不断变化的LED玩具,用这个东西供电之后会始终停留在最初的颜色上。QX5252的一个功能是只有环境光线暗下来来的时候才会点亮LED,这应该是通过判断太阳能电池电压来实现的。

如果不使用QX5252芯片,自己构建电路的话我们需要解决如下问题:

1.DC 的升压 (电池电压通常无法点亮LED)
2.电池的防止过充过放 (充电电池的要求,如果你不使用充电电池那么无需考虑)
3.防止电流倒灌(据说加入肖特基二极管就可以了)
4.环境光线的感知 (应该没人想做一只有阳光才会亮,没有阳光绝对不亮的“太阳能电筒”吧)

QX5252 SPEC

QX5252

如果你用Delphi处理文件发现有乱码,不妨考虑一下编码的问题

最近编写了一个间的的程序,主要的代码是

begin
  AssignFile(rFile,'xyz.csv');
  AssignFile(wFile,'111.txt');
  reset(rFile);
  rewrite(wFile);
  s:='';
  i:=0;
  while NOT eof(rFile) do
    begin
      readln(rFile,s);
      writeln(wFile,s);
    end;
  closefile(rFile);
  closefile(wFile);
end.

 

运行之后发现结果竟然并非预期,这个是原始文件内容

org

这是运行之后的结果

fail

多次查找无法确定原因,后来在想到可能是 unicode导致的问题,找到了下面的文章

http://stackoverflow.com/questions/14232900/unicode-text-file-output-differs-between-xe2-and-delphi-2009

但是因为 Delphi2010 并没有 AssignFile(rFile,”,CP_UTF8); 这样的Override,所以直接升级到XE4.再次运行现象消失问题解决。因此,如果你在读取文本文件时遇到奇怪的无法解释的问题,不妨先考虑是否为 unicode 导致。解决方法上,最简单就是升级Delphi版本,此外还可以使用MemoryStream来自行处理。

区分本地磁盘与移动硬盘

这一系列文章是根据cutebunny 的BLOG “windows的磁盘操作” 写成的,主要是部分修改原作中的代码,使之兼容Unicode和Windows 7 64bit. 原文可以在下面的网址找到

http://cutebunny.blog.51cto.com 。 本文是参考 “windows的磁盘操作之九——区分本地磁盘与移动硬盘”写成。

程序实现了区分当前存储设备是硬盘还是移动硬盘的功能。

// gettype.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "windows.h"

/******************************************************************************
* Function: get the bus type of an disk
* input: drive name (c:)
* output: bus type
* return: Succeed, 0
*         Fail, -1
******************************************************************************/
DWORD GetDriveTypeByBus(const TCHAR *drive, WORD *type)
{
    HANDLE hDevice;               // handle to the drive to be examined
    BOOL result;                 // results flag
    DWORD readed;                   // discard results
 
    STORAGE_DESCRIPTOR_HEADER *pDevDescHeader;
    STORAGE_DEVICE_DESCRIPTOR *pDevDesc;
    DWORD devDescLength;
    STORAGE_PROPERTY_QUERY query;
 
    hDevice = CreateFile(
                    drive, // drive to open
                    GENERIC_READ | GENERIC_WRITE,     // access to the drive
                    FILE_SHARE_READ | FILE_SHARE_WRITE, //share mode
                    NULL,             // default security attributes
                    OPEN_EXISTING,    // disposition
                    0,                // file attributes
                    NULL            // do not copy file attribute
                    );
    if (hDevice == INVALID_HANDLE_VALUE) // cannot open the drive
    {
        fprintf(stderr, "CreateFile() Error: %ld\n", GetLastError());
        return DWORD(-1);
    }
 
    query.PropertyId = StorageDeviceProperty;
    query.QueryType = PropertyStandardQuery;
 
    pDevDescHeader = (STORAGE_DESCRIPTOR_HEADER *)malloc(sizeof(STORAGE_DESCRIPTOR_HEADER));
    if (NULL == pDevDescHeader)
    {
        return (DWORD)-1;
    }
   
    result = DeviceIoControl(
                    hDevice,     // device to be queried
                    IOCTL_STORAGE_QUERY_PROPERTY,     // operation to perform
                    &query,
                    sizeof query,               // no input buffer
                    pDevDescHeader,
                    sizeof(STORAGE_DESCRIPTOR_HEADER),     // output buffer
                    &readed,                 // # bytes returned
                    NULL);      // synchronous I/O
    if (!result)        //fail
    {
        fprintf(stderr, "IOCTL_STORAGE_QUERY_PROPERTY Error: %ld\n", GetLastError());
        free(pDevDescHeader);
        (void)CloseHandle(hDevice);
        return DWORD(-1);
    }
 
    devDescLength = pDevDescHeader->Size;
    pDevDesc = (STORAGE_DEVICE_DESCRIPTOR *)malloc(devDescLength);
    if (NULL == pDevDesc)
    {
        free(pDevDescHeader);
        return (DWORD)-1;
    }
 
    result = DeviceIoControl(
                    hDevice,     // device to be queried
                    IOCTL_STORAGE_QUERY_PROPERTY,     // operation to perform
                    &query,
                    sizeof query,               // no input buffer
                    pDevDesc,
                    devDescLength,     // output buffer
                    &readed,                 // # bytes returned
                    NULL);      // synchronous I/O
    if (!result)        //fail
    {
        fprintf(stderr, "IOCTL_STORAGE_QUERY_PROPERTY Error: %ld\n", GetLastError());
        free(pDevDescHeader);
        free(pDevDesc);
        (void)CloseHandle(hDevice);
        return DWORD(-1);
    }
 
    //printf("%d\n", pDevDesc->BusType);
    *type = (WORD)pDevDesc->BusType;
    free(pDevDescHeader);
    free(pDevDesc);
 
    (void)CloseHandle(hDevice);
    return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
	WORD	t;
    TCHAR   *Driver;

	Driver=L"\\\\.\\c:";

	GetDriveTypeByBus(Driver,&t);

	fprintf(stdout, "%x\n",t);

	Driver=L"\\\\.\\d:";

	GetDriveTypeByBus(Driver,&t);

	fprintf(stdout, "%x\n",t);

	getchar();
	return 0;
}

 

运行结果如下 (注意,运行时需要管理员的权限)

hdorrhd

表示一个是 BusTypeAta 另一个是 BusTypeUsb,前者是固定在机器中的硬盘,后者是U盘。

源代码以及可执行程序

gettype

参考:
1.cutebunny 的BLOG “windows的磁盘操作” 可以在这里下载 WindowsDisk

根据分区确定硬盘

这一系列文章是根据cutebunny 的BLOG “windows的磁盘操作” 写成的,主要是部分修改原作中的代码,使之兼容Unicode和Windows 7 64bit. 原文可以在下面的网址找到

http://cutebunny.blog.51cto.com 。 本文是参考 “windows的磁盘操作之四——根据逻辑分区号获得物理磁盘号”写成。实现的功能简单的说就是输入 c: ,程序返回这个

盘符是处于 \\physicalDriveX 上的。

// GetPD.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "windows.h"

/******************************************************************************
* Function: get disk's physical number from its drive letter
*           e.g. C-->0 (C: is on disk0)
* input: letter, drive letter
* output: N/A
* return: Succeed, disk number
*         Fail, -1
******************************************************************************/
DWORD GetPhysicalDriveFromPartitionLetter(TCHAR letter)
{
    HANDLE hDevice;               // handle to the drive to be examined
    BOOL result;                 // results flag
    DWORD readed;                   // discard results
    STORAGE_DEVICE_NUMBER number;   //use this to get disk numbers
 
    TCHAR path[MAX_PATH];
    wsprintf(path, L"\\\\.\\%c:", letter);
    hDevice = CreateFile(path, // drive to open
                         GENERIC_READ | GENERIC_WRITE,    // access to the drive
                         FILE_SHARE_READ | FILE_SHARE_WRITE,    //share mode
                         NULL,             // default security attributes
                         OPEN_EXISTING,    // disposition
                         0,                // file attributes
                         NULL);            // do not copy file attribute
    if (hDevice == INVALID_HANDLE_VALUE) // cannot open the drive
    {
        fprintf(stderr, "CreateFile() Error: %ld\n", GetLastError());
        return DWORD(-1);
    }
 
    result = DeviceIoControl(
                hDevice,                // handle to device
                IOCTL_STORAGE_GET_DEVICE_NUMBER, // dwIoControlCode
                NULL,                            // lpInBuffer
                0,                               // nInBufferSize
                &number,           // output buffer
                sizeof(number),         // size of output buffer
                &readed,       // number of bytes returned
                NULL      // OVERLAPPED structure
            );
    if (!result) // fail
    {
        fprintf(stderr, "IOCTL_STORAGE_GET_DEVICE_NUMBER Error: %ld\n", GetLastError());
        (void)CloseHandle(hDevice);
        return (DWORD)-1;
    }
    printf("%d %d %d\n\n", number.DeviceType, number.DeviceNumber, number.PartitionNumber);
 
    (void)CloseHandle(hDevice);
    return number.DeviceNumber;
}


int _tmain(int argc, _TCHAR* argv[])
{
	GetPhysicalDriveFromPartitionLetter('c');	
	getchar();
	return 0;
}

 

运行结果如下 (注意,运行时需要管理员的权限)

lt2phy

其中的 7 是 FILE_DEVICE_DISK, 0 表示 PhysicalDrive0 ,2 表示这个硬盘上有2个分区。

GetPD

参考:

1.cutebunny 的BLOG “windows的磁盘操作” 可以在这里下载 WindowsDisk
2.http://msdn.microsoft.com/en-us/library/windows/desktop/bb968800(v=vs.85).aspx IOCTL_STORAGE_GET_DEVICE_NUMBER control code
3.http://msdn.microsoft.com/en-us/library/windows/desktop/bb968801(v=vs.85).aspx STORAGE_DEVICE_NUMBER structure