2017年5月13日 星期六

PSoC 5LP - CY8CKIT-059 + SD112-45-11-221 光電二極體(PD)

     光電二極體與常規的半導體二極體基本相似,只是光電二極體可以直接暴露在光源附近或通過透明小窗、光導纖維封裝,來允許光到達這種器件的光敏感區域來檢測光訊號。許多用來設計光電二極體的二極體使用了一個PIN結,而不是一般的PN接面,來增加器件對訊號的響應速度。光電二極體常常被設計為工作在逆向偏壓狀態。
   一個光電二極體的基礎結構通常是一個PN接面或者PIN結。當一個具有充足能量的光子衝擊到二極體上,它將激發一個電子,從而產生自由電子(同時有一個帶正電的電洞)。這樣的機制也被稱作是內光電效應。如果光子的吸收發生在結的空乏層,則該區域的內電場將會消除其間的屏障,使得電洞能夠向著陽極的方向運動,電子向著陰極的方向運動,於是光電流就產生了。實際的光電流是暗電流和光照產生電流的綜合,因此暗電流必須被最小化來提高器件對光的靈敏度。

   SD112-45-11-221 內含放大器的光電二極體單顆報價80USD算是超級貴的基礎元件。

   PSoC 5LP 內建20Bit的 Delta-Sigma ADC, Delta-Sigma ADC 由微分器、積分器構成的ΔΣ調變電路,會因其微分特性而對量化雜訊(Quantization noise)產生一種高通濾波的效果。一般線性PCM中產生的量化雜訊平均分布在各頻率上,基於前述特性,可以將量化雜訊推往高頻,而產生noise shaping功效。將取樣頻率設高,則人耳可聽到的頻段相對低頻,此時將已經被推往高頻的量化雜訊以低通濾波器濾除,則可以得到量化雜訊較少的原訊號。

SD112-45-11-221



SD112-45-11-221  接腳圖

SD112-45-11-221  需要負電源,我使用LMC7660 把+5V轉換成-5V


PSoC 5LP  規劃內部線路


PSoC 5LP  ADC_Delsig_1 設定


PSoC 5LP 接腳設定,A_in 接 SD112-45-11-221 ,A_in2接地線。


Main 程式碼


#include <project.h>
#include <TFT.h>
#include <device.h>
#include <stdio.h>                    

extern uint16_t MAX_X, MAX_Y ;        

#if defined (__GNUC__)

    asm (".global _printf_float");
#endif

int main()
{
 
    int16 output;
char8 str[10];
 
    SPIM_Start();
 
    ADC_DelSig_1_Start();

    ADC_DelSig_1_StartConvert();
 
 
    TFT_Init(1);
    TFT_FillScreen(0, MAX_X, 0, MAX_Y, BLACK);
    TFT_DrawString("ADC_Output",  0, 0, 2, GREEN);
 
for(;;)
    {
        if(ADC_DelSig_1_IsEndConversion(ADC_DelSig_1_RETURN_STATUS))
        {
            output = ADC_DelSig_1_GetResult16();
output = ADC_DelSig_1_CountsTo_mVolts(output) ;
sprintf(str, "%d mV", output);

TFT_DrawString("         ",1,0,2,RED);
            TFT_DrawString(str,1,32,2,RED);
CyDelay(1000u);
            TFT_FillRectangle(0,  32,   320,    64, BLACK);
        }
 
    }
}
/* [] END OF FILE */

上傳到後PSoC 5LP - CY8CKIT-059就會開始顯示光度值,SD112-45-11-221 的RG高達600Mohm 一般亮度夏一定爆表!!

這是閱讀的亮度下,這已經是超過飽和值。


白天關燈後還是爆表。


關燈再用罩子罩住還會有1.4V輸出。

PSoC 5LP 的 Delta-Sigma ADC 的確可抑制雜訊。

Teensyduino (筆記 4 ) Teensy 3.6 BPW21R 光電二極體(PD)

