Search

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

2020年4月8日 星期三

MSP430G2 Geiger Counter and True Random Number Generator(TRNG)

清個庫存把MSP430G2433消耗掉做個蓋格計數器跟物理亂數產生器,如果有核種可以找露營燈紗(瓦斯燈芯, 汽化燈燈芯)含釷的可以加快亂數產生,製作時如果電錶調整到1000VDC檔位內阻在10M(查文件)則調整VR1測量蓋格管的電壓為250v實際電壓就大概是400v了,我測試J321βγ和M4011沒問題,但這兩個管子拿強光手電筒照會有Event做輻射測量需要防光,相關資訊都在下面連結。




2019年11月9日 星期六

Atrial Fibrillation Detection Blood Pressure Monitor Oscillometric Method


This project is electronic blood pressure monitor research platform with Oscillometric method, include SBP and DBP estimate algorithm, Atrial Fibrillation detection algorithm, calibration curve, GUI tool for fine-tune BP algorithm...etc, ofcourse, this is research use only.














2018年12月20日 星期四

Pulse Oximeter with MAX3010X

在同一波長下針對Hb和HbO2的吸收(extinction coefficient or absorption coefficient)有差異,而如果要求SPo2量測兩個不同波長的吸收就可以求解一個未知數。

因為光對於動脈微血管血液的變化(隨著心率)會有交流訊號,而其他的組織(吸收散射反射)則是直流成分,因此將兩波長反射量變化正規化後的結果作為R-Ration(SPo2為非線性),利用經驗公式A, B係數可得一SPo2曲線,例如660nm/940nm = 1, SPo2 = 85%,例如A - B = 110 - 25 斜率。
 具體實現只要動態的將直流成分除去,並且將兩波長光直流水平控制在一範圍(SPo2都有類似PGA的功能),得到的兩波長交流訊號RMS比值即可推算SPo2。





以下Github包含STM32F4 Driver for MAX30100(移植Reference 1.範例),和MAC OS X上的C++ wxWidgets GUI(左下角顯示SPo2)可透過VCP與STM32F4通訊,當然除了此方法使用更多的波長可以使SPo2求解聯立方程更加準確,並且實務上會做更高精度的SPo2曲線的建立以及motion artifact除去等。



Download : 


Demo : 

Reference : 
1. https://morf.lv/implementing-pulse-oximeter-using-max30100 
2. http://www.ti.com/lit/an/slaa274b/slaa274b.pdf 
3. http://www.ti.com/lit/ug/tidu542/tidu542.pdf 
4. http://sam-koblenski.blogspot.com/2015/11/everyday-dsp-for-programmers-dc-and.html



2016年9月24日 星期六

Firework Controller V2

中的裝置新版本,因為Android -Bluetooth太短所以改用2.4G RF控制,加進去火焰偵測和人體紅外線,code for Energia MSP430G2XX3由於PCB在中秋前一天才到所以code離預設的完成度不高不過也不打算再改了,而且今年也沒有機會放到煙火XD相關資料在以下

2015年12月18日 星期五

Electronic Stethoscope DIY

    先到這下載膜面模型:https://github.com/GliaX/Stethoscope,印出來head.stl後先將尾管套上熱縮套然後加熱收縮,另一端套上電容式麥克風加熱,接著再找一層塑膠薄膜套上膜面束緊(我是用實驗用橡膠手套)。





    實驗後敏感度還不錯,不過我下圖Bandpass只有到1~105Hz所以示波器訊號看起來頻率不高但是可以看出心跳,我試著換成人體心肺音1~3100Hz也可以收到第一二心肺音。







MQ Gas Sensors Breakout Board

    MQ系列轉板,搭配一顆SMD 104P電容和10k VR,下面第一張圖是MQ-2,第二張圖是MQ-3,把MQ系列接反或者電壓接反都能Work只影響偵測到目標氣體時電壓曲線上升或下降,板子Pin中間那根就是AnalogOUT。


示波器為酒精棉片靠近時的反應




Download:


2015年6月4日 星期四

HDC1000 Breakout

    HDC1000轉接版實驗用,STM32 PB15連接HDC1000 DRDYn開外部中斷等接收資料即可,轉換公式看Datasheet,下面是STM32 Example,話說STM32的Hardware IIC真的很鳥...


壹圓下面那個

example:

#include "stm32f10x.h"
#include "stdio.h"

#define HDC1000_ADDR  0x40 << 1

