Membuat detektor logam pada ATtiny24A

Saya bermain dengan papan seperti Arduino untuk waktu yang lama, tetapi sepanjang waktu saya ingin "kurang, lebih murah dan lebih dekat ke perangkat keras!", Dan di sini adalah pengalaman pertama pemrograman ATtiny murni. Artikel ini tidak akan menjadi beberapa rangkaian detektor logam yang sangat efektif. Ini hanya demonstrasi dari apa yang mampu dilakukan mikrokontroler selama 47 sen + jalur ketel dalam hal ini, ternyata, sama sekali bukan masalah yang sulit untuk beralih dari Arduino ke tingkat yang lebih rendah.

Pemilihan besi


Setelah analisis singkat, pilihan jatuh pada ATtiny24A-SSU (paket SOIC 14-pin). Mengapa? Alasannya sederhana: harga + inti AVR. Ya, saya tahu bahwa STM8S103F3P6 yang lebih kuat lebih murah ( 39,5 sen dibandingkan dengan 47 untuk ATtiny ), tetapi memiliki pengalaman dengan AVR di Arduino, saya ingin AVR untuk percobaan pertama.

Dari AVR yang tersedia, kami memilih ATtiny sebagai yang termurah, dan kemudian saya ingin paket DIP lebih mudah disolder. Tetapi chip dalam paket DIP ternyata jauh lebih mahal ( 54 sen untuk ATtiny13A 8 kaki , dan ATtiny23A 14 kaki dalam paket DIP adalah 95 sen ). Saya tidak suka gagasan menggunakan ATtiny13A karena guritanya. 6 kaki akan ditempati oleh programmer dan hanya 2 yang tetap gratis, yang tidak cukup.

Diputuskan untuk membeli ATtiny24A-SSU seharga 47 sen dan adaptor lain seharga 30 sen . Secara total, kami mendapatkan 77 sen per perangkat versus 95 untuk paket DIP dan, sebagai bonus, dalam perangkat sederhana menggunakan adaptor sebagai papan dengan kabel solder langsung ke sana, yang tidak mungkin dilakukan dengan paket DIP.

Programmer dipilih oleh prinsip yang sama (termurah): USBasp seharga $ 1,86 .

Tiba!


Saya harus segera mengatakan bahwa saya tidak pernah menyolder kasus SOIC sebelumnya, jadi ada kekhawatiran bahwa itu tidak akan berhasil ... Ternyata tidak sulit, tidak mudah ... secara umum, saya harus melakukan beberapa upaya, tetapi pada akhirnya ternyata! Tampaknya disarankan untuk melakukan pemanasan bukan dengan satu kesimpulan, tetapi langsung dengan kelompok - cara ini lebih cepat dan mudah.

gambar

Bagaimana cara memprogram?


ATtiny24A secara default clock dari generator internal dan beroperasi pada frekuensi 1 MHz. Yah, biarkan itu bekerja, itu cocok untukku. Tetapi agar USBasp dapat bekerja dengannya pada frekuensi seperti itu, ia harus menyolder jumper tambahan (posting di foto): Ada tempat di papan tulis, tetapi orang Cina tidak repot-repot menyolder jumper ... mereka harus melakukannya untuk mereka. Dalam hal lingkungan pengembangan, pilihan ada di Atmel Studio, tetapi itu tidak mendukung USBasp kami ... tetapi itu tidak masalah! Bahkan ketika memilih seorang programmer, direncanakan untuk memutakhirkannya ke AVR-Doper, yang kompatibel dengan STK500, yang berarti didukung oleh Atmel Studio kami. Secara umum, saya mem-flashnya berkali-kali dengan firmware berbeda, tetapi Atmel Studio tidak ingin melihatnya ... kesedihan ... pada akhirnya saya putus asa, mem-flash-nya kembali ke USBasp dan melakukannya dengan alat

gambar



. Setelah itu, saya berhasil mem-flash ATtiny saya, berkedip dengan LED dan menikmati betapa sedikit memori flash yang dibutuhkan dibandingkan dengan Arduino.

Detektor logam


Bahkan ketika saya mencoba-coba Arduino, saya membuat detektor logam yang bekerja pada prinsip gangguan resonansi. Sensitivitasnya mengerikan, tetapi prinsip operasinya sangat sederhana dan mudah diterapkan pada MK mana pun. Sinyal persegi panjang diterapkan pada rangkaian osilasi paralel melalui resistor pada frekuensi resonansi sirkuit ini. Ketika benda logam memasuki medan magnet koil, faktor kualitas sirkuit menurun, amplitudo sinyal yang diukur oleh ADC berkurang, perangkat menyenangkan kita secara visual dan akustik.