****軟體使用 Teensyduino  1.36  + Arduino 1.82 ********

    光電二極體與常規的半導體二極體基本相似,只是光電二極體可以直接暴露在光源附近或通過透明小窗、光導纖維封裝,來允許光到達這種器件的光敏感區域來檢測光訊號。許多用來設計光電二極體的二極體使用了一個PIN結,而不是一般的PN接面,來增加器件對訊號的響應速度。光電二極體常常被設計為工作在逆向偏壓狀態。
   一個光電二極體的基礎結構通常是一個PN接面或者PIN結。當一個具有充足能量的光子衝擊到二極體上,它將激發一個電子,從而產生自由電子(同時有一個帶正電的電洞)。這樣的機制也被稱作是內光電效應。如果光子的吸收發生在結的空乏層,則該區域的內電場將會消除其間的屏障,使得電洞能夠向著陽極的方向運動,電子向著陰極的方向運動,於是光電流就產生了。實際的光電流是暗電流和光照產生電流的綜合,因此暗電流必須被最小化來提高器件對光的靈敏度。

 這是BPW21R 的照片現在半導體這樣亮晶晶的包裝不多了


     Teensy 3.6 內部有16Bit 的ADC預設滿檔位3.3v的狀況下提供了65536的分辨率,比Arduino的10Bit 的1024高出許多。

電路圖



/****以下是程式碼****/

#include <Adafruit_GFX.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_ILI9341.h>

#include <ADC.h>

int readPin = A0; // ADC0

ADC *adc = new ADC();; // adc object

#define TFT_RST   24       //LCM  接腳
#define TFT_DC    25
#define TFT_CS    26
#define TFT_MOSI  27
#define TFT_MISO  28
#define TFT_CLK   29

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_MOSI, TFT_CLK, TFT_RST, TFT_MISO);

void setup() {

    pinMode(readPin, INPUT);
    
    tft.begin();                                          //LCM 啟始
    tft.fillScreen(ILI9341_BLACK);      //LCM 畫面更新為黑色
    tft.setRotation(1);                             // LCM 旋轉90度

    adc->setAveraging(64); // set number of averages
    adc->setResolution(16); // set bits of resolution

    adc->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_LOW_SPEED); // change the conversion speed
    adc->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_LOW_SPEED); // change the sampling speed
    delay(500);
}

int value = 0;
int pin=0;

void loop() {

    value = adc->adc0->analogRead(readPin); // read a new value, will return ADC_ERROR_VALUE if the comparison is false.
    tft.setCursor(0,0);
    tft.setTextSize(2);
    tft.fillRect(0,0,320,40,0);
    tft.print("Value: ");
    tft.print(value*3.3/adc->getMaxValue(ADC_0), DEC);
    
    delay(1000);

}

上傳到Teensy 3.6後 就會開始顯示光電二極體偵測到的亮度值,因為這設計是要低照度下還能做線性檢出在一般亮度下一定爆表,如果應用在一般亮度須更改放大器的放大倍率!!

這是關燈後的照片感測電路還可輸出1.4V左右


這是開燈後的照片,改測器輸出已經是滿檔的3.2V。


Teensy3.6雖然有16Bit的ADC但是在麵包版上雜訊等問題能達到12Bit的穩定度就很好了。

2017年5月9日 星期二

Raspberry Pi 3 ( 10 ) OpenCV 動態人臉偵測

 Raspberry Pi 3 ( 10 )   OpenCV   動態人臉偵測

     人臉偵測的第一個想到的當然是手機,現在手機、數位相機對人臉的偵測已經是非常普及了,問題是這技術是別人的。OpenCV也提供人臉辨識的資料庫可以讓初學者測試學習。這次就用Raspberry Pi3 + PicameraV2做實驗。

Raspberry Camera


PiCamera 接入Raspberry Pi3


  接下來就先更新一下Rasoberry 。$ 不要輸入!!

$ sudo apt-get update
$ sudo apt-gat upgrade

 再來鳩開啟Rasoberry設定。


將Camera 開啟


先測試一下Canera 是否正常運作。
$ raspistill -o output.jpg


在跟目錄會有一個Test.jpg的檔案。


Test.jpg 就是剛剛照下來的照片。


上一篇我我OpenCV & Python 是安裝在虛擬環境中要再安裝其他的模組也要回虛擬環境。

$ source ~/.profile
$ workon cv
注意提示符號前面會有cv