#define HDC1000_BOTH_TEMP_HUMI 0x10
#define HDC1000_TEMP_HUMI_14BIT 0x00
#define HDC1000_HEAT_ON 0x20

#define HDC1000_TEMP  0X00
#define HDC1000_HUMI  0X01
#define HDC1000_CONFIG 0x02

#define true 1
#define false 0

uint8_t HDC1000_SYNC = true;

uint8_t HDC1000_FLAG = HDC1000_TEMP;

double temperature = 0,humidity = 0;

volatile uint32_t delay = 0;

void Delay(volatile uint32_t n)
{
   delay = n;
   while(delay != 0);
}

void Init_I2C() {
   I2C_InitTypeDef I2C_InitStructure;
   GPIO_InitTypeDef GPIO_InitStructure;

   I2C_Cmd(I2C1,ENABLE);

   RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
   GPIO_Init(GPIOB, &GPIO_InitStructure);  

   I2C_InitStructure.I2C_Mode = I2C_Mode_SMBusHost;
   I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
   I2C_InitStructure.I2C_OwnAddress1 = 0x00;
   I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
   I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
   I2C_InitStructure.I2C_ClockSpeed = 100000 ;
   I2C_Init(I2C1, &I2C_InitStructure);
}

void I2C_start(I2C_TypeDef* I2Cx, uint8_t address, uint8_t direction){
    while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));

    I2C_GenerateSTART(I2Cx, ENABLE);

    while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT));

    I2C_Send7bitAddress(I2Cx, address, direction);

    if(direction == I2C_Direction_Transmitter){
        while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
    } else if(direction == I2C_Direction_Receiver){
        while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
    }
}

void I2C_write(I2C_TypeDef* I2Cx, uint8_t data)
{
    I2C_SendData(I2Cx, data);
    while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
}

uint8_t I2C_read_ack(I2C_TypeDef* I2Cx){
    uint8_t data;
    I2C_AcknowledgeConfig(I2Cx, ENABLE);
    while( !I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED) );
    data = I2C_ReceiveData(I2Cx);
    return data;
}

uint8_t I2C_read_nack(I2C_TypeDef* I2Cx){
    uint8_t data;
    I2C_AcknowledgeConfig(I2Cx, DISABLE);
    I2C_GenerateSTOP(I2Cx, ENABLE);
    while( !I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED) );
    data = I2C_ReceiveData(I2Cx);
    return data;
}

void I2C_stop(I2C_TypeDef* I2Cx){
    I2C_GenerateSTOP(I2Cx, ENABLE);
}

void Init_BT(void){
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef interrup;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

    GPIO_Init(GPIOA, &GPIO_InitStructure);

    USART_InitStructure.USART_BaudRate = 9600;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

    USART_Init(USART2, &USART_InitStructure);

    USART_Cmd(USART2, ENABLE);
}

void BT_Send_String(volatile char *string)
{
   while(*string){
      USART_SendData(USART2,*string);
      ++string;
      while(USART_GetFlagStatus(USART2,USART_FLAG_TC) == RESET){}
   }
}

void HDC1000Start()
{
   uint8_t config = HDC1000_BOTH_TEMP_HUMI | HDC1000_TEMP_HUMI_14BIT | HDC1000_HEAT_ON;

    I2C_start(I2C1, HDC1000_ADDR, I2C_Direction_Transmitter);
    I2C_write(I2C1, HDC1000_CONFIG);
    I2C_write(I2C1, config);
    I2C_write(I2C1, 0x00);
    I2C_stop(I2C1);
    Delay(20);
}

void HDC1000DataRequest(uint8_t reg)
{
   if((HDC1000_FLAG == reg) && HDC1000_SYNC){
      HDC1000_SYNC = false;
      I2C_start(I2C1, HDC1000_ADDR, I2C_Direction_Transmitter);
      I2C_write(I2C1, reg);
      I2C_stop(I2C1);
   }
   else if((HDC1000_FLAG == reg) && HDC1000_SYNC){
      HDC1000_SYNC = false;
      I2C_start(I2C1, HDC1000_ADDR, I2C_Direction_Transmitter);
      I2C_write(I2C1, reg);
      I2C_stop(I2C1);
   }
}

double HDC1000GetTemp()
{
    double temp = 0;

    I2C_start(I2C1, HDC1000_ADDR, I2C_Direction_Receiver);

    temp = I2C_read_ack(I2C1) << 8;
    temp += I2C_read_nack(I2C1);

    return temp / 65536.0 * 165.0 - 40.0;

}