Detektor memiliki 2 mode:
1. Cari resonansi rangkaian. Pada saat yang sama, ia mengirimkan sinyal persegi panjang dari frekuensi yang berbeda ke sirkuit dan mengingat frekuensi di mana amplitudo osilasi akan menjadi yang terbesar (kami juga ingat amplitudo terbesar ini).
2. Mode operasi. Kami mengirim sinyal dengan frekuensi resonansi ke sirkuit dan membandingkan amplitudo dengan maksimum yang ada di mode pertama.

Rumit? - Tidak!
Haruskah saya mengambil banyak memori? - Tidak!
Apakah kita memiliki banyak memori (2 KB flash + 128 byte RAM)? - Juga tidak!
Apakah cocok? Ayo coba - kita akan mencari tahu!

Hasilnya, bugar.

Kode firmware utama
#include <avr/io.h>
#include <avr/interrupt.h>
#include "mySerial.cpp"

MySerial ms(&PORTB, &PINB, &DDRB, 0, &PORTB, &PINB, &DDRB, 1);

volatile uint16_t maxAdc = 0; //    (    )
volatile uint8_t dispMode = 0; // 0 -  , 1 -  
volatile uint8_t flags0 = 0; // [0] - need setRes
volatile uint16_t adcSource = 0;
//volatile bool needADC = false;
#define ADC_SOURCE_ARRAY_SIZE_POWER 5
#define ADC_SOURCE_ARRAY_SIZE (1 << ADC_SOURCE_ARRAY_SIZE_POWER)
uint16_t adcSourceArray[ADC_SOURCE_ARRAY_SIZE];
uint8_t adcSourceArrayLastWrited = 0;
void showVal(void);

ISR(ADC_vect){
	//adcSourceArrayLastWrited++;
	if(++adcSourceArrayLastWrited >= ADC_SOURCE_ARRAY_SIZE)
		adcSourceArrayLastWrited = 0;
	adcSourceArray[adcSourceArrayLastWrited] = ADCL | (ADCH << 8);
	uint16_t adcSourceTmp = 0;
	for(uint8_t i = 0; i < ADC_SOURCE_ARRAY_SIZE; i++)
		adcSourceTmp += adcSourceArray[adcSourceArrayLastWrited];
	adcSource = (adcSourceTmp >> ADC_SOURCE_ARRAY_SIZE_POWER);
	//adcSource = ADCL | (ADCH << 8);
	//needADC = false;
}

volatile uint8_t pinaChanged = 0;
volatile uint8_t tim0_ovf_counter = 0;
//uint32_t ticks = 0;
volatile uint16_t ticks10ms = 0;
//volatile uint16_t ticks = 0;
ISR(TIM0_OVF_vect)
{		
	//ticks++;
	//if(255 == tim0_ovf_counter++){ // ticks every 65.5 ms
	if(39 == (tim0_ovf_counter++)){ // ticks every 10 ms
		tim0_ovf_counter = 0;
		ticks10ms++;
		if(pinaChanged > 0)
			pinaChanged--;
	}
}

uint16_t dist16(uint16_t lo, uint16_t hi){
	return (lo <= hi) ? (hi - lo) : (0xFFFF - lo + hi);
}
/*void delayTicks(uint16_t val){
	uint16_t tim0_ovf_counter0 = tim0_ovf_counter;
	while(dist16(tim0_ovf_counter0, tim0_ovf_counter) < val)
		showVal();
}*/
void delay10ms(uint16_t val){
	uint16_t ticks10ms0 = ticks10ms;
	while(dist16(ticks10ms0, ticks10ms) < val)
		showVal();
}

void showVal(void){
	ms.sendByte(adcSource >> 2);
	switch(dispMode){
		case 0:
			OCR0A = adcSource >> 2;
		break;
		case 1:
			uint16_t maxAdcPlus = maxAdc + 2;
			uint16_t dispVal = (maxAdcPlus > adcSource) ? ((maxAdcPlus - adcSource)) : 0;
			dispVal <<= 4;
			if(dispVal > 255)
				dispVal = 255;
			OCR0A = dispVal;
		break;
	}
}

void setRes(void)
{
	dispMode = 0;
	uint16_t maxOCR = 0;
	maxAdc = 0;
	for(uint16_t curOCR = 35; curOCR < 50; curOCR++){
		OCR1A = curOCR;
		OCR1B = (curOCR >> 1);
		//uint32_t ticks0 = ticks;
		//uint16_t ticks0 = ticks;		
		//while(dist16(ticks0, ticks) < 20)
		//	showVal();
		delay10ms(30);
		if(adcSource > maxAdc){
			maxAdc = adcSource;
			maxOCR = curOCR;
		}
	}
	OCR1A = maxOCR;
	OCR1B = (maxOCR >> 1);
	dispMode = 1;
}