安裝Picamera 注意要加 array 

$ pip install "picamera[array]"

在這裡有Opencv 有提供建立好的臉部與眼睛 xml 檔案,建議全部下載下來下次可使用。

https://github.com/opencv/opencv/tree/master/data/haarcascades

 把haarcascades的目錄名稱改成face,在這目錄底下建立一個新的文字檔案命名為 face.py。

以下是face.py 的內容,個人慘痛經驗是用文字編輯器比Python 的IDE好用,Python 不能有多的空格!!縮牌要使用 tab 鍵。

#  以下是Python 程式 ***這一行不要拷貝進程式裡******

#-*-coding:cp950-*-

#上一行是讓 Python 能用中文註解

from picamera.array import PiRGBArray
from picamera import PiCamera
import cv2
import time

#設定攝影機
camera = PiCamera()
camera.resolution = (640, 480)
camera.framerate = 30
rawCapture = PiRGBArray(camera, size=(640, 480))

#新視窗命名為Faces
display_window = cv2.namedWindow("Faces")

#載入臉部特徵模組
face_cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")

time.sleep(0.1)

for frame in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True):

    image = frame.array

    #臉部辨識
    gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray, 1.1, 5)
    for (x,y,w,h) in faces:
        cv2.rectangle(image,(x,y),(x+w,y+h),(255,0,0),2)

    #輸出影像
    cv2.imshow("Faces", image)

    key = cv2.waitKey(1)

    rawCapture.truncate(0)

    if key == ord("q"): # Press 'q' to quit
        break

#  以上是Python 程式 ***這一行不要拷貝進程式裡******

    把整個face目錄用WinSCP傳到Raspberry,用WinSCP對於習慣用Windows的我最大的好處是可以直接編輯Raspberry內的文字檔。


在Raspberry 的終端機輸入
$ cd face
$ Python face.py
Raspberry 就會開啟一個名稱為Faces的視窗,當人臉出現時就會框起來。


以上這段程式是實際測試過的。

2017年5月3日 星期三

Arduino MQ-3 MQ-4 MQ-8 氣體感測器

     MQ 系列感測器又稱為半導體式感測器一般都是用二氧化錫(SnO2) 為基材,二氧化錫在清潔空氣中(含80的氮氣20%的氧氣與少許CO2)導電率非常低,氧化物特點就是本身的活性常低,可長時間保持在同一種狀態,MQ系列感測器壽命可超過20年,二氧化錫(SnO2) 在可燃氣體的催化導電率會升高。MQ系列的結構如下 H 是加溫結構,在加溫後活性氣體通過二氧化錫(SnO2) 時就會改變導電率,只要監測導電率就能判別氣體濃度。

   這是Winsensor 出品的 MQ8 感測器的導電率曲線,MQ8是針對氫氣開發出來的感測器,這圖表能看到紅色的空氣不管濃度多高都不會影響MQ8的導電率,氫氣濃度增加時導電率也會跟著升高,其他顏色是所謂的干擾氣體,也就是乙醇、一氧化碳、甲烷等都會干擾到MQ8的導電率。

下圖是MQ8的溫溼度與導電率的曲線,由此表可看出溫度濕度會影響MQ8的導電率。


   這張圖表是指出當氣體濃度增加時感測器輸出的電壓值,在這張圖表可發現當氣體濃度增加到300ppm後曲線開始平緩到1500ppm時幾乎快成直線。


這是反應時間曲線。

   這是時間變化曲線,把MQ8放在300ppm濃度的氫氣下的輸出電壓變化,由此圖再配合輸出電壓曲線可知MQ8的在300ppm後幾乎不可使用。
 MQ 系列雖然在高濃度時超級不準確但是還有以下優點

1.壽命超長
2.使用簡單
3.價格便宜

     也就是有以上這些優點MQ系列感測器大量應用在氣體洩漏、煙霧感測警報器,這些本來自然界就不該存在的氣體一但洩漏在空氣中超過200ppm~300ppm就發出警報很多人都能接受,只是要拿來當定量使用幾乎不太可能,以下的程式還是照著Datasheet 的曲線期望解出MQ系列的濃度曲線,注意這只是理論!!在上圖MQ8的輸出曲線可得知當超過1500ppm後的電壓變化輸入到只有10bit A/D 轉換器的Arduino根本沒意義。