double HDC1000GetHumi()
{
    double humi = 0;

    I2C_start(I2C1, HDC1000_ADDR, I2C_Direction_Receiver);

    humi = I2C_read_ack(I2C1) << 8;
    humi += I2C_read_nack(I2C1);

    return humi / 65536.0 * 100.0;

}

void HDC1000EXTI15()
{
   GPIO_InitTypeDef GPIO_InitStructure;
   NVIC_InitTypeDef NVIC_InitStructure;
   EXTI_InitTypeDef EXTI_InitStructure;

   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);

   GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_15;
   GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING;
   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;

   GPIO_Init(GPIOB, &GPIO_InitStructure);

   GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource15);

   EXTI_InitStructure.EXTI_Line = EXTI_Line15;
   EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
   EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
   EXTI_InitStructure.EXTI_LineCmd = ENABLE;
   EXTI_Init(&EXTI_InitStructure);

   NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
   NVIC_Init(&NVIC_InitStructure);
}

int main(){
    Init_I2C();
    Init_BT();

    SystemInit();
    if(SysTick_Config(SystemCoreClock / 1000)){
       while(1);
    }

    HDC1000Start();
    HDC1000EXTI15();

    while(true){
       if(HDC1000_FLAG == HDC1000_TEMP){
 HDC1000DataRequest(HDC1000_TEMP);
       }
       else if(HDC1000_FLAG == HDC1000_HUMI){
 HDC1000DataRequest(HDC1000_HUMI);
       }
    }
}

void SysTick_Handler(void)
{
   if(delay != 0){
      delay--;
   }
}

void EXTI15_10_IRQHandler(void) {
   if(EXTI_GetITStatus(EXTI_Line15) != RESET) {
      if(HDC1000_FLAG == HDC1000_TEMP){
HDC1000_FLAG = HDC1000_HUMI;
temperature = HDC1000GetTemp();
      }
      else if(HDC1000_FLAG == HDC1000_HUMI){
HDC1000_FLAG = HDC1000_TEMP;
humidity = HDC1000GetHumi();
      }

      char str[256];
      sprintf(str, "temperature:%.2f humidity:%.2f%%\n",temperature,humidity);
      BT_Send_String(str);

      HDC1000_SYNC = true;
   }
   EXTI_ClearITPendingBit(EXTI_Line15);
}


 Download:

2012年12月7日 星期五

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成板子,有+ -號那邊就是電源。






Simple Kalman Filter

    本來將這篇放到 Arduino - 使用 Wii Nunchuck後面算是針對單個角度值做一維的卡爾曼濾波的簡單應用,不過感覺放在後面有點亂所以還是另外開一篇,下次在寫多維卡爾曼濾波融合加速度計與陀螺儀的應用

   Kalman FIilter的應用最常見的地方在慣性導航系統中,比如四軸飛行器或者火箭以及SEGWAY平衡的PID值預測,OpenCV中也有個Kalman Filter,用來做物體追蹤預測位置或者速度,Kalman FIilter可以從前一個預測結果計算出現在的最佳結果以及下個結果,而Kalman FIilter光是線性與非線性版本就有很多種,維數的少到多與難度從簡單到複雜成正比,簡單的Kalman FIilter可以用來校正加速度計的角度之類的值,或者將陀螺儀的角速度積分後的值與加速度計的角度做Kalman FIilter後取最佳的角度,而簡化後的Simple Kalman Filter只有幾個計算跑在Arduino上也很容易。


Simple Kalman Filter:

    q是預測誤差、r是感測器誤差、k是卡爾曼增益、x用來紀錄狀態值、p直接取q與r的平方和開根號,接著隨著時間更新時,現在的結果會是上一個結果加上上一個結果和現在量測結果的差再乘以增益的值(x += k * (value - x);),而下一個結果預測的偏差值就可以將p乘上1 - k後的值在加上q預測誤差值得到下一個要修正的結果,如此把Covariance遞迴下去就可以一直計算出現在的最佳結果,而且只需要從上一個保存的結果推算。


#include <math.h>

class KalmanFilter
{
   public:

      KalmanFilter(double q,double r);
      double Update(double);
      double GetK(){return k;}

   private:

      double k; //kalman gain
      double p; //estimation error cvariance
      double q; //process noise cvariance
      double r; //measurement noise covariance
      double x; //value
};

KalmanFilter::KalmanFilter(double q,double r):q(q),r(r),x(0.0)
{
  p = sqrt(q * q + r * r);
}

