ATmega8: датчик скорости компьютерного вентилятора на прерывании по захвату (capture interrupt)

разделы: AVR , дата: 13 ноября 2015г.


при поступлении сигнала на захват,
содержимое регистров TCNT1H:TCNT1L
копируется в регистры ICR1H:ICR1L

    В чем сложность в управлении 3-х пиновым компьютерным вентилятором?
  • Чтобы снять показания с датчика Холла, на него нужно подать питание.
  • Если начать управлять скоростью вентилятора через ШИМ, то питание будет периодически то пропадать, то появляться. Cледовательно часть импульсов с датчика Холла будет теряться, и показания будут занижеными.

Что делать? Справедливости ради замечу, что 4-х пиновые вентиляторы лишены этого недостатка, но сейчас речь не о них. Для 3-х пинового вентилятора можно попробывать сделать "умное" управление.

В 8-см вентиляторе на 3300 RPM, датчик Холла срабатывает 110 раз в секунду. Т.е. он (датчик Холла) работает с периодом в девять миллисекунд. Тогда, если мы на время отключим ШИМ, подадим питание 12 Вольт, замерим длительность одного периода, и затем снова включим ШИМ, то мы получим и управление через ШИМ и достоверный контроль за количеством оборотов. Вентилятор, как и все двигатели, очень инерционные штуки и такой фокус никак не скажется на скорости вентилятора, если его проводить не чаще чем раз в две-три секунд. В остальное время, для простого контроля, чтобы быть уверенным, что вентилятор в принципе крутиться, сгодится подсчет имульсов с датчика Холла и без этого фокуса.

Периоды сигналов в AVR можно измерять с помощью т.н. прерывания по захвату. Основные моменты:

  1. Рабочий пин прерывания - ICP1 (interrupt Capture Pin). Это пин "Digital pin 8" на плате Arduino, он же пин 14 на DIP корпусе микроконтроллера ATmega8.
  2. По сути, это тоже самое, что и внешнее прерывание, но "прикрученное" к 16-битному таймеру. Управляется оно через регистры таймера.
  3. Смысл перывания в том, что при поступлении сигнала на пин ICP, значение счетчика таймера копируется с "регистры захвата". В идеале, обработчик прерыванания по захвату, должен отработать быстрее чем, поступит следующий сигнал на ICP.
  4. Т.к. прерываниие базируется на 16-битном таймере, работа с которым, на 8-битном микроконтроллере, занимает некоторое время, то надеятся на измерение периодов меньших чем 1микросекунда, на 16 МГц кварце, не стоит.
  5. В 16-битном таймере регистры которого составные, имеется особый порядок на чтнение и запись этих регистров, чтобы предотвратить ситуации одновременного доступа и модификации их, несколькими независимыми процессами микроконтроллера.
  6. При записи, пишется сначала старший регистр, затем младший.
  7. При чтении, все наоборот. Сначала читается младший регистр, затем старший.
    Алгоритм работы периодомера такой:
  1. Сначала, обнуляется счетчик таймера, после чего он запускается с режимом захвата.
  2. При срабатывании прерывания, значение таймера заносится в таблицу, после чего счетчик таймера снова обнуляется.
  3. В главном теле программы, раз в секунду печатается таблица с полученными значениями.

Бегло пройдемся по нужным нам регистрам 16-битного Timer1 ATmega8.

Здесь общий для всех таймеров TIMSK, составнной регистр счетчика TCNT1H:TCNT1L, составной регистр прерывания захвата ICR1H:ICR1L и контрольный регистр TCCR1B.

Установкой бита TICIE1 в регистре TIMSK запускается таймер в "захватывающием" режиме.

Таймер имеет два управляющих регистра: TCCR1A и TCCR1B. В данном случае интерес передставляет только последний. В нём битами CS12, CS11, CS10 устанавливается частота таймера. При установке бита ICES1 срабатывание прерывания осуществляется по растущему фронту, иначе - по падающему.

Исходник, который у меня получился:

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

#define ONE_SECOND 32
#define MAX_TAB 10

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

volatile uint16_t tab[MAX_TAB];
volatile uint8_t tab_id;
volatile uint8_t myTimer;

ISR(TIMER1_OVF_vect)
{
        myTimer--;
        if (!myTimer)
        {
                PORTB ^= (1<<PB5);
                myTimer=ONE_SECOND;
        }
};

ISR(TIMER1_CAPT_vect)
{
	if (tab_id<MAX_TAB)
	{
		TCNT1H=0; TCNT1L=0;// clearing cunter
		uint16_t tmp;
		tab[tab_id]=(uint16_t)ICR1L;
		tmp=(uint16_t)ICR1H;
		tmp<<=8;
		tab[tab_id]|=tmp;
	}
	tab_id++;
}

int main(void)
{
	//---- init
	init_uart();
        DDRB |= (1<<PB5); // led indicator

	// init 16-bit timer in capture mode
	TIMSK =(1<<TICIE1)|(1<<TOIE1); //enable capture, overflow interrupt enable
	TCCR1B=(1<<ICES1)|(1<<CS11); // prescaler 1/8
	TCNT1H=0;TCNT1L=0; // clear counter

	for(tab_id=0; tab_id<MAX_TAB; tab_id++) // clearing table of a captured values
		tab[tab_id]=0;
	myTimer=ONE_SECOND;
	tab_id=0;

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

	sei();
	//---- main body
	for (;;)
	{
		_delay_ms(1000);

		if (tab_id>0)
		{
			cli();
			uint8_t k;
			printf("\n");
			for(k=0; k<MAX_TAB; k++)
			{
				if (tab[k] > 0)
					printf("period: %d value: %u\n", k,tab[k]);
				tab[k]=0;
			};
			tab_id=0;
			sei();
		} else
			printf("none values. ");
	};

        return 0;
}

Таймер работает с предделителем 1/8, т.е. при частоте кварца 16 МГц, один замеряемый период будет равен половине микросекунды. При такой частоте, сечетчик таймера будет переполняться 32 раз в секунду.

Прерывание на переполнение, в данном случае, служит для визуального контроля. Через него мигает светодиод, сигнализируя, что таймер работает. При срабатывании прерывания по захвату, сечетчик таймера сбрасывается, поэтому светодиод будет "подтормаживать". При отсутствии захвата, он будет мигать с частотой 1 раз в секунду.

Работу программы можно проверить просто замкнув пин ICP на питание Vcc и получив т.н. дребезг контактов. Примерно так:

Если же подключить вентилятор по схеме из предыдущего поста, поменяв контакт подключения с INT0 на ICP1, то для для 8-см вентилятора будет такая картинка:

Здесь видим ряд цифр начинающихся на 18 тысяч. Т.к. одно значение равно 1/2 микросекунды, получаем одно срабатывание датчика Холла за 9 миллисекунд. Чтобы совершить один оборот, датчику Холла нужно сработать дважды. Т.е. один оборот проходится за 18 миллисекунд. Т.е. 1000/18=55.5 оборотов в секунду. Или 60*1000/18=3333.3 оборота в минуту.

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

Этот проходит оборот уже 40 миллисекунд. Т.е. получаем 60*1000/40=1500 оборотов в минуту.

Скачать полный архив с исходниками можно здесь