Search

顯示具有 Arduino 標籤的文章。 顯示所有文章
顯示具有 Arduino 標籤的文章。 顯示所有文章

2019年7月21日 星期日

Cross-Platform Serial Port Library

This is cross-platform(build passing High-Sierra and Win10) serial port library written in C++ , and for demo application wxTerm.


SerialPortLibrary in more detail : Github 

2018年9月9日 星期日

wxNixieClock





wxNixieClock is time sync tool for Nixie Clock project.(Only MAC-OS-X)

g++ -o2 -o wxnixieclock.app wxnixieclock.cpp serialport.cpp connectargsdlg.cpp `wx-config --cxxflags --libs` -m64

1. Pair BT of MAC and Nixie Clock.
2. Open wxNixieClock.app to select tools -> Connect Device -> cu.BT device driver
3. Click SyncTime button
4. Done!

Install wxWidgets dependency in terminal

1. user$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 
2. brew install wxwidgets 


Reference : Nixie Clock

2017年8月1日 星期二

Nixie Clock

C1,C2 如果不裝18650電池就用1000uF以上的電容,R15可以選擇5k~18k 1/4w以上的電阻。Github上有IN-12B與IN-14不過只是管子不同,IN-14的有加NEO-6M-0-001不過校時還沒寫,因為DS3231一年誤差一分鐘而已。







2016年8月18日 星期四

LD7032 0.5" OLED test board

LD7032的測試版上面放了ATMega328與MSP430FR5739,OLED要吃一個15v的電壓與Drive IC吃5v兩個電壓,15v升壓可以參考下面電路。基本上與對岸出的demo board一樣,這東西唯一麻煩的是connector不是普通的FPC而且超難焊很容易融化...插拔幾次後金手指就接觸不良了...,






2015年9月21日 星期一

DCMotor PID Speed Controller

    最近實驗需要弄一個簡易的有機溶液攪拌的裝置,就是在馬達轉子有兩個磁鐵並在溶液中放入小的鐵條,馬達轉動時就會帶動鐵條旋轉就能攪拌溶液了,只不過我這邊需要控制轉速否則太快可能破壞有機分子的三維結構。

    實作上用霍爾IC去感測馬達轉速,並且將結果回授到PID控制器,再由PID計算出PWM大小去控制,這邊我PWM OUTPUT直接接上L293D的ENABLE腳位即可,或者接上電晶體的控制Base Pin也可以。




code:


Reference:

2015年1月16日 星期五

Kossel mini 組裝心得



    很久以前就想要弄一台了,直到上週才把機構相關的零件找齊動工,過程中有遇到大大小小的問題啦...,我在拆上半部的三角固定時,太用力把一個列印角件弄爆...裝皮帶把一片咬合弄斷...還有我發現工具會影響某些部位的安裝順序,總之一些組裝技巧我想就記錄一下。



  1. 上面角件的皮帶軸承要先裝。
  2. 下面角件的步進馬達要先裝。
  3. 擠鋁裝不進角件要先幫擠鋁修切割面的毛邊。
  4. 如果末端效應器跟連桿連接是用釹鐵棚磁鐵,千萬要注意在用AB膠黏之前的方向。
  5. 運動時末端效應器的連桿會稍微脫落,可以在它和滑塊上各自裝個羊眼釘,再連接一個橡皮筋。



    目前我還在校正這一關有問題,官方的頂針我還不會用,有想過將頂針跟金屬底盤各連一條線到Z min 限位的孔,改成機械式接觸導通校正,或者用壓力及光學等等的方式,不過我現在還是手動校正,依然不太好搞等我有心得再補充吧。









    也因為弄了這台讓我又想重寫之前的Project XD , 3D Scanner - wxWidgets+OpenCV
















2014年7月8日 星期二

Arduino WDT ISR

  用AVR C Library有wdt_enable & wdt_disable這兩個Function,他們會用cli清除中斷Flag,所以timer0會被影響因此delay在註冊的WDT ISR會失效只要記得在裡面用sei即可,WDTCSR暫存器除了Set WDE Flag外也要Set WDIE Flag才會在Intterup時進行中斷Jmp到註冊的ISR,若只有Set WDE intterup時會直接Reset,Reference Page54~56 & Table 10-1

wdt_enable:

#define wdt_enable (value)
__asm__ __volatile__ (  \
        "in __tmp_reg__,__SREG__" "\n\t"    \
        "cli" "\n\t"    \
        "wdr" "\n\t"    \
        "out %0,%1" "\n\t"  \
        "out __SREG__,__tmp_reg__" "\n\t"   \
        "out %0,%2" \
        : /* no outputs */  \
        : "I" (_SFR_IO_ADDR(_WD_CONTROL_REG)), \
        "r" (_BV(_WD_CHANGE_BIT) | _BV(WDE)),   \
        "r" ((uint8_t) ((value & 0x08 ? _WD_PS3_MASK : 0x00) | \
            _BV(WDE) | (value & 0x07)) ) \
        : "r0"  \
    )

wdt_disable:


#define wdt_disable ()
__asm__ __volatile__ (  \
    "in __tmp_reg__, __SREG__" "\n\t" \
     "cli" "\n\t" \
    "out %0, %1" "\n\t" \
    "out %0, __zero_reg__" "\n\t" \
    "out __SREG__,__tmp_reg__" "\n\t" \
    : /* no outputs */ \
    : "I" (_SFR_IO_ADDR(_WD_CONTROL_REG)), \
    "r" ((uint8_t)(_BV(_WD_CHANGE_BIT) | _BV(WDE))) \
    : "r0" \
)

wdt_reset:

#define wdt_reset ()  __asm__ __volatile__ ("wdr")


2014年7月2日 星期三

久久一更新

  最近在學校試著弄個讀書會,因為要用到Arduino沒有DIP的ATMega328,所以只好做一個類似Arduino Mini Pro的板子,不過沒有LDO以及板子比Arduino Mini Pro大一點,唯一的優點是可以用單面板就洗出來。




SV8從5到1為GND,VCC,RX,TX,DTR。
SV1從6到1為A5~A0。
SV7從1到12為D2~D13。
LED2是電源LED1是D13測試用。





Downdload:




2014年2月22日 星期六

Ardutester


  上禮拜在FB社團看到一個很酷的project = > ArduTester – Arduino Component Tester
,然後就畫一個PVC1602 LCD的PCB板洗出來玩,這個project用暴力測試的方式測出所有狀況,所以你的電子元件任意插在測試口都能測出來,最主要他還可以測電容跟二極體我認為這部分很讚!


