Herstellung eines Metalldetektors auf ATtiny24A

Ich habe lange Zeit mit Arduino-ähnlichen Boards gespielt, aber die ganze Zeit wollte ich "weniger, billiger und näher an der Hardware!". Und hier ist die erste Erfahrung mit der Programmierung von reinem ATtiny. Der Artikel wird keine besonders effektive Metalldetektorschaltung sein. Dies ist nur eine Demonstration dessen, wozu der Mikrocontroller für 47 Cent + den Weg des Kessels in der Lage ist. Wie sich herausstellte, ist es keineswegs kompliziert, von Arduino auf eine niedrigere Ebene zu wechseln.

Eisenauswahl


Nach einer kurzen Analyse fiel die Wahl auf die ATtiny24A-SSU (14-poliges SOIC-Paket). Warum? Der Grund ist einfach: Preis + AVR-Kern. Ja, ich weiß, dass ein noch leistungsstärkerer STM8S103F3P6 billiger ist ( 39,5 Cent pro Stück gegenüber 47 für ATtiny ), aber da ich einige Erfahrungen mit AVR in Arduino habe, wollte ich AVR für die ersten Experimente.

Aus den verfügbaren AVRs wählen wir ATtiny als das billigste aus, und dann möchte ich, dass das DIP-Paket einfacher zu löten ist. Die Chips im DIP-Paket erwiesen sich jedoch als viel teurer ( 54 Cent für den 8-Fuß-ATtiny13A und der 14-Fuß-ATtiny23A im DIP-Paket sind 95 Cent ). Ich mag die Idee, den ATtiny13A wegen seines Oktopus zu verwenden, nicht. 6 Beine werden vom Programmierer besetzt und nur 2 bleiben frei, was klein ist.

Es wurde beschlossen, ATtiny24A-SSU für 47 Cent und einen weiteren Adapter für 30 Cent zu kaufen . Insgesamt erhalten wir 77 Cent pro Gerät gegenüber 95 Cent für ein DIP-Paket. Als Bonus verwenden wir bei einfachen Geräten einen Adapter als Platine mit Lötdrähten direkt daran, was mit einem DIP-Paket unmöglich wäre.

Der Programmierer wird nach dem gleichen Prinzip (dem billigsten) ausgewählt: USBasp für 1,86 USD .

Ist eingetroffen!


Ich muss sofort sagen, dass ich noch nie zuvor ein SOIC-Gehäuse gelötet habe, daher gab es einige Befürchtungen, dass es nicht funktionieren würde ... Es stellte sich als nicht schwierig heraus, nicht einfach ... im Allgemeinen musste ich einige Anstrengungen unternehmen, aber am Ende stellte sich heraus! Es schien ratsam, sich nicht durch eine Schlussfolgerung aufzuwärmen, sondern sofort durch Gruppen - dieser Weg ist schneller und einfacher.

Bild

Wie programmiere ich?


ATtiny24A wird standardmäßig vom internen Generator getaktet und arbeitet mit einer Frequenz von 1 MHz. Nun, lass es funktionieren, es passt perfekt zu mir. Aber damit USBasp mit einer solchen Frequenz mit ihm arbeiten konnte, musste er einen zusätzlichen Jumper löten (Postings auf dem Foto): Es gab einen Platz auf der Platine, aber die Chinesen machten sich nicht die Mühe, den Jumper zu löten ... sie mussten es für sie tun. In Bezug auf die Entwicklungsumgebung fiel die Wahl auf Atmel Studio, aber es unterstützt unser USBasp nicht ... aber es spielt keine Rolle! Selbst bei der Auswahl eines Programmiergeräts war geplant, ein Upgrade auf AVR-Doper durchzuführen, das mit dem STK500 kompatibel ist, was bedeutet, dass es von unserem Atmel Studio unterstützt wird. Im Allgemeinen habe ich es viele Male mit einer anderen Firmware geflasht, aber Atmel Studio wollte es nicht sehen ... Traurigkeit ... am Ende war ich verzweifelt, habe es zurück auf USBasp geflasht und es per Tool gemacht

