Hacer un detector de metales en ATtiny24A

Jugué con tableros tipo Arduino durante mucho tiempo, pero todo el tiempo quería "menos, más barato y más cerca del hardware". Y aquí está la primera experiencia de programación de ATtiny puro. El artículo no será un circuito detector de metales particularmente efectivo. Esto es solo una demostración de lo que es capaz el microcontrolador por 47 centavos + el camino del hervidor de agua en esto, como resultó, no es en absoluto un asunto difícil cambiar de Arduino a un nivel inferior.

Selección de hierro


Después de un breve análisis, la elección recayó en el ATtiny24A-SSU (paquete SOIC de 14 pines). ¿Por qué? La razón es simple: precio + núcleo AVR. Sí, sé que un STM8S103F3P6 aún más potente es más barato ( 39.5 centavos cada uno contra 47 para ATtiny ), pero al tener cierta experiencia con AVR en Arduino, quería AVR para los primeros experimentos.

De los AVR disponibles, seleccionamos ATtiny como el más barato, y luego quiero que el paquete DIP sea más fácil de soldar. Pero los chips en el paquete DIP resultaron ser mucho más caros ( 54 centavos para el ATtiny13A de 8 pies , y el ATtiny23A de 14 pies en el paquete DIP es de 95 centavos ). No me gusta la idea de usar el ATtiny13A debido a su pulpo. El programador ocupará 6 patas y solo 2 quedarán libres, lo que no es suficiente.

Se decidió comprar ATtiny24A-SSU a 47 centavos y otro adaptador a 30 centavos . En total, obtenemos 77 centavos por dispositivo frente a 95 para un paquete DIP y, como beneficio adicional, en dispositivos simples, usamos un adaptador como placa con cables de soldadura directamente, lo que sería imposible con un paquete DIP.

El programador es seleccionado por el mismo principio (el más barato): USBasp por $ 1.86 .

¡Llegado!


Debo decir de inmediato que nunca antes había soldado un caso de SOIC, por lo que había cierto temor de que no funcionara ... Resultó no ser difícil, no fácil ... en general, tuve que hacer algunos esfuerzos, ¡pero al final resultó! Parecía aconsejable calentar no por una conclusión, sino inmediatamente por grupos, de esta manera es más rápido y fácil.

imagen

¿Cómo programar?


ATtiny24A por defecto se sincroniza desde el generador interno y funciona a una frecuencia de 1 MHz. Bueno, déjalo funcionar, me queda perfectamente. Pero para que USBasp trabaje con él con tanta frecuencia, tuvo que soldar un puente adicional (publicaciones en la foto): había un lugar en el tablero, pero los chinos no se molestaron en soldar el puente ... tenían que hacerlo por ellos. En términos del entorno de desarrollo, la elección recayó en Atmel Studio, pero no es compatible con nuestro USBasp ... ¡pero no importa! Incluso al elegir un programador, se planificó actualizarlo a AVR-Doper, que es compatible con el STK500, lo que significa que es compatible con nuestro Atmel Studio. En general, lo flasheé muchas veces con un firmware diferente, pero Atmel Studio no quería verlo ... tristeza ... al final me desespere, lo devolví a USBasp y lo hice por herramienta

imagen



. Después de eso, logré flashear mi ATtiny, parpadear un LED y disfrutar de la poca memoria flash que necesitaba en comparación con Arduino.

Detector de metales


Incluso cuando incursioné en Arduino, hice un detector de metales que funcionaba según el principio de interrupción de resonancia. La sensibilidad es terrible, pero el principio de funcionamiento es muy simple y fácil de implementar en cualquier MK. Se aplica una señal rectangular a un circuito oscilatorio paralelo a través de una resistencia a la frecuencia resonante de este circuito. Cuando un objeto metálico ingresa al campo magnético de la bobina, el factor de calidad del circuito disminuye, la amplitud de la señal medida por el ADC disminuye, el dispositivo nos agrada visual y acústicamente.

El detector tiene 2 modos:
1. Busque la resonancia del circuito. Al mismo tiempo, envía señales rectangulares de diferentes frecuencias al circuito y recuerda la frecuencia a la que la amplitud de las oscilaciones será mayor (también recordamos esta amplitud más grande).
2. El modo de funcionamiento. Enviamos una señal con una frecuencia resonante al circuito y comparamos la amplitud con el máximo que estaba en el primer modo.

¿Complicado? - ¡No!
¿Debo tomar mucha memoria? - ¡No!
¿Tenemos mucha memoria (2 KB flash + 128 bytes de RAM)? - ¡También no!
¿Encaja? Probemos, ¡lo descubriremos!

Como resultado, en forma.

El código de firmware principal
#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;
		}
	}
}


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


¡Y no solo eso encaja, por lo que solo toma 1044 bytes en flash del 2048 disponible! ¡Y esto a pesar del hecho de que además de la función principal, también envía información de depuración (MySerial)! Explicaré un poco por qué aquí (de izquierda a derecha):

imagen


  • Una bobina de alambre es una bobina sensible de un detector de metales;
  • El botón a la izquierda de la placa de pruebas: llama a la función de detección de resonancia;
  • Un diodo + resistencia + condensador es un detector de amplitud;
  • Mantón verde: adaptador con ATtiny24A;
  • LED con una resistencia y una caja negra grande (este es un microamperímetro antiguo) - indicación PWM;
  • Arduino Nano conectado por dos cables: un receptor para depurar información.

Esquema:
imagen
  • L1, C1 - circuito oscilatorio;
  • D1, C2, R2 - detector de amplitud.

La sensibilidad resultó ser demasiado baja para un uso práctico. Se puede sentir un peso de 0.5 kg a partir de 7 centímetros, y una moneda generalmente solo se arroja dentro de la bobina. Pero, en general, el dispositivo funciona:


La grabación muestra cómo, cuando se coloca un objeto metálico en la bobina, las lecturas de ADC caen (en la pantalla) y el MK aumenta la corriente a través del indicador.

¿Que sigue?


La tarea de "jugar con ATtiny" se ha completado. Todo funciona, todo está bien. El rastrillo en el camino fue incluso menor de lo esperado. Pero debido al hecho indicado al principio (que incluso el STM8S103F3P6 más potente es más barato), solo veo dos razones para hacer algo en el AVR: simplicidad y buena documentación. Bueno, tal vez incluso el doble de la corriente de salida máxima permitida en algunos casos puede ser un argumento.

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


All Articles