Circuit:

 左上的SMD原件是兩個1206的104p陶瓷電容跟AMS1117 or LM 5V的線性穩壓器,JP3 左邊為pin 1,LCD使用 PVC160203P,JP2 左邊開始為 TX RX DTR VCC GND,JP1 就是Analog測點。





code要自己去原作者網站找
ardutester.ino v0.7f

參考:


ArduTester – Arduino Component Tester
AVR Transistortester

2013年9月29日 星期日

ATmega328P-AU With UNO Bootloader

    最近想到拿ATmega328P-AU寫入Arduino UNO Bootloader來快速做個樣品給人看,結果今天Layout好了零件焊上完全無法用Arduino ISP的方式寫入Bootloader,一直朝更改Boards.txt與avrdude.conf的方向去走,結果弄了幾個小時想到把當作ISP的Digital 10直接接到ATmega328P-AU的Reset,不是走原本預留給Upload程式用的Auto Reset線路上有個104P電容,就可以寫入Bootloader了,不知道為什麼Digital 10多走一個104P電容就沒辦法,總之ATmega328P-AU寫入Arduino UNO Bootloader跟ATmega328P-PU一樣,接下來可以改作STM32F1的cost down版本。




=============2014/01/06===============


    如果發現Upload程式失敗可能是Upload的過程中被原先的程式佔用(?),可以Reset或者改接3.3v供電再試幾次就可以了。




=============2016/07/19===============
若在燒錄bootloader時發生錯誤可以在ArduinoISP board上的RESET與GND並聯一個10uF的電容。

2013年9月26日 星期四

Arduino - RFID NXP MFRC522

  MFRC522是飛利浦的13.56Mhz的RFID Solution,悠遊卡或者高雄一卡通用的,本來想說要自己Layout洗板,不過因為沒熱風槍不知道怎麼黏MFRC522,總之只要按照Document由SPI傳操作暫存器跟指令就可以用了,Mifare One卡的Block 0前面4byte就是UID,第5byte是校驗和由UID的4byte依序XOR沒錯誤就會得到和第5byte相同的值,另外就是一開始很那悶為什麼得到的UID和iBon不同,原來要轉換成8H10D的格式,將Block 0前面4byte十六進位值反向然後直接視為一個4byte的十進位值就是8H10D了,例如得到:0x12 0x34 0x56 0x78 -> 8H10D:0x78563412 = 2018915346,如果不滿十位數就在開頭補零,最後在網路上看到很多Library不過都很亂所以我重寫整理一下和加入一個8H10D格式的example。


                 Arduino                     MFRC522
            Digital 10            ---->     SDA
            Digital 11(MOSI)  ----> MOSI(DI)
            Digital 12(MISO)  ----> MISO(DO)
            Digital 13(SCK) ----> SCLK(CK)
            Digital 5              ---->      RST
                VCC 3.3V  ---->      VCC
                    GND    ---->      GND

MFRC522.h

#ifndef __MFRC522__
#define __MFRC522__


#if ARDUINO >= 100
#include <Arduino.h>
#else
#include <WProgram.h>
#endif

#include <SPI.h>

//data array maxium length
const unsigned int MAX_LEN = 16;

//MFRC522 command bits
const unsigned char PCD_IDLE = 0x00; //NO action; cancel current commands
const unsigned char PCD_AUTHENT = 0x0E; //verify password key
const unsigned char PCD_RECEIVE = 0x08; //receive data
const unsigned char PCD_TRANSMIT = 0x04; //send data
const unsigned char PCD_TRANSCEIVE = 0x0C; //send and receive data
const unsigned char PCD_RESETPHASE = 0x0F; //reset
const unsigned char PCD_CALCCRC = 0x03; //CRC check and caculation

//Mifare_One card command bits
const unsigned char PICC_REQIDL = 0x26; //Search the cards that not into sleep mode in the antenna area
const unsigned char PICC_REQALL = 0x52; //Search all the cards in the antenna area
const unsigned char PICC_ANTICOLL = 0x93; //prevent conflict
const unsigned char PICC_SElECTTAG = 0x93; //select card
const unsigned char PICC_AUTHENT1A = 0x60; //verify A password key
const unsigned char PICC_AUTHENT1B = 0x61; //verify B password key
const unsigned char PICC_READ = 0x30; //read
const unsigned char PICC_WRITE = 0xA0; //write
const unsigned char PICC_DECREMENT = 0xC0; //deduct value
const unsigned char PICC_INCREMENT = 0xC1; //charge up value
const unsigned char PICC_RESTORE = 0xC2; //Restore data into buffer
const unsigned char PICC_TRANSFER = 0xB0; //Save data into buffer
const unsigned char PICC_HALT = 0x50; //sleep mode


//THe mistake code that return when communicate with MFRC522
const unsigned char MI_OK = 0;
const unsigned char MI_NOTAGERR = 1;
const unsigned char MI_ERR = 2;


//------------------MFRC522 register ---------------
//Page 0:Command and Status
const unsigned char Reserved00 = 0x00;
const unsigned char CommandReg = 0x01;
const unsigned char CommIEnReg = 0x02;
const unsigned char DivlEnReg = 0x03;
const unsigned char CommIrqReg = 0x04;
const unsigned char DivIrqReg = 0x05;
const unsigned char ErrorReg = 0x06;
const unsigned char Status1Reg = 0x07;
const unsigned char Status2Reg = 0x08;
const unsigned char FIFODataReg = 0x09;
const unsigned char FIFOLevelReg = 0x0A;
const unsigned char WaterLevelReg = 0x0B;
const unsigned char ControlReg = 0x0C;
const unsigned char BitFramingReg = 0x0D;
const unsigned char CollReg = 0x0E;
const unsigned char Reserved01 = 0x0F;
//Page 1:Command
const unsigned char Reserved10 = 0x10;
const unsigned char ModeReg = 0x11;
const unsigned char TxModeReg = 0x12;
const unsigned char RxModeReg = 0x13;
const unsigned char TxControlReg = 0x14;
const unsigned char TxAutoReg = 0x15;
const unsigned char TxSelReg = 0x16;
const unsigned char RxSelReg = 0x17;
const unsigned char RxThresholdReg = 0x18;
const unsigned char DemodReg = 0x19;
const unsigned char Reserved11 = 0x1A;
const unsigned char Reserved12 = 0x1B;
const unsigned char MifareReg = 0x1C;
const unsigned char Reserved13 = 0x1D;
const unsigned char Reserved14 = 0x1E;
const unsigned char SerialSpeedReg = 0x1F;
//Page 2:CFG
const unsigned char Reserved20 = 0x20;
const unsigned char CRCResultRegM = 0x21;
const unsigned char CRCResultRegL = 0x22;
const unsigned char Reserved21 = 0x23;
const unsigned char ModWidthReg = 0x24;
const unsigned char Reserved22 = 0x25;
const unsigned char RFCfgReg = 0x26;
const unsigned char GsNReg = 0x27;
const unsigned char CWGsPReg = 0x28;
const unsigned char ModGsPReg = 0x29;
const unsigned char TModeReg = 0x2A;
const unsigned char TPrescalerReg = 0x2B;
const unsigned char TReloadRegH = 0x2C;
const unsigned char TReloadRegL = 0x2D;
const unsigned char TCounterValueRegH = 0x2E;
const unsigned char TCounterValueRegL = 0x2F;
//Page 3:TestRegister
const unsigned char Reserved30 = 0x30;
const unsigned char TestSel1Reg = 0x31;
const unsigned char TestSel2Reg = 0x32;
const unsigned char TestPinEnReg = 0x33;
const unsigned char TestPinValueReg = 0x34;
const unsigned char TestBusReg = 0x35;
const unsigned char AutoTestReg = 0x36;
const unsigned char VersionReg = 0x37;
const unsigned char AnalogTestReg = 0x38;
const unsigned char TestDAC1Reg = 0x39;
const unsigned char TestDAC2Reg = 0x3A;
const unsigned char TestADCReg = 0x3B;
const unsigned char Reserved31 = 0x3C;
const unsigned char Reserved32 = 0x3D;
const unsigned char Reserved33 = 0x3E;
const unsigned char Reserved34 = 0x3F;
//-----------------------------------------------

