ATmega168: колесный энкодер на дополнительном внешнем прерывании

разделы: AVR , myRobot , дата: 11 марта 2016г.


колесо в сборе с двигателем постоянного тока, редуктором и диском энкодера

Дополнительные внешние прерывания, относительно новая штука в периферии AVR. В микроконтроллерах ATmega8/ATmega16 их нет, зато они есть в ATmega88/ATmega168. В официальной документации они описаны в главе 13 "Внешние прерывания". В отличии от обычных внешних прерываний, в дополнительных, одно прерывание отведено на один порт (т.е. на восемь пинов), и в них нельзя выставить условия срабатывания. Прерывание будет генерироваться при любом изменении сигнала, даже если он был изменен программно, самим микроконтроллером.

    Т.о. задача состоит в следующем. Дано одно прерывание, и на него подключено два энкодера. Нужно:
  • определить какой именно энкодер вызвал прерывание;
  • определить время прошедшее с предыдущего срабатывания энкодера, т.е. интервал;
  • обеспечить защиту от "дребезга"
    Рассмотрим матчасть:
  • Диск энкодера имеет двадцать прорезей. Следовательно, поворот на одно деление примерно равно ~0.1π
  • Диаметр колеса около 66мм. Окружность 20.5 см, т.е. поворот на одно деление диска, равно примерно 1см пройденного пути.


датчик энкодера на компараторе LM393

Для считывания энкодера я использовал ИК-оптопару на компараторе LM393. Плата имеет цифровой выход D0. Если в проём оптопары вставить непрозрачный предмет, например монетку, то выход D0 примет значение логической единицы.

Для тестирования соберем такую схему:

В качестве основы для первого наброска я взял пример из поста: "ATmega8: Blink без delay на Си" и немного доработав, написал простой повторитель.

#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <uart.h>

#define LED 		PB5
#define LED_PORT 	PORTB

#define LEFT		PB0
#define LEFT_PIN	PINB

static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);

volatile unsigned long millis;

ISR(TIMER0_OVF_vect)
{
	millis++;
}

int main(void)
{
	//---- init
	init_uart();
	DDRB |= (1<<LED); // led indicator
	DDRB &= ~(1<<LEFT); // input left encoder

        // init 8-bit timer0
        TIMSK0 =(1<<TOIE0);  // timer0 overflow enable
        TCCR0B = (1<<CS01) | (1<<CS00); // prescaler 1/64

	unsigned  long curTime, lastTime, period;
//	uint16_t sec; sec=0;

	millis=0;
	lastTime=0;
	period=20; // 1 sec

	//---- ready indication
	blink13(3);
	stdout = &mystdout;
	printf("Ok, I'm ready!\n");
	sei();

	//---- main body
	for (;;)
	{

		uint8_t val;
		curTime=millis;
		if ((curTime -lastTime)> period)
		{
			val=LEFT_PIN & (1<<LEFT);
			if (val)
				LED_PORT |= (1<<LED);
			else
				LED_PORT &= ~(1<<LED);

			lastTime=curTime;
		}
	};

        return 0;
}

Здесь, в главном цикле программы реализуется опрос порта, и при получении сигнала энкодера, загорается светодиод на PB5(pin 13)

На следующем этапе, нужно будет опрос порта перекинуть из главного цикла в прерывание. Также требуется узнать временной интервал, для предотвращения дребезга.

#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <uart.h>

#define LED 		PB5
#define LED_PORT 	PORTB

#define LEFT		PB0
#define LEFT_PIN	PINB

static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);

volatile unsigned long millis;
volatile unsigned long lastTime;
volatile unsigned long curTime;

volatile uint8_t val;
volatile uint8_t last;

volatile uint16_t data;

ISR(TIMER0_OVF_vect)
{
	millis++;
}

ISR(PCINT0_vect)
{
	val=LEFT_PIN & (1<<LEFT);
        if (val != last)
	{

		if (val)
        		LED_PORT |= (1<<LED);
	        else
		{
        		LED_PORT &= ~(1<<LED);
			curTime=millis-lastTime;
			data=(uint16_t)curTime;
			lastTime=millis;
		}
		last=val;
	}
}