// 載入 LCD  Library
#include <LiquidCrystal.h>

//      LCD 接腳:  rs, enable, d4, d5, d6, d7
LiquidCrystal  lcd(13, 12, 11, 10 , 9, 8);

/********************* 硬體設定  *************************************/

#define         MQ_3_PIN                         (0)     // MQ_3 輸入腳
#define         MQ_8_PIN                         (1)     // MQ_8 輸入腳
#define         MQ_4_PIN                         (2)     // MQ_4 輸入腳
#define         RL_VALUE                         (5)    // RL電阻為 10K歐姆
#define         MQ_3_Ro_CLEAN_AIR_FACTOR         (1)    //MQ_3在乾淨空氣中的電阻/MQ_3_Ro,
#define         MQ_8_Ro_CLEAN_AIR_FACTOR         (1)    //MQ_8在乾淨空氣中的電阻/MQ_8_Ro,
#define         MQ_4_Ro_CLEAN_AIR_FACTOR         (1)    //MQ_4在乾淨空氣中的電阻/MQ_4_Ro,
                                                   

/*********************** 軟體設定  ************************************/

#define         CALIBARAION_SAMPLE_TIMES     (50)    //設定校準時樣品讀取次數
#define         CALIBRATION_SAMPLE_INTERVAL  (100)   //設定較準時每次讀取採樣的時間間隔(ms)
                                                   
#define         READ_SAMPLE_INTERVAL         (50)    //測試時用幾次的檢測值的平均值
#define         READ_SAMPLE_TIMES            (5)         //測試時每次取樣的間隔時間(ms)
                                         
#define         GAS_Alcohol                  (0)
#define         GAS_H2                       (1)
#define         GAS_CH4                      (2)

/*****************************Globals***********************************************/

float           AlcoholCurve[3]   =  {1.7, -0.77,-1};      // 這斜率參考MQ-3的 Datasheet
                                                                                 
float           MQ_3_Ro           =  10;                           //MQ_3_Ro 的預設值

float           H2Curve[3]        =  {2, -0.6,-1};         // 這斜率參考MQ-8的 Datasheet
                                                                         
float           MQ_8_Ro           =  10;                       //MQ_8_Ro 的預設值

float           CH4Curve[3]       =  {2.48, -0.62,-0.4};   // 這斜率參考MQ-4的 Datasheet
                                                         

float           MQ_4_Ro           =  10;                    //MQ_4_Ro 的預設值


void setup()
{
  lcd.begin(20, 4);                                   //設定 LCM 為 20*4
  lcd.print("Calibrating..");                         //LCM 輸出 Calibrating...
  MQ_3_Ro = MQ_3Calibration(MQ_3_PIN);                //開機校正要確保這段時間內為乾淨空氣
  MQ_8_Ro = MQ_8Calibration(MQ_8_PIN);                //開機校正要確保這段時間內為乾淨空氣
  MQ_4_Ro = MQ_4Calibration(MQ_4_PIN);                //開機校正要確保這段時間內為乾淨空氣
}

void loop()
{
lcd.setCursor(0, 0);                                                 // LCM 游標移到位置 0,0
   lcd.print("Alcohol: ");                                           //顯示文字 Alcohol
   lcd.setCursor(8,0);                                               //LCM 游標移到位置 8,0
   lcd.print(MQ_3GetGasPercentage(MQ_3Read(MQ_3_PIN)/MQ_3_Ro,GAS_Alcohol )); //顯示測到的Alcohol值
   lcd.print(" ppm     ");                                           //LCM 顯示 PPm
   lcd.setCursor(0, 1);                                                 // LCM 游標移到位置 0,0
   lcd.print("  H2  :  ");                                           //顯示文字 H2
   lcd.setCursor(8,1);                                             //LCM 游標移到位置 8,0
   lcd.print(MQ_8GetGasPercentage(MQ_8Read(MQ_8_PIN)/MQ_8_Ro,GAS_H2 )); //顯示測到的H2值
   lcd.print(" ppm     ");                                           //LCM 顯示 PPm
   lcd.setCursor(0, 2);                                                 // LCM 游標移到位置 0,0
   lcd.print("  CH4 :  ");                                           //顯示文字 CH4
   lcd.setCursor(8,2);                                               //LCM 游標移到位置 8,0
   lcd.print(MQ_4GetGasPercentage(MQ_4Read(MQ_4_PIN)/MQ_4_Ro,GAS_CH4 )); //顯示測到的CH4值
   lcd.print(" ppm     ");                                           //LCM 顯示 PPm
   delay(1000);
}