class MFRC522
{
public:
MFRC522();

void Write(const unsigned char,const unsigned char);
unsigned char Read(const unsigned char);
void SetBitMask(const unsigned char,const unsigned char);
void ClearBitMask(const unsigned char,const unsigned char);
void AntennaOn();
void AntennaOff();
void Reset();
void Init();

unsigned char MFRC522ToCard(const unsigned char,
const unsigned char*,
const unsigned char,
unsigned char*,
unsigned int*);
unsigned char WriteBlock(unsigned char,unsigned char*);
unsigned char Request(unsigned char,unsigned char*);
unsigned char Anticoll(unsigned char*);
void CalulateCRC(unsigned char*,unsigned char,unsigned char*);
unsigned char SelectTag(unsigned char*);
void Halt();
private:
const static int chip_select_pin = 10;
const static int NRSTPD = 5;
};

#endif

MFRC522.cpp

#include <MFRC522.h>

#if ARDUINO >= 100
#include <Arduino.h>
#else
#include <WProgram.h>
#endif

MFRC522::MFRC522()
{
pinMode(chip_select_pin,OUTPUT);
digitalWrite(chip_select_pin,LOW);

    pinMode(NRSTPD,OUTPUT);
digitalWrite(NRSTPD,HIGH);
}

void MFRC522::Write(const unsigned char address,const unsigned char value)
{
digitalWrite(chip_select_pin,LOW);
SPI.transfer((address << 1) & 0x7E);
    SPI.transfer(value);
digitalWrite(chip_select_pin,HIGH);
}

unsigned char MFRC522::Read(const unsigned char address)
{
unsigned char value;

digitalWrite(chip_select_pin,LOW);
    SPI.transfer(((address << 1) & 0x7E) | 0x80);
    value = SPI.transfer(0x00);
digitalWrite(chip_select_pin,HIGH);

return value;
}

void MFRC522::SetBitMask(const unsigned char reg,const unsigned char mask)
{
    unsigned char temp;
    temp = Read(reg);
    Write(reg,temp | mask);
}

void MFRC522::ClearBitMask(const unsigned char reg,const unsigned char mask)
{
    unsigned char temp;
    temp = Read(reg);
    Write(reg,temp & (~mask)); // clear bit mask
}

void MFRC522::AntennaOn()
{
    unsigned char temp;

    temp = Read(TxControlReg);
    if(!(temp & 0x03)){
SetBitMask(TxControlReg,0x03);
}
}

void MFRC522::AntennaOff()
{
ClearBitMask(TxControlReg,0x03);
}

void MFRC522::Reset()
{
Write(CommandReg,PCD_RESETPHASE);
}

void MFRC522::Init()
{
Reset();

    Write(TModeReg,0x8D);
    Write(TPrescalerReg,0x3E);
    Write(TReloadRegL,30);
    Write(TReloadRegH,0);
   
    Write(TxAutoReg,0x40);
    Write(ModeReg,0x3D);

AntennaOn();
}

unsigned char MFRC522::MFRC522ToCard(const unsigned char command,const unsigned char *send_data,const unsigned char send_len,unsigned char *back_data,unsigned int *back_len)
{
    unsigned char status = MI_ERR;
    unsigned char irqEn = 0x00;
    unsigned char waitIRq = 0x00;
    unsigned char lastBits;
    unsigned char n;
    unsigned int i;

    switch(command)
    {
        case PCD_AUTHENT: //verify card password
        {
            irqEn = 0x12;
            waitIRq = 0x10;
            break;
        }
        case PCD_TRANSCEIVE: //send data in the FIFO
        {
            irqEn = 0x77;
            waitIRq = 0x30;
            break;
        }
        default:
            break;
    }
 
    Write(CommIEnReg,irqEn | 0x80); //Allow interruption
    ClearBitMask(CommIrqReg,0x80); //Clear all the interrupt bits
    SetBitMask(FIFOLevelReg,0x80); //FlushBuffer=1, FIFO initilizate
   
    Write(CommandReg,PCD_IDLE); //NO action;cancel current command ???

    //write data into FIFO
    for (i = 0; i < send_len;++i){
        Write(FIFODataReg,send_data[i]);
    }

    //procceed it
    Write(CommandReg,command);
    if (command == PCD_TRANSCEIVE){
SetBitMask(BitFramingReg,0x80); //StartSend=1,transmission of data starts
    }
   
    //waite receive data is finished
    i = 2000; //i should adjust according the clock, the maxium the waiting time should be 25 ms???
    do {
        //CommIrqReg[7..0]
        //Set1 TxIRq RxIRq IdleIRq HiAlerIRq LoAlertIRq ErrIRq TimerIRq
        n = Read(CommIrqReg);
        --i;
    }

    while ((i != 0) && !(n & 0x01) && !(n & waitIRq));

    ClearBitMask(BitFramingReg,0x80); //StartSend=0
   
    if (i != 0){
if(!(Read(ErrorReg) & 0x1B)){ //BufferOvfl Collerr CRCErr ProtecolErr
            status = MI_OK;
            if(n & irqEn & 0x01){
                status = MI_NOTAGERR; //??
            }
           
            if(command == PCD_TRANSCEIVE){
                n = Read(FIFOLevelReg);
                lastBits = Read(ControlReg) & 0x07;
                if (lastBits){
                    *back_len = (n - 1) * 8 + lastBits;
                }
                else{
                    *back_len = n*8;
                }
               
                if(n == 0){
                    n = 1;
                }
                if (n > MAX_LEN){
                    n = MAX_LEN;
                }
               
                //read the data from FIFO
                for (i=0; i < n;++i){
                    back_data[i] = Read(FIFODataReg);
                }
            }
        }
        else{
            status = MI_ERR;
        }
       
    }
   
    //SetBitMask(ControlReg,0x80); //timer stops
    //Write(CommandReg, PCD_IDLE);

    return status;
}

unsigned char MFRC522::WriteBlock(unsigned char block_address,unsigned char  *write_data)
{
    unsigned char status;
    unsigned int recvBits;
    unsigned char i;
    unsigned char buff[18];
   
    buff[0] = PICC_WRITE;
    buff[1] = block_address;
    CalulateCRC(buff,2,&buff[2]);
    status = MFRC522ToCard(PCD_TRANSCEIVE,buff,4,buff,&recvBits);

    if ((status != MI_OK) || (recvBits != 4) || ((buff[0] & 0x0F) != 0x0A)){
        status = MI_ERR;
    }
       
    if (status == MI_OK){
for (i=0;i < 16;++i){ //Write 16 bytes data into FIFO
            buff[i] = *(write_data+i);
        }
        CalulateCRC(buff,16,&buff[16]);
        status = MFRC522ToCard(PCD_TRANSCEIVE,buff,18,buff,&recvBits);
       
        if ((status != MI_OK) || (recvBits != 4) || ((buff[0] & 0x0F) != 0x0A)){
            status = MI_ERR;
        }
    }
   
    return status;
}

unsigned char MFRC522::Request(unsigned char mode,unsigned char *tag_type)
{
    unsigned char status;
    unsigned int backBits; //the data bits that received

    Write(BitFramingReg,0x07); //TxLastBists = BitFramingReg[2..0] ???
   
    tag_type[0] = mode;
    status = MFRC522ToCard(PCD_TRANSCEIVE,tag_type,1,tag_type,&backBits);

    if ((status != MI_OK) || (backBits != 0x10)){
        status = MI_ERR;
    }
 
    return status;
}

unsigned char MFRC522::Anticoll(unsigned char *uid)
{
    unsigned char status;
    unsigned char i;
    unsigned char serNumCheck=0;
    unsigned int unLen;
   
    //ClearBitMask(Status2Reg, 0x08); //strSensclear
    //ClearBitMask(CollReg,0x80); //ValuesAfterColl
    Write(BitFramingReg,0x00); //TxLastBists = BitFramingReg[2..0]

    uid[0] = PICC_ANTICOLL;
    uid[1] = 0x20;
    status = MFRC522ToCard(PCD_TRANSCEIVE,uid,2,uid,&unLen);

    if (status == MI_OK){
        //Verify card UID
        for (i=0;i < 4;++i){
            serNumCheck ^= uid[i];
        }
        if (serNumCheck != uid[i]){
            status = MI_ERR;
        }
    }

    //SetBitMask(CollReg, 0x80); //ValuesAfterColl=1

    return status;
}

void MFRC522::CalulateCRC(unsigned char *input,unsigned char len,unsigned char *output)
{
    unsigned char i,n;

    ClearBitMask(DivIrqReg,0x04); //CRCIrq = 0
    SetBitMask(FIFOLevelReg,0x80); //Clear FIFO pointer
    //Write(CommandReg, PCD_IDLE);

    //Write data into FIFO
    for (i=0;i < len;i++){
        Write(FIFODataReg,*(input + i));
    }
    Write(CommandReg,PCD_CALCCRC);

    //waite CRC caculation to finish
    i = 0xFF;
    do {
        n = Read(DivIrqReg);
        --i;
    }
    while ((i != 0) && !(n & 0x04)); //CRCIrq = 1

    //read CRC caculation result
    output[0] = Read(CRCResultRegL);
    output[1] = Read(CRCResultRegM);
}

unsigned char MFRC522::SelectTag(unsigned char *uid)
{
unsigned char i;
unsigned char status;
unsigned char size;
unsigned int recvBits;
unsigned char buffer[9];

//ClearBitMask(Status2Reg, 0x08);                        //MFCrypto1On=0

buffer[0] = PICC_SElECTTAG;
buffer[1] = 0x70;

for (i=0; i<5; i++){
    buffer[i + 2] = *(uid + i);
}

  CalulateCRC(buffer,7,&buffer[7]);

  status = MFRC522ToCard(PCD_TRANSCEIVE,buffer,9,buffer,&recvBits);
  if ((status == MI_OK) && (recvBits == 0x18)){
    size = buffer[0];
  }
  else{
    size = 0;
  }

  return size;
}

void MFRC522::Halt()
{
    unsigned char status;
    unsigned int unLen;
    unsigned char buff[4];

    buff[0] = PICC_HALT;
    buff[1] = 0;
    CalulateCRC(buff,2,&buff[2]);

    status = MFRC522ToCard(PCD_TRANSCEIVE,buff,4,buff,&unLen);
}


8H10D.ino

#include <MFRC522.h>
#include <SPI.h>

/* 16^0 ~ 16^7 */
unsigned long table[8] = {1,16,256,4096,65536,1048576,16777216,268435456};

MFRC522 mfrc522;

unsigned char uid[5];

void setup()
{
  Serial.begin(9600);
  SPI.begin();
  mfrc522.Init();
}