int main(void)
{
	//---- init
	init_uart();
	DDRB |= (1<<LED); // led indicator
	DDRB &= ~(1<<LEFT); // input left encoder

        // init 8-bit timer0
        TIMSK0 =(1<<TOIE0);  // timer0 overflow enable
        TCCR0B = (1<<CS01) | (1<<CS00); // prescaler 1/64

	// init external interrupt
	PCICR = (1<<PCIE0); // PCIE0 enable
	PCMSK0 = (1<<PCINT0); // PCINT0 enable

	last=0;

	//---- ready indication
	blink13(3);
	stdout = &mystdout;
	printf("Ok, I'm ready!\n");
	sei();


	//---- main body
	for (;;)
	{
		_delay_ms(100);
		printf("period: %u\n", data);

	};

        return 0;
}

Я подключил двигатель на холостом ходу чтобы узнать максимальную скорость которую он способен развить. Через UART получил такую картинку:

т.е. одно деление энкодера проходится минимум за 15 миллисекунд. Если одно деление энкодера равно 1 сантиметру пройденного пути, то 1000/15 ~66 сантиметров в секунду. Теперь, для устранения дребезга энкодера, для отсечки ложных срабатываний, можно будет взять величину в 10мс, предполагая, что быстрее колесо уже не покатиться.

Теперь, чтобы реализовать исходную задачу, нужно чтобы счетчики от двух энкодерах работали на одном прерывании. Получилось это так:

#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <uart.h>

#define LED 		PB5
#define LED_PORT 	PORTB

#define LEFT		PB0
#define LEFT_PIN	PINB

#define RIGHT		PB1
#define RIGHT_PIN	PINB

#define MIN		10

static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);

volatile unsigned long millis;
volatile unsigned long lastTimeR, curTimeR;
volatile unsigned long lastTimeL, curTimeL;

volatile uint8_t valR, lastR;
volatile uint8_t valL, lastL;
volatile uint16_t dataL, dataR;

ISR(TIMER0_OVF_vect)
{
	millis++;
}

ISR(PCINT0_vect)
{
	valL=LEFT_PIN & (1<<LEFT);
	valR=RIGHT_PIN & (1<<RIGHT);
	curTimeL=millis-lastTimeL;
	curTimeR=millis-lastTimeR;

        if ((valL != lastL) && (curTimeL > MIN))
	{
        	LED_PORT ^= (1<<LED);
		if (!valL)
		{
			lastTimeL=curTimeL;
			dataL++;
		}
		lastL=valL;

	} else if ((valR != lastR) && (curTimeR > MIN))
        {
                LED_PORT ^= (1<<LED);
                if (!valR)
                {
                        lastTimeR=curTimeR;
                        dataR++;
                }
                lastR=valR;
        }

}

int main(void)
{
	//---- init
	init_uart();
	DDRB |= (1<<LED); // led indicator
	DDRB &= ~(1<<LEFT); // input left encoder

        // init 8-bit timer0
        TIMSK0 =(1<<TOIE0);  // timer0 overflow enable
        TCCR0B = (1<<CS01) | (1<<CS00); // prescaler 1/64

	// init external interrupt
	PCICR = (1<<PCIE0); // PCIE0 enable
	PCMSK0 = (1<<PCINT0) | (1<<PCINT1); // PCINT0 and PCINT1 enable

	lastL=0; lastR=0;
	dataL=0; dataR=0;

	//---- ready indication
	blink13(3);
	stdout = &mystdout;
	printf("Ok, I'm ready!\n");
	sei();


	//---- main body
	for (;;)
	{
		_delay_ms(1000);
		printf("Left: %u Right: %u\n", dataL, dataR);
		cli(); dataL=0; dataR=0; sei();
	};

        return 0;
}

второй сенсор был подключен на PB1(digital pin 9). Значения счетчиков обоих энкодеров полученных за одну секунду посылаются на UART, после чего обнуляются.

Результат работы:

Из скриншота видно, что колесо за одну секунду совершает 3 с небольшим оборотов. т.е. максимальная скорость будет около 65 сантиметров в секунду или 2.34 км/ч. Половина скорости человеческого шага.

скачать архив с исходниками и Makefile можно здесь:

скачать

поделиться: