在ATtiny24A上制作金属探测器

我玩了很长时间的类似Arduino的板,但是一直以来我都想“更便宜,更便宜,更接近硬件!”,这是对纯ATtiny进行编程的第一次经验。该物品将不是某些特别有效的金属探测器电路。事实证明,从Arduino切换到较低的级别,微控制器的功能仅需47美分,而且水壶的运行路径也不难。

铁的选择


经过简短分析后,选择还是使用ATtiny24A-SSU(14引脚SOIC封装)。为什么?原因很简单:价格+ AVR核心。是的,我知道功能更强大的STM8S103F3P6便宜(每枚39.5美分,ATtiny 则为47 美分),但是我有一些在Arduino上使用AVR的经验,所以我希望AVR可以用于第一个实验。

从可用的AVR中,我们选择最便宜的ATtiny,然后我希望DIP封装更容易焊接。但在DIP封装芯片被证明是昂贵得多(54美分为8英尺ATTINY13A,并且在DIP封装14英尺ATtiny23A是95美分)。我不喜欢因为章鱼而使用ATtiny13A的想法。程序员将占用6条腿,而只有2条腿空闲,这还不够。

决定以47美分的价格购买ATtiny24A-SSU,并以30美分的价格购买另一个适配器总的来说,每台设备我们获得77美分,而DIP封装则为95美分,此外,在简单的设备中,使用适配器作为直接将焊锡线连接到其上的板子是DIP封装不可能做到的。

按照相同的原则(最便宜)选择编程器:USBasp售价$ 1.86

到了!


我必须马上说,我以前从未焊接过SOIC外壳,因此有人担心它不起作用。。。事实证明这并不困难,也不容易...总的来说,我不得不付出一些努力,但最终结果是!似乎不建议仅凭一个结论就热身,而应按小组进行热身-这种方式更快,更容易。

图片

如何编程?


默认情况下,ATtiny24A由内部发生器提供时钟,并以1 MHz的频率工作。好吧,让它工作,非常适合我。但是为了使USBasp以这样的频率与他合作,他必须焊接一个额外的跳线(照片上的贴子): 板上有一个地方,但是中国人没有费心去焊接跳线...他们必须为他们做。 就开发环境而言,选择权在于Atmel Studio,但它不支持我们的USBasp ...但这没关系!即使选择编程器,也计划将其升级到与STK500兼容的AVR-Doper,这意味着我们的Atmel Studio支持它。通常,我使用不同的固件多次刷新它,但是Atmel Studio不想看到它……悲伤……最后我感到绝望,将其刷新回USBasp并通过工具完成

图片



之后,我设法使ATtiny闪烁,使LED闪烁,并享受与Arduino相比所需的闪存很少。

金属探测器


即使当我涉猎Arduino时,我也制造了一个金属探测器,用于共振破坏原理。灵敏度很差,但是操作原理非常简单,并且可以在任何MK上轻松实现。矩形信号通过电阻以并联谐振电路的谐振频率施加到该电路。当金属物体进入线圈的磁场时,电路的品质因数降低,ADC测量的信号幅度降低,该器件在视觉和听觉上都令我们满意。

检测器有两种模式:
1.搜索电路的谐振。同时,它将不同频率的矩形信号发送到电路,并记住振荡幅度最大的频率(我们也记得这个最大幅度)。
2.操作模式。我们将具有谐振频率的信号发送到电路,并将振幅与第一种模式下的最大值进行比较。

复杂?-不!
是否应该占用大量内存?-不!
我们是否有很多内存(2 KB闪存+ 128字节RAM)?-也不行!
它适合吗?让我们尝试-我们将找出答案!

结果,适合。

主要固件代码
#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;
		}
	}
}


还有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;
	}
};


不仅如此,从2048个可用闪存中仅占用1044个字节!而且,尽管除了主要功能外,它还发送调试信息(MySerial)! 我会在这里解释原因(从左到右):

图片


  • 线圈是金属探测器的灵敏线圈。
  • 面包板左侧的按钮-调用共振检测功能;
  • 二极管+电阻+电容器是一个幅度检测器;
  • 绿色披肩-上面带有ATtiny24A的适配器;
  • 带电阻器和大黑盒的LED(这是一个古老的微安表)-PWM指示;
  • Arduino Nano通过两根线连接-调试信息接收器。

方案:
图片
  • L1,C1-振荡电路;
  • D1,C2,R2-幅度检测器。

结果表明灵敏度太低而无法实际使用。从7厘米处可以感觉到0.5千克的重量,通常只有将硬币扔进线圈内才能投硬币。但是,一般而言,该设备可以运行:


记录显示了当将金属物体放入线圈中时,ADC读数(在屏幕上)下降,而MK如何增加流经指示器的电流。

下一步是什么?


“与ATtiny一起玩”的任务已完成。一切正常,一切都很好。路上的耙子甚至比预期的要少。但是由于一开始就指出了事实(即使是功能更强大的STM8S103F3P6都更便宜),所以我发现在AVR上执行某些操作的原因只有两个:简单性和良好的文档编制。好吧,在某些情况下甚至可能是最大允许输出电流的两倍。

Source: https://habr.com/ru/post/zh-CN383819/


All Articles