void loop()
{
  unsigned char status;
  unsigned char str[MAX_LEN];

  status = mfrc522.Request(PICC_REQIDL,str);
  if (status != MI_OK){
    return;
  }

  status = mfrc522.Anticoll(str);
  if (status == MI_OK){
    memcpy(uid,str,5);
    mfrc522.SelectTag(uid); //防止同張卡片持續感應與讀取
 
    Serial.print("Original UID : ");
    unsigned long sum = 0; //將UID每位byte加總(4byte uid & 1byte check)
    for(int i = 3;i >= 0;--i){
      unsigned long temp_h = (uid[i] >> 4) & 0x0000000F;
      Serial.print(temp_h,HEX);
      sum += (temp_h * table[(i*2)+1]); //加上UID每byte的高4bit
      unsigned long temp_l = (uid[i] & 0x0000000F);
      Serial.print(temp_l,HEX);
      sum += (temp_l * table[i*2]); //加上UID每byte的低4bit
    }
    Serial.println("");
 
    Serial.print("8H10D UID : ");
    String _8H10D = String(sum);
    if(_8H10D.length() < 10){
      for(int i = 0;i < (10 - _8H10D.length());++i){
        Serial.print("0"); //8H10D格式未滿十碼開頭補零
      }
    }
    for(int i = 0;i < _8H10D.length();++i){
      Serial.print(_8H10D.charAt(i));
    }
  }
  Serial.println("");

  mfrc522.Halt();
  delay(100);
}

參考

MFRC522 Datasheet

2013年6月9日 星期日

Arduino - PIR Sensor(ZEPIR0AAS01SBCG )

  學校作業做物聯網相關,所以就借一大堆Sensor來玩,人體紅外線跟一般三隻腳的不同,借到的ZEPIR0AAS01SBCG有很多隻腳,可以選擇兩個Mode,一個是跟三隻腳的用法一樣,另一個是Serial的方式,可以看下面參考的Datasheet。

VCC         --->         3.3V
GND         --->         GND
MD/RST   --->         GPIO
LG            --->         3.3V


const int PIR_PIN = 5;

void setup()
{
  Serial.begin(9600);
  pinMode(PIR_PIN,INPUT);
}

void loop()
{
  boolean pir = digitalRead(PIR_PIN);

  Serial.println(pir);

  delay(10);
}

參考:

2013年1月28日 星期一

wxArduino IDE(?) Compile and Uploader Test

  今天早上沒事做想說來寫個Arduino IDE順便研究AVR-Toolchain的Command,UI用wxWidgets刻然後底下包AVR-GCC相關工具 and Avrdude,前者要負責編譯code以及用到的Arduino Library,所以自己的IDE要寫個解析Include多少Arduino Library的Function,最後將這些編譯後檔案的obj File與Arduino自帶的Core328(ATmega328) Link完之後產生HEX File,再由給定的Command(MCU的Baudrate と Clock と Serial Port..etc)傳給Avrdude然後將HEX File上傳到MCU上,用wxWidgets可以偷懶直接用wxExecute之類的在背景開個Shell去跑AVR-Toolchain,不過我比較喜歡用該平台的API另外開個Thread去跑,下面一個簡單的demo,主要把Blink Examples Code編譯然後上傳到Arduino,第二次把Code暗與亮從50ms改成500ms然後重新編譯上傳看到LED差異證明確實有功能。




參考:


  1. Arduino Build Process
  2. Arduino Uploader – command line utility for compiling and uploading Arduino sketch
  3. WinAVR 初體驗

2013年1月24日 星期四

PID Controller

  去年暑假無意間看到有paper用基因演算法去自動調整PID控制參數,感覺上非常實用也很有趣不過工作很忙一直沒時間研究,這幾天又想趁有空閒來實現那個paper,又突然看到Arduino PID Library的作者的Blog解說,發現跟一般普通理想化的PID公式不同,因此讀完了以下參考前八篇(Arduino PID Library)以及第九篇(補充說明),跟著每一篇實作code與推導公式收益良多!

PID.h


#ifndef __PID__
#define __PID__

const bool DIRECT = true;
const bool REVERSE = false;

const bool ON = true;
const bool OFF = false;

class PID
{
   public:
      PID(double*,double*,double*,double,double,double,unsigned long,bool,double,double,double);
      void Computing();
      void SetTunings(double,double,double);
      void SetSampleTime(unsigned long);
      void SetOutputLimits(double,double);
      void SetOnOff(bool);
      void SetDirection(bool);

   private:
      double Kp,Ki,Kd;
      double *input,*output;
      double *setpoint;

      double integral;
      double last_input;

      double min,max;

      unsigned long last_time;
      unsigned long sample_time;

      bool on_off;
      bool direction;
};

#endif


PID.cpp


#if ARDUINO >= 100
#include <Arduino.h>
#else
#include <WProgram.h>
#endif

#include "PID.h"

PID::PID(double *_input,double *_output,double *_setpoint,
      double _Kp,double _Ki,double _Kd,
      unsigned long _sample_time = 100000,
      bool _on_off = OFF,double _direction = DIRECT,
      double _min = 0,double _max = 255)
{
   input = _input;
   output = _output;
   setpoint = _setpoint;

   sample_time = _sample_time;

   on_off = _on_off;

   SetOutputLimits(_min,_max);

   SetDirection(_direction);

   SetTunings(_Kp,_Ki,_Kd);

   last_time = (micros() - sample_time);
}

void PID::Computing()
{
   if(on_off == OFF){
      return ;
   }

   unsigned long now = micros();

   if((now - last_time) >= sample_time){
      double error = (*setpoint - *input);

      integral += (Ki * error);
      if(integral > max){
integral = max;
      }
      else if(integral < min){
integral = min;
      }

      double diff_input = (*input - last_input);

      *output = (Kp * error) + integral - (Kd * diff_input);

      if(*output > max){
*output = max;
      }
      else if(*output < min){
*output = min;
      }

      last_input = *input;
      last_time = now;
   }
}

void PID::SetTunings(double _Kp,double _Ki,double _Kd)
{
   if((_Kp < 0) || (_Ki < 0) || (_Kd < 0)){
      return ;
   }

   double SampleTimeInSec = ((double)sample_time) / 1000000.0f;

   Kp = _Kp;
   Ki = _Ki * SampleTimeInSec;
   Kd = _Kd / SampleTimeInSec;

   if(direction != REVERSE){
      Kp = (0 - Kp);
      Ki = (0 - Ki);
      Kd = (0 - Kd);
   }
}

void PID::SetSampleTime(unsigned long _sample_time)
{
   if(_sample_time > 0){
      double ratio = ((double)_sample_time / (double)sample_time);

      Ki *= ratio;
      Kd /= ratio;

      sample_time = _sample_time;
   }
}

void PID::SetOutputLimits(double _min,double _max)
{
   if(_min > _max){
      return ;
   }

   min = _min;
   max = _max;

   if(on_off == ON){
      if(*output > max){
*output = max;
      }
      else if(*output < min){
*output = min;
      }

      if(integral > max){
integral = max;
      }
      else if(integral < min){
integral = min;
      }
   }
}