/****************** MQ_Sensor 電阻值換算 ****************************************/



 float MQ_3ResistanceCalculation(int raw_adc)
{
  return ( ((float)RL_VALUE*(1023-raw_adc)/raw_adc));
}

float MQ_8ResistanceCalculation(int raw_adc)
{
  return ( ((float)RL_VALUE*(1023-raw_adc)/raw_adc));

}

float MQ_4ResistanceCalculation(int raw_adc)
{
  return ( ((float)RL_VALUE*(1023-raw_adc)/raw_adc));
}

/***************************** MQ_Sensor 校正 ****************************************/

float MQ_3Calibration(int MQ_3_pin)
{
  int i;
  float val=0;

  for (i=0;i<CALIBARAION_SAMPLE_TIMES;i++) {          
    val += MQ_3ResistanceCalculation(analogRead(MQ_3_pin));
    delay(CALIBRATION_SAMPLE_INTERVAL);
  }
  val = val/CALIBARAION_SAMPLE_TIMES;                

  val = val/MQ_3_Ro_CLEAN_AIR_FACTOR;                      

  return val;
}

float MQ_8Calibration(int MQ_8_pin)
{
  int i;
  float val=0;

  for (i=0;i<CALIBARAION_SAMPLE_TIMES;i++) {          
    val += MQ_8ResistanceCalculation(analogRead(MQ_8_pin));
    delay(CALIBRATION_SAMPLE_INTERVAL);
  }
  val = val/CALIBARAION_SAMPLE_TIMES;                

  val = val/MQ_8_Ro_CLEAN_AIR_FACTOR;                      

  return val;
}

float MQ_4Calibration(int MQ_4_pin)
{
  int i;
  float val=0;

  for (i=0;i<CALIBARAION_SAMPLE_TIMES;i++) {          
    val += MQ_4ResistanceCalculation(analogRead(MQ_4_pin));
    delay(CALIBRATION_SAMPLE_INTERVAL);
  }
  val = val/CALIBARAION_SAMPLE_TIMES;                

  val = val/MQ_4_Ro_CLEAN_AIR_FACTOR;                      

  return val;
}


/*****************************  MQ_Sensor 讀取*********************************************/


 float MQ_3Read(int MQ_3_pin)
{
  int i;
  float rs=0;

  for (i=0;i<READ_SAMPLE_TIMES;i++) {
    rs += MQ_3ResistanceCalculation(analogRead(MQ_3_pin));
    delay(READ_SAMPLE_INTERVAL);
  }

  rs = rs/READ_SAMPLE_TIMES;

  return rs;
}

float MQ_8Read(int MQ_8_pin)
{
  int i;
  float rs=0;

  for (i=0;i<READ_SAMPLE_TIMES;i++) {
    rs += MQ_8ResistanceCalculation(analogRead(MQ_8_pin));
    delay(READ_SAMPLE_INTERVAL);
  }

  rs = rs/READ_SAMPLE_TIMES;

  return rs;
}

float MQ_4Read(int MQ_4_pin)
{
  int i;
  float rs=0;

  for (i=0;i<READ_SAMPLE_TIMES;i++) {
    rs += MQ_4ResistanceCalculation(analogRead(MQ_4_pin));
    delay(READ_SAMPLE_INTERVAL);
  }

  rs = rs/READ_SAMPLE_TIMES;

  return rs;
}

/*****************************  MQ_Sensor 換算ppm **********************************/


int MQ_3GetGasPercentage(float rs_MQ_3_Ro_ratio, int gas_id)
{
  if ( gas_id == GAS_Alcohol) {
     return MQ_3GetPercentage(rs_MQ_3_Ro_ratio,AlcoholCurve);
  }
  return 0;
}

