ATmega8: ультразвуковой сенсор HC-SR04 на прерывании по захвату(capture interrupt)

разделы: AVR , HC-SR04 , дата: 24 ноября 2015г.

Ультразвуковые дальнометры HC-SR04 черезвычайно популярны в любительской робототехнике из-за своей дешевизны и простоты. Я тоже не остался в стороне, когда баловался с Arduino, теперь же хочу разобрать работу с сенсором на "низком уровне" и напсать пример работы на чистом Си.

Есть один принципальный момент. Часто работу с сенсором HC-SR04 реализуют через внешние прерывания. Но. В ATmega8 их всего два, и если сенсор будет работать на колесном шасси, то внешние прерывания будут заняты колесными энкодерами. Однако, как я говорил в посте про прерывание захвата, оно работает аналогично внешнему, т.е. есть смысл попытаться его задействовать.

Заглянем в datasheet HC-SR04 и посмотрим на протокол работы сенсора:

Здесь видно две линии: управляющую Trig, и Echo работающую на приём.

    Протокол работы сенсора:
  1. Микроконтроллер посылает имульс на Trig длительность 10 мкс.
  2. Сенсор, получив этот импульс, посылает звуковой имульс на частоте 40КГц, и устанавливает уровень Echo в логическую еденицу.
  3. Получив отраженный эхо-сигнал, сенсор сбрасывает Echo в ноль.

т.о. измерив длительность Echo сигнала, получим растояние до препятствия.

    Теперь считаем:
  1. скорость звука 340 м/c. Значит один метр звук преодолевает за 1/340 сек.
  2. 1/340 = 2.941 миллисекунд. Или 2941 микросекунд.
  3. Тогда, один сантиметр звук преодолевает за ~29.4 микросекунд.
  4. Т.к. звук отраженый, то он дважды проходит дистанцию. Значит один сантиметр будет проходиться за вдвое большее время. (29.4*2) = 58.8 мкс.
  5. Если микроконтроллер работает на 16МГц, а таймер с делителем 8, то один тик таймера будет равен 0.5 микросекунд. Следовательно, один сантиметр будет равен ~118 тикам таймера.

В качестве шаблона я использовал программу из поста про прерывание по захвату. Получился такой текст:

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

#define LED PB5
#define TRIG PB1

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

volatile uint16_t  impulse;

ISR(TIMER1_CAPT_vect)
{
	TCNT1H=0;TCNT1L=0;
	uint16_t tmp;
	impulse=(uint16_t)ICR1L;
	tmp=(uint16_t)ICR1H;
	impulse |=(tmp << 8);
	impulse /= 118;
}

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

	// init 16-bit timer in capture mode
	TIMSK =(1<<TICIE1);//enable capture
	TCCR1B=(1<<CS11); // prescaler 1/8
	//--- counter clear
	TCNT1H=0;
	TCNT1L=0;
	impulse=1;
	// --- enable Trig
	DDRB |= (1<<TRIG);

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

	sei();

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

		if (impulse!=0)
		{
			cli();
			printf("impulse: %u cm\n", impulse);

			// clearing counters
	        	TCNT1H=0;
	        	TCNT1L=0;
	        	impulse=0;

			/// start impulse to TRIG
			PORTB |= (1<<TRIG);
			_delay_us(10);
			PORTB &= ~(1<<TRIG);
			sei();
		} else
			printf("none values.\n");
	};

        return 0;
}

По сравнению с оригинальной программой, здесь было убрано прерывание по переполнению счетчика, которое мигало светодиодом. И сброшен бит ICES1, т.е. срабатывание прерывания по захвату идет по низкому уровню, а не по смене фронта. Результат работы программы:

Схема подключения:

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