void PID::SetOnOff(bool _on_off)
{
   bool new_state = (_on_off == ON);

   if(new_state == (!on_off)){
      integral = *output;
      last_input = *input;

      if(integral > max){
integral = max;
      }
      else if(integral < min){
integral = min;
      }
   }

   on_off = new_state;
}

void PID::SetDirection(bool _direction)
{
  if((on_off == ON) && (_direction != direction)){
      Kp = (0 - Kp);
      Ki = (0 - Ki);
      Kd = (0 - Kd);
   }

   direction = _direction;
}




參考:

  第一篇是基本的PID公式,第二篇將採樣時間固定而消去幾次除法運算(Kalman Filter等等的微積分時間也可以這樣簡化計算),第三篇說明如何做Anti-Derivative Kick,第四篇說明如何消去中途修改PID參數的落差,第五篇說明如何做Anti-Reset Windup,第六篇其實就是說中途中止計算要記得直接跳出不要更改狀態,第七篇接續前一篇如果要恢復記得要回到上次計算的狀態,第八篇說明計算結果方向(正負號),例如Input大於Setpoint就增加Output(正向),或者Input小於Setpoint就增加Output(反向),第九篇算是補充說明積分項計算時間點的差異。

  1. Improving the Beginner’s PID – Introduction
  2. Improving the Beginner’s PID – Sample Time
  3. Improving the Beginner’s PID – Derivative Kick
  4. Improving the Beginner’s PID: Tuning Changes
  5. Improving the Beginner’s PID: Reset Windup
  6. Improving the Beginner’s PID: On/Off
  7. Improving the Beginner’s PID: Initialization
  8. Improving the Beginner’s PID: Direction
  9. PID: When Should I Compute the Integral Term?

2012年12月18日 星期二

wxWidgets Connect Arduino(Windows)

    跟之前的wxWidgets Connect Arduino(Linux)一樣,只不過變成Windows版,因為只用Serial Port所以Enumeration就直接一個一個Create,如果改天要用其他如USB的Device寫成Driver會比較好處理,最後非同步有開/Od要處理WriteFile的ERROR_IO_PENDING而開/O2不用,另外我是在Windows 7 x64測試。

2012年12月7日 星期五

Arduino - SD Card

    趁這兩天工作又被隊友delay又跑去研究其他姿態穩定的濾波演算法,Arduino IDE和Library好像沒有把數據存成文字檔的功能,也不想自己另外弄個receive程式,所以還是直接用SD Card去存,然後將文字資料丟到Google Doc就能製作成曲線圖方便觀察濾波效果,不過SPI我很少用所以常常忘記接法,所以還是記錄上來。

                 Arduino                     SD Card
            Digital 4  (XCK) ---->        CS
            Digital 11(MOSI)  ----> MOSI(DI)
            Digital 12(MISO)  ----> MISO(DO)
            Digital 13(SCK) ----> SCLK(CK)
                VCC 3.3V  ---->      VCC
                    GND    ---->      GND

Heartbeat Sensor的值製成曲線圖。



#include <SD.h>

int sensor_pin = 0;
int n = 0;
int n_1 = 0;
int diff = 0;
int count = 0;
int elapse_up = 0;
int elapse_down = 0;
int integral_plus = 0;
int integral_minus = 0;

void setup()
{
  Serial.begin(9600);
  pinMode(10,OUTPUT);
  if(!SD.begin(4)){
    Serial.println("SD Card Error!");
    return ;
  }
}

void loop()
{
  File file = SD.open("data.txt",FILE_WRITE);

  n = analogRead(sensor_pin); //取得現在得到的值
  diff = (n - n_1);           //將現在的值減去上次得到的值(N -(N-1))
  n_1 = n;                    //更新上一次的值

  file.println(n / 5);

  //如果差值趨近零,則表示到達波谷又要進到新的一個PWM中。
  //並且在確定上個PWM的爬升(integral_plus)與下降(integral_minus)
  //是否有到達積分值來確定是否有PWM。
  //兩者成立則更新所有狀態。
  if(diff < 10 && diff > -10 &&
  integral_plus > 160 && integral_minus < -200){
    if(elapse_up > 50 && elapse_up < 400 &&
    elapse_down > 96 && elapse_down < 800){
      ++count;
      Serial.print(count);
      Serial.println(" Heartbeat");
    }
    elapse_up = 0;
    elapse_down = 0;
    integral_plus = 0;
    integral_minus = 0;
  }
  else if(diff > 20 && diff < 280){
    //PWM上升
    integral_plus += diff;
    elapse_up += 20;
  }
  else if(diff < -20 && diff > -200){
    //PWM下降
    integral_minus += diff;
    elapse_down += 20;
  }

  file.close();

  //每20ms取樣,即一秒取樣50次。
  delay(20);
}

Kalman Filter

    基本上所有參數跟成員的意思跟Simple Kalman Filter都差不多(參考預測與更新七個步驟),初始化r_measure是觀測誤差q_angle與q_bias是在加速度計與陀螺儀的預測選擇較相信誰arg_angle是初始角度,而member function 的 Update沒有空行的code表示是同一組算式,第一組計算當前陀螺儀的角速度減去前次計算出的偏移,接著將角度加上這個差的角度,第二組P是一個2x2 Matrix,內容是推導後的結果用來計算預測估計誤差,第三組量測現在加速度計角度與上次角度的差,第四組計算預測與觀測的Covariance,第五組計算最佳卡爾曼增益,第六組由卡爾曼增益乘上前次與這次角度差決定角度與偏移要更新多少,第七組則是用卡爾曼增益乘上當前預測估計誤差然後更新下個預測估計誤差,這七個步驟的前兩個是計算預測,後五個才是真正的更新,順序都與上面的wiki參考預測與更新七個步驟一樣,然後這整個系統的觀測時間統計是個隱馬爾可夫模型


Acc只有加速度計,CF用互補濾波加上陀螺儀修正,KF用卡爾曼濾波加上陀螺儀修正。


Kalman Filter:


#ifndef __KALMAN_FILTER__
#define __KALMAN_FILTER__

class KalmanFilter
{
  public:

  KalmanFilter(double r_measure,double q_angle,double q_bias,double arg_angle)
  {
    Q_angle = q_angle;
    Q_bias = q_bias;
    R_measure = r_measure;
    angle = arg_angle;
 
    bias = 0;
    P[0][0] = 0;
    P[0][1] = 0;
    P[1][0] = 0;
    P[1][1] = 0;
  }