int MQ_8GetGasPercentage(float rs_MQ_8_Ro_ratio, int gas_id)
{
  if ( gas_id == GAS_H2) {
     return MQ_8GetPercentage(rs_MQ_8_Ro_ratio,H2Curve);
  }
  return 0;
}


int MQ_4GetGasPercentage(float rs_MQ_4_Ro_ratio, int gas_id)
{
  if ( gas_id == GAS_CH4) {
     return MQ_4GetPercentage(rs_MQ_4_Ro_ratio,CH4Curve);
  }
  return 0;
}

/*****************************   計算RS MQ_Ro 百分比  **********************************/


int  MQ_3GetPercentage(float rs_MQ_3_Ro_ratio, float *pcurve)
{
  return (pow(10,( ((log(rs_MQ_3_Ro_ratio)-pcurve[1])/pcurve[2]) + pcurve[0])));
}

int  MQ_8GetPercentage(float rs_MQ_8_Ro_ratio, float *pcurve)
{
  return (pow(10,( ((log(rs_MQ_8_Ro_ratio)-pcurve[1])/pcurve[2]) + pcurve[0])));
}


int  MQ_4GetPercentage(float rs_MQ_4_Ro_ratio, float *pcurve)
{
  return (pow(10,( ((log(rs_MQ_4_Ro_ratio)-pcurve[1])/pcurve[2]) + pcurve[0])));
}


上傳後就會顯示個感測器的值,注意這是沒校正過的只能當參考 !!


     當然還有其他氣體感測器可選擇,比如有比較好的高濃度曲線的平面半導體製程MP系列,催化燃燒的MC系列,幾乎可線性輸出的電化學系列,還有使元紅外線的NDIR技術。會常用來半定量使用的是電化學與NDIR技術,只是電化學感測器(NT$ 1500~4000 )壽命只有兩年!!不管有沒有使用出廠後就只有兩年壽命,NDIR技術雖然貴一點(NT$ 3000~5000 ) 可用5年,MQ售價只要 NT$ 50~3002。

Raspberry CM3 (一) 安裝 Raspbian Jessie Lite

        CM3採用最高時脈為1.2GHz的BCM2837處理器與1GB記憶體,CPU效能為CM1的10倍,記憶體容量則是兩倍,可執行基於Windows 10的IoT Core,適用於採用Raspberry Pi 3的IoT專案。為了迎合客戶的需要,CM3提供了CM3與CM3 Lite兩種版本,前者內建4GB eMMC的快閃儲存空間,後者則讓使用者根據儲存需求自行安裝SD卡。



    Raspberry CM3 的研發模組,比前一代多了SD卡曹方便沒eMMC的CM3 Lite 使用,使用CM3版本時 SD 不能使用。



Raspberry CM3 沒SD卡須要到這裡下載導引程式。

https://www.raspberrypi.org/documentation/hardware/computemodule/cm-emmc-flashing.md



下載後就安裝



    安裝好後就可插入電源與電腦USB,白色線是 IO BOARD 的電源紅色是連入電腦USB,要注意 IO BOARD 上J4跳線要在EN位置!!


正常狀況電腦會辨識出BCM2710 Boot。


如果電腦辨識出數不明裝置,個人的實驗結果是換掉CM3與電腦連接的USB線就可。


再來就執行剛剛安裝的RPi Boot。


執行RPi Boot 後會出現一個磁碟機,不用急著格式化就按取消就好。


下載 Raspbian Jessie Lite ,CM3 容量只有4GB  完整 Raspbian Jessie 會超過4GB 只能安裝Raspbian Jessie Lite。



    下載好後就開啟 Win32 Disk Imager 將 Raspbian Jessie Lite 寫入CM3 ,注意磁碟機位置如果選錯了磁碟機內容就消失了!!





    寫入完成後就可以拆除IO BOARD 與電腦的連線,把 IO BOARD 的 J4跳線移到DIS位置,IO BOARD 插上HDMI與 K/B連接線,就會看到CM3開機畫面。



使用者輸入 : pi
密碼 : raspberry


這樣就完成了。