Bild



. Danach gelang es mir, mein ATtiny zu blinken, eine LED zu blinken und zu genießen, wie wenig Flash-Speicher im Vergleich zu Arduino benötigt wurde.

Metalldetektor


Selbst als ich mich mit Arduino beschäftigte, baute ich einen Metalldetektor nach dem Prinzip der Resonanzstörung. Die Empfindlichkeit ist schrecklich, aber das Funktionsprinzip ist sehr einfach und auf jedem MK leicht zu implementieren. Ein rechteckiges Signal wird über einen Widerstand mit der Resonanzfrequenz dieser Schaltung an einen parallelen Schwingkreis angelegt. Wenn ein Metallgegenstand in das Magnetfeld der Spule eintritt, nimmt der Qualitätsfaktor der Schaltung ab, die vom ADC gemessene Signalamplitude nimmt ab, das Gerät gefällt uns optisch und akustisch.

Der Detektor verfügt über zwei Modi:
1. Suchen Sie nach der Resonanz des Schaltkreises. Gleichzeitig sendet es rechteckige Signale mit unterschiedlichen Frequenzen an die Schaltung und merkt sich die Frequenz, bei der die Schwingungsamplitude am größten ist (wir erinnern uns auch an diese größte Amplitude).
2. Die Betriebsart. Wir senden ein Signal mit einer Resonanzfrequenz an die Schaltung und vergleichen die Amplitude mit dem Maximum, das im ersten Modus war.

Kompliziert? - Nein!
Sollte ich viel Speicherplatz beanspruchen? - Nein!
Haben wir viel Speicher (2 KB Flash + 128 Byte RAM)? - Auch nicht!
Passt es hinein? Versuchen wir es - wir werden es herausfinden!

Als Ergebnis fit.

Der Haupt-Firmware-Code
#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;
		}
	}
}


Und 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;
	}
};


Und nicht nur das passt, es werden nur 1044 Byte Flash von den verfügbaren 2048 benötigt! Und das trotz der Tatsache, dass es neben der Hauptfunktion auch Debugging-Informationen (MySerial) sendet! Ich werde hier ein wenig erklären, warum (von links nach rechts):

Bild


  • Eine Drahtspule ist eine empfindliche Spule eines Metalldetektors;
  • Die Taste links auf dem Steckbrett ruft die Resonanzerkennungsfunktion auf.
  • Eine Diode + Widerstand + Kondensator ist ein Amplitudendetektor;
  • Grüner Schal - Adapter mit ATtiny24A;
  • LED mit einem Widerstand und einer großen Blackbox (dies ist ein altes Mikroammeter) - PWM-Anzeige;
  • Arduino Nano über zwei Drähte verbunden - ein Empfänger zum Debuggen von Informationen.

Planen:
Bild
  • L1, C1 - Schwingkreis;
  • D1, C2, R2 - Amplitudendetektor.

Die Empfindlichkeit erwies sich für den praktischen Gebrauch als zu niedrig. Ein Gewicht von 0,5 kg ist ab 7 Zentimetern zu spüren, und eine Münze wird im Allgemeinen nur dann geworfen, wenn sie in die Spule geworfen wird. Aber im Allgemeinen funktioniert das Gerät:


Die Aufzeichnung zeigt, wie beim Einsetzen eines Metallobjekts in die Spule die ADC-Messwerte (auf dem Bildschirm) fallen und der MK den Strom durch die Anzeige erhöht.

Was weiter?


Die Aufgabe "mit ATtiny spielen" ist abgeschlossen. Alles funktioniert, alles ist gut. Der Rechen auf der Straße war noch geringer als erwartet. Aufgrund der eingangs genannten Tatsache (dass sogar der leistungsstärkere STM8S103F3P6 billiger ist) sehe ich nur zwei Gründe, etwas am AVR zu tun: Einfachheit und gute Dokumentation. Nun, vielleicht kann in einigen Fällen sogar das Doppelte des maximal zulässigen Ausgangsstroms ein Argument sein.

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


All Articles