  /* Accelerometer Angle , Gyro Rate , dt */
  double Update(double acc,double gyro,double dt)
  {
    rate = gyro - bias;
    angle += rate * dt;
 
    P[0][0] += dt * (dt * P[1][1] - P[0][1] - P[1][0] + Q_angle);
    P[0][1] -= dt * P[1][1];
    P[1][0] -= dt * P[1][1];
    P[1][1] += Q_bias * dt;
 
    angle_diff = acc - angle;
 
    S = P[0][0] + R_measure;
 
    K[0] = P[0][0] / S;
    K[1] = P[1][0] / S;
 
    angle += K[0] * angle_diff;
    bias += K[1] * angle_diff;
 
    P[0][0] -= K[0] * P[0][0];
    P[0][1] -= K[0] * P[0][1];
    P[1][0] -= K[1] * P[0][0];
    P[1][1] -= K[1] * P[0][1];
 
    return angle;
  }

  private:

  double R_measure;
  double Q_angle;
  double Q_bias;

  double angle;
  double bias;
  double rate;

  double angle_diff;
  double S;
  double P[2][2];
  double K[2];
};

#endif


Used:

    下面初始參數適用幾乎所有的IMU除非有特別要自己設定。


KalmanFilter KFx(0.03f,0.001f,0.003f,0.0f);
KalmanFilter KFy(0.03f,0.001f,0.003f,0.0f);
unsigned long last_KF_computing_time;     //us





void loop()
{
  double KF_x_angle = KFx.Update(AccAngleX,GyroRateYaw,
  (double)(micros() - last_KF_computing_time) / 1000000.0f);

  double KF_y_angle = KFx.Update(AccAngleY,GyroRatePitch,
  (double)(micros() - last_KF_computing_time) / 1000000.0f);

  last_KF_computing_time = micros();
}

2012年12月6日 星期四

Complementary Filter

    互補濾波是另一種比較簡單取得平滑的採樣值的方法,計算起來比卡爾曼濾波簡單,要將兩個感測器的值互補只要對一方做高通濾波(陀螺儀瞬時動態較靈敏所以高頻的值較有效)一方做低通濾波(加速度計長時間靜態的值較準確所以低頻的值較有效)然後相加,但兩者乘上的係數相加要等於一,這篇文章有比較詳細的說明我是參考他的,將陀螺儀的角速度乘上時間微分dt然後連續積分就可以得到該時間累積的角度,再與加速度計的角度做互補濾波即可,另外WMP與Wii Nunchuk使用延伸模式時對I2C操作跟單純使用單一個不一樣(參考Activated Wii Motion Plus in Nunchuck passthrough mode),使用這個模式兩者好像會有3ms左右的採樣延遲(MultiWii Source Code - IMU.ino內的computeIMU注解說的,我沒計算過驗證)。

方向


Acc只有加速度計Noise突出很多,AccGyro有用陀螺儀做互補所以較平滑。


Complementary Filter.ino


#include <Wire.h>

uint8_t buffer[6];
int xID;

double AccAngleX = 0,AccAngleY = 0;

int GyroLastYaw = 0,GyroLastPitch = 0,GyroLastRoll = 0;
double GyroAngleYaw = 0,GyroAnglePitch = 0,GyroAngleRoll = 0;

double HighPass = 0.93f,LowPass = 0.07f;
double AngleX = 0,AngleY = 0;

unsigned long last_loop_time;       //ms
unsigned long last_gyro_computing_time;  //us

void setup()
{
  Serial.begin(115200);
  Wire.begin();
  Init6DOF();
  last_loop_time = millis();
  last_gyro_computing_time = micros();
}

void loop()
{
  if(millis() > (last_loop_time + 10)){
    IICRead(buffer,0x52,6);
 
    if((buffer[5] & 0x03) == 0x00){
      ComputingNunchuk(buffer);
    }
    else if((buffer[5] & 0x03) == 0x02){
      ComputingWMP(buffer);
    }
 
    ComplementaryFilter();
 
    IICWrite(0x52,0x00);
    last_loop_time = millis();
  }
}

void Init6DOF()
{
  delay(100);

  IICWrite(0x53,0xFE,0x05);
  Serial.println("Passthrough Mode Ok!");
  delay(100);

  IICWrite(0x53,0xF0,0x55);
  Serial.println("Init WMP Ok!");
  delay(100);

  IICWrite(0x52,0xFA);
  Serial.println("Set Reading Address 0xFA Ok!");
  delay(100);

  IICRead(buffer,0x52,6);
  xID = buffer[0] + buffer[1] + buffer[2] +
  buffer[3] + buffer[4] + buffer[5];
  Serial.print("Extension Controller xID = 0x");
  Serial.println(xID,HEX);
  if(xID == 0xCB){
    Serial.println("WMP Connected But Not Avtivared!");
  }
  if(xID == 0xCE){
    Serial.println("WMP Connected And Avtivared!");
  }
  if(xID == 0x00){
    Serial.println("WMP Not Connected!");
  }
  delay(100);

  IICWrite(0x52,0x8);
  Serial.println("Set Reading Address 0x08 Ok!");
  delay(100);

  IICWrite(0x52,0x00);
}

double Map(double value,double Input_Min,double Input_Max,double Output_Min,double Output_Max)
{
  double rValue = (value - Input_Min) * (Output_Max - Output_Min) / (Input_Max - Input_Min) + Output_Min;

  double rMin,rMax;
  if(Output_Min < Output_Max){
    rMin = Output_Min;
    rMax = Output_Max;
  }
  else{
    rMin = Output_Max;
    rMax = Output_Min;
  }
  if(rValue < rMin){
    return rMin;
  }
  if(rValue > rMax){
    return rMax;
  }

  return rValue;
}

void ComputingNunchuk(uint8_t *buf)
{
  int accel_x_axis = (buf[2] << 2) + ((buf[5] >> 3) & 2);
  int accel_y_axis = (buf[3] << 2) + ((buf[5] >> 4) & 2);
  int accel_z_axis = (buf[4] << 2) + ((buf[5] >> 5) & 6);

  int mapX = Map(accel_x_axis,300.0f,700.0f,-90.0f,90.0f);
  int mapY = Map(accel_y_axis,300.0f,700.0f,-90.0f,90.0f);
  int mapZ = Map(accel_z_axis,360.0f,760.0f,-90.0f,90.0f);

  AccAngleX = atan2(mapX,mapZ) / 3.14159 * 180.0f;
  AccAngleY = atan2(mapY,mapZ) / 3.14159 * 180.0f;
  /*
  Serial.print(accel_x_axis);
  Serial.print(" ");
  Serial.print(accel_y_axis);
  Serial.print(" ");
  Serial.println(accel_z_axis);
  */
}