ISR(PCINT0_vect)
{	
	if(pinaChanged > 0)
		return;
	pinaChanged = 5;
	if(0 == (PINA & (1 << 7)))
		flags0 |= 1;
}


int main(void)
{
	// init PWM:
	DDRB |= 4;  // OC0A as output
	//TIMSK0 |= 7; //  TIM0_OVF_vect, TIM0_COMPA_vect, TIM0_COMPB_vect
	TIMSK0 |= 1; //  TIM0_OVF_vect
	TCCR0B |= 1; // no prescaling. OVF  256  (3.91 )
	//TCCR0B |= 2; // clk/8
	//TCCR0B |= 3; // clk/64
	//TCCR0B |= 5; // clk/1024. OVF  262  (3.815 )
	TCCR0A |= (3 | (1 << 7)); //WGM0[2:0] = 3 - fawt PWM mode. bit7 -  
	//OCR0A = 150;
	//OCR0B = 100;
	// :init PWM
	
	// init ADC:
	//ADMUX |= (1 << 7); // internal 1.1V reference. Comment this to use VCC as reference
	//ADMUX |= (1 << 3) | 1; // MUX[5:0] = 001001. Res = ADC0 - ADC1. Gain = 20
	ADMUX |= (1 << 3); // MUX[5:0] = 001000. Res = ADC0 - ADC1. Gain = 1
	ADCSRA |= ((1 << 7) // enable ADC
		| (1 << 5) // ADC Auto Trigger Enable.  
		| (1 << 6) //  1 
		| (1 << 3)   // ADC interrupt enable
		| (1 << 2)); // prescaller = 16 ( 50-200 kHz)
	// :init ADC
	
	// init 16-bit timer: // pin7 = MOSI = PA6 = OC1A
	//DDRA |= (1 << 6); // OC1A as output
	DDRA |= (1 << 5); // OC1B as output
	//TCCR1A |= (1 << 6); // Toggle OC1A/OC1B on Compare Match
	TCCR1A |= (1 << 5) // Clear OC1B on Compare Match, set OC1B at BOTTOM (non-inverting mode)
		| (3); // set WGM10 and WGM11 // WGM1[3:0] = 1111 - Fast PWM, TOP = OCR1A.	
//	TCCR1A |= (1 << 6) | (1 << 7) // Set OC1A on Compare Match (Set output to high level).
//			| (1 << 5); // Clear OC1B on Compare Match	(Set output to low level)
	TCCR1B |= 1 // no prescalling
			| (1 << 3) | (1 << 4); // set WGM12 and WGM13
	//TIMSK1 |= (1 << 2) | (1 << 1) | 1; // enable all interrupts
	OCR1B = 21;
	OCR1A = 42;
	//for(;;){;};
	// :init 16-bit timer
	
	// init button:
	PORTA |= (1 << 7); //     6- . PA7 = PCINT7
	GIMSK |= (1 << 4); // Pin Change Interrupt Enable 0
	PCMSK0 |= (1 << 7); //   PCINT7
	// :init button
	
	sei();
	flags0 = 1; //   22       !
	while(1){
		showVal();
		//ms.sendByte(0x99);
		if(0 != (1 & flags0)){
			setRes();
			flags0 &= ~1;
		}
	}
}


Dan mySerial.cpp
#include <avr/io.h>
#include <avr/interrupt.h>