double KalmanFilter::Update(double value)
{
   p += q;
   k = p / (p + r);
   x += k * (value - x);
   p *= (1 - k);

   return x;
}

Used:
 
    假設預測誤差為2,感測器測量誤差為1.5(這兩個值看情況調整)。

KalmanFilter kalman(2.0,1.5);
    然後將每次測量的值用Update更新取得修正結果即可,會發現得到修正的結果會比沒修正的結果變動幅度較為平滑。



void loop()
{
  double Angle = Accelerometer();
  double k_value = kalman.Update(Angle);
}


直接用兩個OpenGL View Port其中各有一個方塊,並將角度值直接帶入World Matrix直接套用矩陣旋轉公式去作矩陣轉換,而兩方塊放在同個畫面可以方便看非使用卡爾曼濾波(螢幕左)以及使用卡爾曼濾波(螢幕右)兩個方塊振動幅度的差別。


2011年6月30日 星期四

Pyroelectric Detector

    本來這篇是要跟Temperature Senso寫在同一篇才對,但是一直沒有找到之前上課存下來的電路圖,所以就現在才寫而且獨立開一篇了XD,人體溫度感測器可能常見的還是騎樓下的保全系統或是小七的電動門,當經過感測器時感測器瞬間電壓的變化就會造成打光系統啟動或是電動門打開,不過很多監視攝影機其實不用打光,因為本身就是CCD元件在加上紅外線,即使在夜晚也能拍到物體。

2011年6月8日 星期三

Switch

開關的種類相當多種,開關的基本原理就是將斷路點相接啟動反之則關閉,要注意的是開關的選用除了構造是否合適外還跟承受力大小有關,換句話說要讓斷路點接合的力量大小取決於金屬材料本身的彈性係數,因此這類開關反應速度沒有電晶體來的快而且會彈性疲乏,但是在大多工業應用還是很廣泛而且又便宜。

1.磁簧開關:

市面上賣的都是將一個開關電路封在一個玻璃阻隔物中,然後可以用帶有磁性的物件來吸附接點的金屬使之導通,也就是取決於磁力,因此一般的「繼電器(Relay)」就是屬於這樣的應用。



2.振動開關

當這種開關側面受力時會使接點接合然後導通,一般郵局銀行的防盜玻璃上安裝的就是這種類的開關,當歹徒敲擊玻璃時表面振動的力量就能導通警報電路。



3.極限開關

這種開關在CNC之類的加工母機一定都會看到,當操作人員或是程式撰寫不當導致CNC加工母機發生所謂的「過行程」時,剛好使極限開關啟動發出警報聲,並且同時間切斷整個加工機的電源來保護機器,而這類開關也都相當靈敏。



Condenser Microphone

電容式麥克風的原理是利用聲波改變電容兩極板的距離,造成電壓值的不同來輸出,看下圖可知聲波打到前板造成與後板的距離改變,使電容的電壓改變之後輸出,通常輸出的電壓還會在做放大然後才輸入到喇叭。



電容式麥克風的電器符號,上面接點是VCC下面是GND中間就是電壓值輸出了。



可以看下面一個電容麥克風與喇叭的電路圖,將電容麥克風Vo11的電壓值輸出到喇叭的電路Vin1在做訊號放大最後傳至喇叭放大音訊!




2011年6月7日 星期二

Phototransistor

光電晶體的應用很廣泛,從常見的循跡車到滑鼠還有快門計數器等等都可以看到,光電晶體的種類很多,常見的單一個還有光遮斷器以及上照式光遮斷器(CNY70)都是光電晶體的一種。

1.光電晶體


2.光遮斷器

3.上照式光遮斷器


Temperature Sensor

溫度感測器一般用在空調的溫度顯示,根據需求不同選用適當的量測範圍,以AD590來說大約是在 -55℃ ~ 150℃其精度是1uA/K,要注意的是要將絕對溫度換成要將絕對溫度減去273.15,溫度感測器的電器符號是以兩個圓圈串上一條直線並在兩端畫上正負號作為代表。

2011年6月1日 星期三

Arduino - 使用 Wii Nunchuck

前言:


Wii Nunchuck是使用Inter-Integrated Circuit也就是俗稱的I2C作為protocol,關於I2C的初始化等等工作可以參考Example -> Wire裡面的範例,Nunchuck有四個重要的腳位:VCC、GND、SDA、SCL,這四個腳可以分別接上Arduino Analog 2~5,因為Analog 4、5是Arduino的I2C腳位SDA、SCL,而Analog 2、3則就會設成GND與VCC。