Fazendo um detector de metais no ATtiny24A

Joguei com placas do tipo Arduino por um longo tempo, mas o tempo todo eu queria “menos, mais barato e mais próximo do hardware!”. E aqui está a primeira experiência de programação do ATtiny puro. O artigo não será um circuito detector de metais particularmente eficaz. Esta é apenas uma demonstração do que o microcontrolador é capaz por 47 centavos + o caminho da chaleira, como se viu, não é nada difícil mudar do Arduino para um nível mais baixo.

Seleção de ferro


Após uma breve análise, a escolha recaiu sobre o ATtiny24A-SSU (pacote SOIC de 14 pinos). Por quê? A razão é simples: preço + núcleo AVR. Sim, eu sei que um STM8S103F3P6 ainda mais poderoso é mais barato ( 39,5 centavos de dólar contra 47 no ATtiny ), mas, tendo alguma experiência com o AVR no Arduino, eu queria o AVR para os primeiros experimentos.

Nos AVRs disponíveis, selecionamos ATtiny como o mais barato e, em seguida, quero que o pacote DIP seja mais fácil de soldar. Mas os chips no pacote DIP acabaram sendo muito mais caros ( 54 centavos para o ATtiny13A de 8 pés e o ATtiny23A de 14 pés no pacote DIP é de 95 centavos ). Não gosto da ideia de usar o ATtiny13A por causa de seu polvo. 6 pernas serão ocupadas pelo programador e apenas duas permanecerão livres, o que não é suficiente.

Decidiu-se comprar ATtiny24A-SSU a 47 centavos e outro adaptador a 30 centavos . No total, obtemos 77 centavos por dispositivo versus 95 para um pacote DIP e, como bônus, em dispositivos simples, usamos um adaptador como uma placa com fios de solda diretamente a ele, o que seria impossível com um pacote DIP.

O programador é selecionado pelo mesmo princípio (o mais barato): USBasp por US $ 1,86 .

Chegou!


Devo dizer imediatamente que nunca havia soldado um caso SOIC antes, então havia um certo medo de que não funcionasse ... Acabou não sendo difícil, nem fácil ... em geral, tive que fazer alguns esforços, mas no final acabou! Parecia aconselhável aquecer não por uma conclusão, mas imediatamente por grupos - dessa maneira é mais rápida e fácil.

imagem

Como programar?


O ATtiny24A, por padrão, possui um clock do gerador interno e opera a uma frequência de 1 MHz. Bem, deixe funcionar, combina perfeitamente comigo. Mas, para o USBasp trabalhar com ele com tanta frequência, ele teve que soldar um jumper adicional (postagens na foto): havia um lugar no quadro, mas os chineses não se preocuparam em soldar o jumper ... eles precisavam fazer isso por eles. Em termos de ambiente de desenvolvimento, a escolha recaiu sobre o Atmel Studio, mas ele não suporta o USBasp ... mas isso não importa! Mesmo ao escolher um programador, foi planejado atualizá-lo para o AVR-Doper, que é compatível com o STK500, o que significa que ele é suportado pelo nosso Atmel Studio. Em geral, eu mostrei muitas vezes com firmware diferente, mas o Atmel Studio não queria vê-lo ... tristeza ... no final, eu me desesperei, mostrei de volta para USBasp e fiz por ferramenta

imagem



. Depois disso, consegui piscar meu ATtiny, piscar um LED e apreciar a pouca memória flash necessária em comparação com o Arduino.

Detector de metal


Mesmo quando brinquei com o Arduino, fiz um detector de metais trabalhando no princípio da interrupção da ressonância. A sensibilidade é terrível, mas o princípio de operação é muito simples e facilmente implementado em qualquer MK. Um sinal retangular é aplicado a um circuito oscilatório paralelo através de um resistor na frequência ressonante desse circuito. Quando um objeto metálico entra no campo magnético da bobina, o fator de qualidade do circuito diminui, a amplitude do sinal medida pelo ADC diminui, o dispositivo nos agrada visual e acusticamente.

O detector possui 2 modos:
1. Procure a ressonância do circuito. Ao mesmo tempo, envia sinais retangulares de diferentes frequências para o circuito e lembra a frequência na qual a amplitude das oscilações será a maior (também lembramos essa maior amplitude).
2. O modo de operação. Enviamos um sinal com uma frequência ressonante para o circuito e comparamos a amplitude com o máximo que estava no primeiro modo.

Complicado? - Não!
Devo ocupar muita memória? - Não!
Temos muita memória (flash de 2 KB + 128 bytes de RAM)? - Também não!
Isso se encaixa? Vamos tentar - vamos descobrir!

Como resultado, ajuste.

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


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


E não apenas esse ajuste, portanto, são necessários apenas 1044 bytes em flash dos 2048 disponíveis! E isso apesar do fato de que, além da função principal, ele também envia informações de depuração (MySerial)! Vou explicar um pouco por que aqui (da esquerda para a direita):

imagem


  • Uma bobina de fio é uma bobina sensível de um detector de metais;
  • O botão à esquerda da tábua de pão - chama a função de detecção de ressonância;
  • Um diodo + resistor + capacitor é um detector de amplitude;
  • Xale verde - adaptador com ATtiny24A;
  • LED com um resistor e uma grande caixa preta (este é um microamperímetro antigo) - indicação PWM;
  • Arduino Nano conectado por dois fios - um receptor para depuração de informações.

Esquema:
imagem
  • L1, C1 - circuito oscilatório;
  • D1, C2, R2 - detector de amplitude.

A sensibilidade acabou sendo muito baixa para uso prático. Um peso de 0,5 kg pode ser sentido a partir de 7 centímetros, e uma moeda geralmente é apenas se jogada dentro da bobina. Mas, em geral, o dispositivo funciona:


A gravação mostra como, quando um objeto metálico é colocado na bobina, as leituras do ADC caem (na tela) e o MK aumenta a corrente através do indicador.

Qual é o próximo?


A tarefa de "brincar com o ATtiny" foi concluída. Tudo funciona, está tudo bem. O ancinho na estrada foi ainda menor do que o esperado. Mas, devido ao fato indicado no início (que mesmo o STM8S103F3P6 mais poderoso é mais barato), vejo apenas duas razões para fazer algo no AVR: simplicidade e boa documentação. Bem, talvez até o dobro da corrente de saída máxima permitida em alguns casos possa ser um argumento.

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


All Articles