void ComputingWMP(uint8_t *buf)
{
  int yaw = (((buf[5] & 0xFC) << 6) + buf[0]);
  int pitch = (((buf[4] & 0xFC) << 6) + buf[1]);
  int roll = (((buf[3] & 0xFC) << 6) + buf[2]);

  double GyroDiffYaw = (yaw - GyroLastYaw) / 14.375f;
  double GyroDiffPitch = (pitch - GyroLastPitch) / 14.375f;
  double GyroDiffRoll = (roll - GyroLastRoll) / 14.375f;

  GyroAngleYaw  = GyroDiffYaw *
  (double)(micros() - last_gyro_computing_time) / 1000000.0f;
  GyroAnglePitch = GyroDiffPitch *
  (double)(micros() - last_gyro_computing_time) / 1000000.0f;
  GyroAngleRoll = GyroDiffRoll *
  (double)(micros() - last_gyro_computing_time) / 1000000.0f;

  last_gyro_computing_time = micros();

  GyroLastYaw = yaw;
  GyroLastPitch = pitch;
  GyroLastRoll = roll;
  /*
  Serial.print(yaw);
  Serial.print(" ");
  Serial.print(pitch);
  Serial.print(" ");
  Serial.println(roll);
  */
  /*
  Serial.print(GyroAngleYaw);
  Serial.print(" ");
  Serial.print(GyroAnglePitch);
  Serial.print(" ");
  Serial.println(GyroAngleRoll);
  */
}

void ComplementaryFilter()
{
  AngleX = (HighPass * (AngleX + GyroAngleYaw)) + (LowPass * AccAngleX);
  AngleY = (HighPass * (AngleY + GyroAnglePitch)) + (LowPass * AccAngleY);

  Serial.print(AngleX);
  Serial.print("  ");
  Serial.println(AngleY);

}

void IICWrite(uint8_t address,uint8_t register_address)
{
  Wire.beginTransmission(address);
  Wire.write(register_address);
  Wire.endTransmission();
}

void IICWrite(uint8_t address,uint8_t register_address,uint8_t data)
{
  Wire.beginTransmission(address);
  Wire.write(register_address);
  Wire.write(data);
  Wire.endTransmission();
}

void IICRead(uint8_t *buf,uint8_t address,uint8_t length)
{
  Wire.requestFrom(address,length);
  for(int i = 0;Wire.available();++i){
    buf[i] = Wire.read();
  }
}

2012年8月29日 星期三

Heartbeat Sensor

    前幾天看到這篇Arduino 心拍センサシールド感覺很有趣所以也來實做看看,原理是利用心臟收縮與擴張時血液流通使末梢組織的透明度發生變化,這時候將手指放上CNY70會因為這樣讓反射回來的紅外線有差異產生PWM,因此就可以靠產生的PWM測出心跳以及次數。

Part List :


  1. CNY70(或者其他種類的上照式光遮斷器)
  2. 紅色LED
  3. 10k Ohm可變電阻
  4. 220 Ohm電阻 X 2
  5. 4.7k Ohm電阻
  6. 1k Ohm電阻
  7. 100k Ohm電阻
  8. 1M Ohm電阻
  9. 0.1uF陶瓷、積層、無極性電解電容 X 2
  10. 1uF陶瓷、積層、無極性電解電容
  11. LM358雙運算放大器(我買到的上面寫HA17358,但是我發現不是所有運放都可以,例如NE5532、JRC4556AD就不行,所以這邊就用原本的358運放,可能還是要請有電子背景的朋友研究一下datasheet解答差別)

電路說明:

    左邊那個四腳的IC就是CNY70,將CNY70的紅外線發射(負極)與接收(射極)晶體接地,紅外線發射正極接上220Ohm的電阻再接上5V電源,接著將10k的可變電阻左端接上4.7k的電阻再接上5V電源分壓,然後右端接地,接著中間輸出腳(圖中黃色接線的部份)分別接上CNY70的接收晶體(集極)以及1uF的電容,並且將電容另一隻腳接到LM358的第三隻腳(正向輸入端),而這隻腳還要再接一個 100k的電阻接地,接著LM358的第二隻腳(反向輸入端)要接一個1k電阻接地,並且還要連接一個1M電阻與0.1uF電容到LM358的第一隻腳(輸出端)做回授,然後LM358的第四隻腳要接地,LM358的第八隻腳要接5V電源
並且要再接一個0.1uF電容接地消除雜訊,最後將LM358的第一隻腳(輸出端)分別接到一個220k的電阻與LED上(圖中綠色接線的部份)以及接到Arduino的Analog Pin 0(圖中紫色接線部份)。



程式:

    每秒取樣50次左右,並且確定積分值是否有達到形成PWM的程度來判斷是否有PWM,並且以每個PWM的波谷區隔每個波來更新心跳次數。


int sensor_pin = 0;
int n = 0;
int n_1 = 0;
int diff = 0;
int count = 0;
int elapse_up = 0;
int elapse_down = 0;
int integral_plus = 0;
int integral_minus = 0;

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  n = analogRead(sensor_pin); //取得現在得到的值
  diff = (n - n_1);           //將現在的值減去上次得到的值(N -(N-1))
  n_1 = n;                    //更新上一次的值

  //如果差值趨近零,則表示到達波谷又要進到新的一個PWM中。
  //並且在確定上個PWM的爬升(integral_plus)與下降(integral_minus)
  //是否有到達積分值來確定是否有PWM。
  //兩者成立則更新所有狀態。
  if(diff < 10 && diff > -10 &&
  integral_plus > 160 && integral_minus < -200){
    if(elapse_up > 50 && elapse_up < 400 &&
    elapse_down > 96 && elapse_down < 800){
      ++count;
      Serial.print(count);
      Serial.println(" Heartbeat");
    }
    elapse_up = 0;
    elapse_down = 0;
    integral_plus = 0;
    integral_minus = 0;
  }
  else if(diff > 20 && diff < 280){
    //PWM上升
    integral_plus += diff;
    elapse_up += 20;
  }
  else if(diff < -20 && diff > -200){
    //PWM下降
    integral_minus += diff;
    elapse_down += 20;
  }

  //每20ms取樣,即一秒取樣50次。
  delay(20);
}


DEMO:

    使用的時候要先調整10k可變電阻,將手指放上CNY70完全蓋住但是不能大力壓緊,接著觀察到LED會隨著心跳變動就可以了,如果不行則繼續調整可變電阻的分壓。


=============2012/09/17===============
最近有空把它Layout成板子,有+ -號那邊就是電源。