class MySerial{
	public:
	volatile uint8_t *dataPort;
	volatile uint8_t *dataPin;
	volatile uint8_t *dataDDR;
	volatile uint8_t *clockPort;
	volatile uint8_t *clockPin;
	volatile uint8_t *clockDDR;
	uint8_t dataPinMask, clockPinMask;
	uint8_t rBit,
		lastState, // (dataPin << 1) | clockPin
		inData;
	// MySerial ms(&PORTD, &PIND, &DDRD, 2, &PORTD, &PIND, &DDRD, 3);
	MySerial(
		volatile uint8_t *_dataPort,
		volatile uint8_t *_dataPin,
		volatile uint8_t *_dataDDR,
		uint8_t _dataPinN,
		volatile uint8_t *_clockPort,
		volatile uint8_t *_clockPin,
		volatile uint8_t *_clockDDR,
		uint8_t _clockPinN
	){
		rBit = 255;
		lastState = 3;
		inData = 0;
		dataPort = _dataPort;
		dataPin = _dataPin;
		dataDDR = _dataDDR;
		dataPinMask = (1 << _dataPinN);
		clockPort = _clockPort;
		clockPin = _clockPin;
		clockDDR = _clockDDR;
		clockPinMask = (1 << _clockPinN);
	}
	void dataZero() {
		*dataPort &= ~dataPinMask; //digitalWrite(pinData, 0);
		*dataDDR |= dataPinMask;   //pinMode(pinData, OUTPUT);
	}
	void dataRelease() {
		*dataDDR &= ~dataPinMask; //pinMode(pinData, INPUT);
		*dataPort |= dataPinMask; //digitalWrite(pinData, 1);
	}
	void clockZero() {
		*clockPort &= ~clockPinMask; //digitalWrite(pinClock, 0);
		*clockDDR |= clockPinMask;  //pinMode(pinClock, OUTPUT);
	}
	void clockRelease() {
		*clockDDR &= ~clockPinMask; //pinMode(pinClock, INPUT);
		*clockPort |= clockPinMask; //digitalWrite(pinClock, 1);
	}
	void pause() {
		//delay(v * 1);
		//unsigned long time = micros();
		//while(v-- > 0)
		for(uint16_t i = 0; i < 250; i++)
			__asm__ __volatile__(
				"nop"
			);
		//time = micros() - time;
		//LOG("Paused "); LOG(time); LOGLN("us");
	}
	void sendByte(uint8_t data){
		//LOG("Sending byte: "); LOGLN(data);
		//   data  clock = 1:
		dataRelease();
		clockRelease();
		pause();
		dataZero();
		pause();
		
		//LOGLN("Going to loop...");
		for(uint8_t i = 0; i < 8; i++){
			clockZero();
			pause();
			if( 0 == (data & (1 << 7)) )
				dataZero();
			else
				dataRelease();
			//LOG("Sending bit "); LOGLN((data & (1 << 7)));
			pause();
			clockRelease();
			pause();

			data = data << 1;
		}
		
		//   data  clock = 1:
		dataZero();
		pause();
		dataRelease();
		pause();
	}	
	void tick(){
		//uint8_t curState = (digitalRead(pinData) << 1) | digitalRead(pinClock);
		dataRelease();
		clockRelease();
		uint8_t curState = 0;
		if(0 != (*dataPin & dataPinMask))
			curState |= 2;
		if(0 != (*clockPin & clockPinMask))
			curState |= 1;

		//LOGLN(curState);
		if((3 == lastState) && (1 == curState)) //  
		rBit = 7;
		if(255 != rBit)
		if( (0 == (lastState & 1)) && (1 == (curState & 1)) ) { //    clock
			//LOG("Getted bit "); LOGLN((curState >> 1));
			if( 0 == (curState >> 1) )
				inData &= ~(1 << rBit);
			else
				inData |= (1 << rBit);
			rBit--;
		}
		
		if( (1 == lastState) && (3 == curState) ){ //  
			//LOG("Recieved byte: "); LOGLN(inData);
			rBit = 255;
			//delay(5000);
		}
		lastState = curState;
	}
};


Dan tidak hanya itu yang pas, sehingga hanya dibutuhkan 1044 bytes dalam flash dari 2048 yang tersedia! Dan ini terlepas dari fakta bahwa selain fungsi utama, ia juga mengirimkan informasi debug (MySerial)! Saya akan menjelaskan sedikit mengapa di sini (dari kiri ke kanan):

gambar


  • Gulungan kawat adalah gulungan sensitif detektor logam;
  • Tombol di sebelah kiri papan tempat memotong roti - memanggil fungsi deteksi resonansi;
  • Dioda + resistor + kapasitor adalah detektor amplitudo;
  • Green shawl - adapter dengan ATtiny24A di atasnya;
  • LED dengan resistor dan kotak hitam besar (ini adalah microammeter kuno) - Indikasi PWM;
  • Arduino Nano dihubungkan oleh dua kabel - penerima untuk informasi debug.

Skema:
gambar
  • L1, C1 - sirkuit berosilasi;
  • D1, C2, R2 - detektor amplitudo.

Sensitivitas ternyata terlalu rendah untuk penggunaan praktis. Bobot 0,5 kg dapat dirasakan dari 7 sentimeter, dan koin umumnya hanya jika dilemparkan ke dalam koil. Namun, secara umum, perangkat berfungsi:


Rekaman menunjukkan bagaimana, ketika benda logam ditempatkan di koil, pembacaan ADC jatuh (di layar) dan MK meningkatkan arus melalui indikator.

Apa berikutnya?


Tugas "bermain dengan ATtiny" telah selesai. Semuanya berfungsi, semuanya baik-baik saja. Penggaruk di jalan bahkan lebih kecil dari yang diharapkan. Tetapi karena fakta yang ditunjukkan di awal (bahwa STM8S103F3P6 yang lebih kuat lebih murah), saya hanya melihat dua alasan untuk melakukan sesuatu pada AVR: kesederhanaan dan dokumentasi yang baik. Yah, mungkin bahkan dua kali lipat dari arus keluaran maksimum yang diijinkan dalam beberapa kasus dapat menjadi argumen.

Source: https://habr.com/ru/post/id383819/


All Articles