이 프로젝트 정보
이번에는 철과 비철 물질을 구별할 수 있는 민감한 금속 탐지기를 만드는 방법을 보여 드리겠습니다. 감도는 비교적 간단한 기기라는 점에서 만족스럽습니다.
이 프로젝트는 PCBgogo에서 후원합니다:
https://www.pcbgogo.com/promo/from_MirkoPavleskiMK
이것은 2013년 Arduino CC 포럼에서 발표된 David Crocker의 프로젝트의 연속입니다. 이 금속 탐지기가 누군가에 의해 만들어졌고 잘 작동한다는 증거(사진 또는 비디오)를 찾지 못했기 때문에 코드를 테스트하기로 결정했습니다.
먼저 장치의 기능을 확인하기 위해 GitHub에 제공된 코드로 기본 버전을 만든 다음 가청 신호가 있고 16 on 2 LCD에 유형에 대한 시각적 정보를 표시하도록 코드를 업그레이드했습니다. 감지된 물체(철 또는 비철 재료) 및 감지된 물체의 근접성에 대한 LCD 막대 그래프.
이 장치는 구축이 매우 간단하고 몇 가지 구성 요소로 구성되어 있습니다.
- 아두이노 나노 마이크로컨트롤러
- 연산 증폭기(제 경우에는 LT1677이지만 TL081 또는 741을 사용할 수 있음)
- 적은 수의 저항 및 커패시터
- 소형 트랜지스터 및 스피커
- LCD 디스플레이
- 3개의 스위치
- 전위차계
- 배터리
- 검색 코일
이것은 VLF(초저주파) 유도 평형 감지기 기술이며 두 개의 동일한 코일(송신기 코일과 수신기 코일)을 포함합니다. 모든 유도 균형 감지기와 마찬가지로 코일 균형은 매우 중요합니다. 전위차계는 신호의 작은 90도 역위상 성분을 0으로 만드는 데 사용됩니다. (동위상 성분은 일반적인 IB 검출기 스타일에서 코일의 상대적 배치를 조정하여 무효화됩니다). 각 코일은 11cm 몸체에 D자 모양의 0.5mm^2 에나멜 동선을 64회 감고 테이프로 감고 주석도금 동선으로 묶은 알루미늄 호일로 감았습니다. 화면이 짧은 회전처럼 작동하지 않음), 플라스틱 판에 타이로 감았습니다.
먼저 많은 온라인 계산기 중 하나를 사용하여 1차 코일 커패시터 회로의 병렬 공진 주파수를 결정해야 합니다. 오실로스코프로 측정해봤는데 위의 치수대로 하시면 정확히 7.64kHz가 되므로 코드에 주어진 값을 직접 입력하시면 됩니다. 공진 주파수의 다른 값의 경우 대기열의 코드를 적절하게 변경해야 합니다.
#define TIMER1_TOP (249) // 주파수 미세 조정
비디오에서 볼 수 있듯이 결과는 놀라울 정도로 좋습니다. 금속이 없으면 장치가 완벽하게 안정적입니다. 범위가 비교적 넓고 예를 들어 직경 15cm의 금속 덮개가 30cm 이상의 거리에서 감지됩니다. 더 큰 금속 물체는 40-50cm 이상의 거리에서 감지됩니다. 우리는 공중에서 15cm 거리에서 작은 동전을 감지할 수 있습니다. 직렬(7.4볼트)로 연결된 전원 공급 장치에 두 개의 리튬 배터리를 사용하고 이 전압은 Arduino의 Vin 입력에 연결됩니다. 소비는 20mA를 초과하지 않으므로 배터리가 매우 오래 지속됩니다. 비디오는 전체 장치의 구성을 자세히 설명합니다.
이는 예비 결과일 뿐입니다. Tx 코일을 구동하기 위한 파워 MOSFET 트랜지스터를 삽입하여 감도를 크게 향상시킬 수 있는 가능성이 있지만 다음 동영상 중 하나에서 테스트하여 소개하겠습니다.
섹션> <섹션 클래스="섹션 컨테이너 섹션 축소 가능" id="코드"> 코드
<울> Arduino 코드
LcdBarGraph 라이브러리
Arduino 코드Arduino
// 유도 평형 금속 검출기// CPU를 16MHz로, ADC 클럭을 1MHz로 실행합니다. ADC 분해능은 이 속도에서 8비트로 감소합니다. // 타이머 1은 62.5kHz 구형파를 생성하기 위해 시스템 클록을 약 256으로 나누는 데 사용됩니다. // 이것은 타이머 0을 구동하고 ADC 변환을 트리거하는 데 사용됩니다. // 타이머 0은 타이머 1의 출력을 8로 나누는 데 사용되어 송신 코일을 구동하기 위한 7.8125kHz 신호를 제공합니다. // 이것은 우리에게 16 ADC를 제공합니다 각 ADC 변환에 대한 클록 주기(실제로는 13.5주기 소요)이며 코일 구동 전압 주기당 8개의 샘플을 취합니다. // ADC는 45도 간격으로 4개의 위상 감지 감지기를 구현합니다. 2 대신 4를 사용하면 // 코일 주파수의 3차 고조파를 취소할 수 있습니다.// 타이머 2는 이어폰이나 헤드셋의 톤을 생성하는 데 사용됩니다.// 타이머 1에 대한 다른 분할 비율은 다음에서 가능합니다. 235 위쪽으로.// Wiring:// 디지털 핀 4(별칭 T0)를 디지털 핀 9에 연결// 디지털 핀 5를 저항을 통해 1차 코일 및 튜닝 커패시터에 연결// 수신 증폭기의 출력을 아날로그 핀 0에 연결합니다. 수신의 출력 증폭기는 아날로그 기준의 약 절반으로 바이어스되어야 합니다.// USB 전원을 사용할 때 3.3V 핀으로 아날로그 기준을 변경하십시오. 좋은 감도를 얻기 위해 +5V 레일에 너무 많은 노이즈가 있기 때문입니다.#include #include #define max_ampAverage 200LiquidCrystal lcd(6, 7, 10, 11, 12, 13);LcdBarGraph lbg(&lcd, 16, 0, 1); #define TIMER1_TOP (259) // 주파수를 미세 조정하여 코일을 튜닝하도록 조정할 수 있습니다(위 참조)#define USE_3V3_AREF(1) // USB 전원으로 Arduino에서 실행하는 경우 1로 설정하고 임베디드의 경우 0으로 설정 사용 가능한 3.3V 공급 장치가 없는 atmega28p// 디지털 핀 정의// 디지털 핀 0은 사용되지 않지만 디버깅을 위해 직렬 포트를 사용하는 경우 직렬 inputconst int debugTxPin =1; // 디버깅을 위해 예약된 전송 핀 const int encoderButtonPin =2; // 인코더 버튼, 절전 모드에서 깨우기 위한 IN0도 const int earpiecePin =3; // 이어피스, 톤 생성을 위한 OCR2B라고도 함const int T0InputPin =4;const int coilDrivePin =5;const int LcdRsPin =6;const int LcdEnPin =7;const int LcdPowerPin =8; // LCD 전원 및 백라이트 enableconst int T0OutputPin =9;const int lcdD4Pin =10;const int lcdD5Pin =11; // 핀 11-13은 ICSP에도 사용됨 const int LcdD6Pin =12;const int LcdD7Pin =13;// 아날로그 핀 정의const int receiverInputPin =0;const int encoderAPin =A1;const int encoderBpin =A2;// 아날로그 핀 3-5 not used// ISRint16_t bins[4]에서만 사용되는 변수; // ADC 판독값을 누적하는 데 사용되는 빈, 4개의 phasesuint16_t numSamples =0;const uint16_t numSamplesToAverage =1024;// ISR 및 외부에서 사용되는 변수volatile int16_t averages[4]; // 빈에 충분한 판독값을 축적하면 ISR은 이를 여기에 복사하고 다시 시작합니다.volatile uint32_t ticks =0; // 시간 유지를 위한 시스템 틱 카운터volatile bool sampleReady =false; // 평균 배열이 업데이트되었음을 나타냅니다. // 변수는 ISRint16_t calib[4] 외부에서만 사용됩니다. // 평균에서 빼는 값(교정 중에 설정됨)volatile uint8_t lastctr;volatile uint16_t misses =0; // 이것은 ISR이 너무 늦게 실행된 횟수를 계산합니다. 모든 것이 제대로 작동하면 0으로 유지해야 합니다.const double halfRoot2 =sqrt(0.5);const double QuarterPi =3.1415927/4.0;const double radiansToDegrees =180.0/3.1415927;// ADC 샘플 및 홀드는 2 ADC 클럭(=32 시스템)에서 발생합니다. clocks) 타이머 1 오버플로 플래그가 설정된 후. // 이것은 약간의 위상 오류를 발생시키며, 이것은 우리가 계산에서 조정합니다. const float phaseAdjust =(45.0 * 32.0)/(float)(TIMER1_TOP + 1);float threshold =5.0; // 더 낮음 =더 큰 감도. 10은 균형 잡힌 코일과 함께 사용할 수 있습니다. // 사용자는 포트 또는 회전식 인코더를 통해 이것을 조정할 수 있습니다.void setup(){ lcd.begin(16, 2);// LCD 16X2 pinMode(encoderButtonPin, INPUT_PULLUP); digitalWrite(T0OutputPin, LOW); 핀모드(T0출력핀, 출력); // 타이머 0을 공급하는 데 사용되는 타이머 1의 펄스 핀 digitalWrite(coilDrivePin, LOW); 핀모드(코일드라이브핀, 출력); // 타이머 0 출력, 전송 코일을 구동하기 위한 구형파 cli(); // Arduino 코어에 의해 설정된 타이머 0을 중지 TCCR0B =0; // 타이머 중지 TIMSK0 =0; // 인터럽트 비활성화 TIFR0 =0x07; // 보류 중인 인터럽트를 모두 지웁니다. // 타이머 1 오버플로에서 채널 0을 트리거하고 읽도록 ADC를 설정합니다. #if USE_3V3_AREF ADMUX =(1 <> 8); OCR1AL =(TIMER1_TOP/2 &0xFF); ICR1H =(TIMER1_TOP>> 8); ICR1L =(TIMER1_TOP &0xFF); TCNT1H =0; TCNT1L =0; TIFR1 =0x07; // 보류 중인 인터럽트를 모두 지웁니다. TIMSK1 =(1 <
15000) *p =15000; } else { *p -=발값; if (*p <-15000) *p =-15000; } if (ctr ==7) { ++numSamples; if (numSamples ==numSamplesToAverage) { numSamples =0; if (!sampleReady) // 이전 샘플이 소비된 경우 { memcpy((void*)averages, bins, sizeof(averages)); 샘플 준비 =참; } memset(빈, 0, sizeof(빈)); } }}void 루프(){ while (!sampleReady) {} uint32_t oldTicks =ticks; if (digitalRead(encoderButtonPin) ==LOW) { // 보정 버튼을 눌렀습니다. 현재 위상 검출기 출력을 저장하고 미래 결과에서 뺍니다. // 코일이 약간 균형이 맞지 않으면 감지기를 사용할 수 있습니다. // 하나의 샘플을 취하는 것보다 여러 샘플을 사용하는 것이 좋습니다. for (int i =0, i <4, ++i) { calib[i] =평균[i]; } 샘플 준비 =거짓; Serial.print("보정됨:"); lcd.setCursor(0,0); lcd.print("보정 중... "); for (int i =0; i <4; ++i) { Serial.write(' '); Serial.print(칼리브[i]); lcd.setCursor(0,1); lcd.print(' '); lcd.print(칼리브[4]); lcd.print(" "); } 직렬.println(); } else { for (int i =0; i <4; ++i) { 평균[i] -=calib[i]; } const 이중 f =200.0; // 결과를 마사지하여 3차 고조파에 대한 감도를 제거하고 200으로 나눕니다. double bin0 =(averages[0] + halfRoot2 * (averages[1] - Averages[3]))/f; 이중 bin1 =(평균[1] + halfRoot2 * (평균[0] + 평균[2]))/f; 이중 bin2 =(평균[2] + halfRoot2 * (평균[1] + 평균[3]))/f; 이중 bin3 =(평균[3] + halfRoot2 * (평균[2] - 평균[0]))/f; 샘플 준비 =거짓; // 평균 읽기를 마쳤으므로 ISR은 자유롭게 다시 덮어쓸 수 있습니다. double amp1 =sqrt((bin0 * bin0) + (bin2 * bin2)); 이중 amp2 =sqrt((bin1 * bin1) + (bin3 * bin3)); 이중 ampAverage =(amp1 + amp2)/2.0; // ADC 샘플/홀드는 타이머 오버플로 후 2 클럭에서 발생합니다. double phase1 =atan2(bin0, bin2) * radiansToDegrees + 45.0; 이중 위상2 =atan2(bin1, bin3) * radiansToDegrees; if (phase1> phase2) { 이중 온도 =phase1; 1단계 =2단계; 2단계 =온도; } 이중 phaseAverage =((phase1 + phase2)/2.0) - phaseAdjust; if (phase2 - phase1> 180.0) { if (phaseAverage <0.0) { phaseAverage +=180.0; } else { 위상 평균 -=180.0; } } // 진단을 위해 개별 빈 수와 독립적으로 계산된 2개의 이득 및 위상을 인쇄합니다. Serial.print(misses); 직렬.쓰기(' '); if (bin0>=0.0) Serial.write(' '); Serial.print(bin0, 2); 직렬.쓰기(' '); if (bin1>=0.0) Serial.write(' '); Serial.print(bin1, 2); 직렬.쓰기(' '); if (bin2>=0.0) Serial.write(' '); Serial.print(bin2, 2); 직렬.쓰기(' '); if (bin3>=0.0) Serial.write(' '); Serial.print(bin3, 2); Serial.print(" "); Serial.print(amp1, 2); 직렬.쓰기(' '); Serial.print(amp2, 2); 직렬.쓰기(' '); if (phase1>=0.0) Serial.write(' '); Serial.print(1단계, 2단계); 직렬.쓰기(' '); if (phase2>=0.0) Serial.write(' '); Serial.print(2단계, 2단계); Serial.print(" "); // 우리가 무엇을 찾았는지 결정하는 데 사용하는 최종 진폭과 위상을 인쇄합니다. if (ampAverage>=0.0) Serial.write(' '); Serial.print(ampAverage, 1); 직렬.쓰기(' '); lcd.setCursor(0,0); lcd.print(" "); lcd.print(ampAverage); lcd.setCursor(0,1); lbg.drawValue(ampAverage, max_ampAverage); if (phaseAverage>=0.0) Serial.write(' '); Serial.print((int)phaseAverage); // 우리가 찾은 것을 결정하고 사용자에게 알립니다. if (ampAverage>=threshold) { // 코일의 중심과 일직선상에 있을 때:// - 비철금속은 음의 위상 변이를 제공합니다. 예를 들어, 두꺼운 구리 또는 알루미늄의 경우 -90deg, 구리 올리브의 경우 -30deg 얇은 알루미늄의 경우. // 철 금속은 제로 위상 변이 또는 작은 양의 위상 변이를 제공합니다. // 그래서 우리는 -20deg 이하의 위상 변이를 가진 것은 무엇이든 비철금속이라고 말할 것입니다. if (phaseAverage <-20.0) { Serial.print("비철금속"); lcd.setCursor(0,0); lcd.print("비철금속 "); } else { Serial.print("철류"); lcd.setCursor(0,0); lcd.print("철류"); } 부동 온도 =ampAverage; int thisPitch =맵(온도, 10, 200, 100, 1500); 톤(3, thisPitch,120); while (temp>
임계값) { Serial.write('!'); 온도 -=(임계값/2); } } 직렬.println(); } 동안 (틱 - oldTicks <8000) { } }
LcdBarGraph 라이브러리.C/C++
미리보기 없음(다운로드만 가능).
섹션> 회로도