Измерение расстояний с помощью микроконтроллера AVR и датчика HC-SR04

В этой статье мы рассмотрим схему для измерения расстояний, построенную с использованием ультразвукового датчика HC-SR04 и микроконтроллера ATmega32 (семейство AVR). Датчик HC-SR04 использует технологию под названием “ECHO” (эхо), то есть испускает ультразвуковой сигнал и потом анализирует отраженный от препятствий сигнал.

Измерение расстояний с помощью микроконтроллера AVR и датчика HC-SR04: внешний вид конструкции

Принцип работы ультразвукового датчика HC-SR04

Мы знаем, что звуковые волны не могут проникать через твердые тела. Эти волны распространяются по воздуху со скоростью примерно 220 м/с. Когда они достигают нашего уха, мы слышим их как звук. Но когда эти колебания (звуковые волны) встречают на своем пути твердые тела это равносильно тому, что они встречают на своем пути непроходимую стену, поэтому они отражаются от них с такой же самой скоростью, с которой они распространялись до этого. Этот эффект называется эхо.

Ультразвуковой датчик HC-SR04 обеспечивает на своем выходе сигнал, пропорциональный дистанции до препятствия. Датчик генерирует звуковые колебания в ультразвуковом диапазоне (после получения управляющего импульса) и после этого ждет когда они вернутся к нему (эхо), отразившись от какого-нибудь препятствия. Затем, основываясь на скорости звука (220 м/с) и времени, необходимом для того чтобы эхо достигло источника (нашего датчика), датчик обеспечивает на своем выходе сигнал, пропорциональный расстоянию до препятствия.

Временные диаграммы работы ультразвукового датчика HC-SR04

Как показано на рисунке сначала нам нужно инициировать датчик для измерения расстояний, для этого на его триггерный контакт (trigger pin) необходимо подать логический сигнал высокого уровня длительностью не менее 10 мкс, после этого датчик генерирует серию звуковых колебаний и после получения отраженного сигнала (эхо) датчик обеспечивает на своем выходе сигнал, пропорциональный расстоянию между ним и препятствием.

Расстояние при этом рассчитывается по следующей формуле: distance (in cm) = width of pulse output (in uS) / 58. То есть дистанция (в сантиметрах) равна длительности выходного импульса датчика (в микросекундах), деленная на 58.

Необходимые компоненты

Аппаратное обеспечение

Микроконтроллер ATmega32
Источник питания с напряжением 5 Вольт
Программатор AVR-ISP, USBASP или другой подобный
JHD_162ALCD (ЖК дисплей 16×2)
Конденсатор 1000 мкФ
Резистор 10 кОм (2 шт.)
Датчик HC-SR04

Программное обеспечение

Atmel Studio версии 6.1 (или выше)
Progisp или flash magic (необязательно)

Работа схемы

Схема устройства приведена на следующем рисунке.

Схема измерения расстояний с помощью микроконтроллера AVR и датчика HC-SR04

В представленной схеме PORTB микроконтроллера ATmega32 соединен с портом данным жидкокристаллического (ЖК) дисплея. Если вы не хотите трогать фьюзы (FUSE BITS) микроконтроллера, то не используйте PORTC, поскольку PORTC содержит специальные функции (типы связи), которые можно деактивировать только с помощью изменения фьюзов.

В ЖК дисплее (если мы не хотим использовать черный цвет) можно задействовать только 14 его контактов: 8 контактов для передачи данных (7-14 или D0-D7), 2 контакта для подачи питания (1&2 или VSS&VDD или gnd&+5v), 3-й контакт для управления контрастностью, 3 контакта для управления (RS&RW&E).

В представленной схеме мы использовали только 2 контакта управления ЖК дисплея для лучшего понимания работы схемы. Бит контраста и READ/WRITE используются нечасто, поэтому они могут быть замкнуты на землю. Это обеспечивает ЖК дисплею максимальную контрастность и переводит его в режим чтения. Теперь нам всего лишь нужно контролировать контакты ENABLE и RS чтобы передавать на ЖК дисплей символы и данные. Также на нашем сайте вы можете прочитать более подробную статью о подключении ЖК дисплея к микроконтроллеру AVR ATmega32.

В схеме необходимо сделать следующие соединения с ЖК дисплеем:
PIN1 или VSS — земля
PIN2 или VDD или VCC — +5v питание
PIN3 или VEE — земля (обеспечивает максимальный контраст ЖК дисплею)
PIN4 или RS (Register Selection) – контакт PD6 микроконтроллера
PIN5 или RW (Read/Write) — земля (переводит ЖК дисплей в режим чтения что упрощает взаимодействие с ним для начинающих)
PIN6 или E (Enable) — контакт PD5 микроконтроллера
PIN7 или D0 — контакт PB0 микроконтроллера
PIN8 или D1 — контакт PB1 микроконтроллера
PIN9 или D2 — контакт PB2 микроконтроллера
PIN10 или D3 — контакт PB3 микроконтроллера
PIN11 или D4 — контакт PB4 микроконтроллера
PIN12 или D5 — контакт PB5 микроконтроллера
PIN13 или D6 — контакт PB6 микроконтроллера
PIN14 или D7 — контакт PB7 микроконтроллера

В схеме мы использовали 8-битную связь (D0-D7) ЖК дисплея с микроконтроллером, хотя можно было ограничиться и 4-битной – но в этом случае код программы стал бы немного сложнее. Таким образом, мы использовали 10 контактов ЖК дисплея, 8 из которых будут использоваться для передачи данных и 2 для управления.

Ультразвуковой датчик HC-SR04 имеет 4 контакта:

  • PIN1 — VCC или +5V – контакт для подачи питающего напряжения;
  • PIN2 — TRIGGER – триггерный контакт. На него подается управляющий импульс для запуска работы датчика;
  • PIN3 — ECHO – на его выходе формируется выходной (эхо) сигнал;
  • PIN4 — GROUND – земля.

Контакт 3 (ECHO) датчика подсоединен к микроконтроллеру как источник внешнего прерывания – то есть к контакту INT0 (interrupt 0) или PD2 микроконтроллера.

Теперь, чтобы измерять расстояние с помощью датчика HC-SR04, мы должны выполнить следующую последовательность действий:

  1. Подать на триггерный вход датчика импульс длительностью не менее 12 мкс (с запасом, как говорится).
  2. После того как отраженный сигнал (эхо) вернется к датчику на его выходе ECHO появится сигнал высокого напряжения, которое мы в микроконтроллере должны обработать как внешнее прерывание и запустить счетчик времени.
  3. Как только на выходе ECHO датчика снова появится сигнал низкого напряжения (выходной импульс закончится) мы должны остановить счетчик времени.
  4. Таким образом, во время действия выходного импульса на контакте ECHO датчика счетчик времени микроконтроллера измеряет длительность этого импульса. Потом мы измеренную длительность этого импульса пересчитываем в расстояние.
  5. Измеренное расстояние отображается на ЖК дисплее 16×2.
    Для выполнения этой последовательности действий нам необходимо установить значения в ряде регистров, показанных на рисунке.

Регистр контроля за прерываниями в микроконтроллере AVRРегистр общего разрешения прерываний в микроконтроллере AVRРегистр управления таймером 1 в микроконтроллере AVR

BLUE (синий, INT0 – внешнее прерывание 0): этот бит должен быть установлен в 1 для того чтобы разрешить обработку внешнего прерывания 0 (interrupt0). Если мы сделали это, то после этого мы можем отслеживать изменения логических уровней на контакте D2 микроконтроллера.

BROWN (коричневый, ISC00, ISC01): эти два бита настраиваются для контроля логических уровней на контакте D2 микроконтроллера, который в данной случае рассматривается как источник внешнего прерывания.

То есть, как мы уже говорили ранее, нам необходимо прерывание чтобы запустить наш счетчик времени и потом остановить его. Таким образом мы устанавливаем бит ISC00 в единицу и получаем прерывание когда имеем переход с логического LOW на HIGH на входе INT0, другое прерывание мы получаем когда имеем переход с логического HIGH на LOW.

RED (красный, CS10): это бит используется для того, чтобы активировать и деактивировать счетчик времени. И хотя он работает вместе с другими битами (CS11, CS12), нам они не важны поскольку в данном случае нам не нужно устанавливать предварительный делитель частоты.

Перед началом программирования микроконтроллера необходимо уяснить две важные вещи:

  • мы используем внутренний тактовый генератор микроконтроллера ATMEGA32A, который работает на частоте 1 МГц. Мы не используем здесь предварительный делитель частоты и не выполняем какой либо сложной обработки прерываний, поэтому нам не нужно никакой сложной установки регистров;
  • значение счетчика времени после завершения счета сохраняется в 16 битном регистре TCNT1 микроконтроллера.

Исходный код программы на языке С (Си) с пояснениями

Программа для рассматриваемой схемы представлена следующим фрагментом кода на языке С (Си). Комментарии к коду программу поясняют принцип работы отдельных команд.

#include <avr/io.h> // заголовок чтобы задействовать контроль данных на контактах
#define F_CPU 1000000 // задание тактовой частоты микроконтроллера
#include <util/delay.h> // заголовок чтобы задействовать функции задержки в программе
#define E 5 // задействуем 5-й контакт PORTD (“enable”), поскольку он соединен с контактом “enable” ЖК дисплея
#define RS 6 // задействуем выбор регистра (“registerselection”) на 6-м контакте PORTD, поскольку он соединен с контактом RS ЖК дисплея
void send_a_command(unsigned char command);
void send_a_character(unsigned char character);
void send_a_string(char *string_of_characters);
static volatile int pulse = 0;//integer to access all though the program
static volatile int i = 0;// integer to access all though the program

int main(void)
{
DDRB = 0xFF; // установка portB на вывод данных
DDRD = 00b11111011; // установка 3-го контакта portD (D2) на ввод данных (вход внешнего прерывания 0
_delay_ms(50); //задержка 50ms
DDRA = 00FF; // установка portA на вывод данных.
GICR|=(1<<INT0); // разрешение внешнего прерывания 0 (interrupt0)
MCUCR|=(1<<ISC00); //установка параметров обработки прерывания – как обсуждалось ранее оно будет срабатывать как при переходе с 0 на 1, так и при переходе с 0 на 1
int16_t COUNTA = 0; //переменная для хранения значения цифрового выхода
char SHOWA [3];//переменная для отображения значения расстояния на ЖК дисплее 16*2
send_a_command(0x01); //очистить экран 0x01 = 00000001
_delay_ms(50);
send_a_command(0x38); // сообщаем ЖК дисплею что мы будем использовать 8 битный режим передачи данных/команд
_delay_ms(50);
send_a_command(0b00001111); //включаем курсор и мигание курсора на ЖК дисплее
sei(); // общее разрешение прерываний

while(1)
{
PORTD|=(1<<PIND0);
_delay_us(15);// подаем управляющий импульс длительностью 15 мкс на триггерный вход датчика
PORTD &=~(1<<PIND0);
COUNTA = pulse/58; // рассчитываем расстояние по вышеприведенной формуле
send_a_string («CIRCUIT DIGEST «);//отображаем строку «CIRCUIT DIGEST » на ЖК дисплее

send_a_command(0x80 + 0x40 + 0); // переводим курсор на 1-ю позицию 2-й строки
send_a_string («DISTANCE=»); // отображаем строку «DISTANCE=» на ЖК дисплее

itoa(COUNTA,SHOWA,10); //command for putting variable number in LCD(variable number, in which character to replace, which base is variable(ten here as we are counting number in base10))

send_a_string(SHOWA); //telling the display to show character(replaced by variable number) after positioning the courser on LCD

send_a_string («cm «);
send_a_command(0x80 + 0); //возврат курсора на 1-ю позицию первой строки
}
}
ISR(INT0_vect)// вызов обработки внешнего прерывания INT0 когда на входе прерывания произошло изменение логического уровня
{
if (i==1) //когда логический уровень изменяется с HIGH на LOW
{
TCCR1B=0; // деактивация (останов) счетчика времени
pulse=TCNT1; // значение TCNT1 записывается в переменную pulse
TCNT1=0; //сброс памяти счетчика времени
i=0;
}

if (i==0) // когда логический уровень изменяется с LOW на HIGH
{
TCCR1B|=(1<<CS10); //активация (запуск) счетчика времени
i=1;
}
}

void send_a_command(unsigned char command)
{
PORTA = command;
PORTD &= ~ (1<<RS); // устанавливаем RS в 0 чтобы сообщить ЖК дисплею что мы будем посылать команду
PORTD |= 1<<E; // сообщаем ЖК дисплею чтобы он принял команду/данные
_delay_ms(50);
PORTD &= ~1<<E;//сообщаем ЖК дисплею что мы закончили передачу данных
PORTA= 0;
}

void send_a_character(unsigned char character)
{
PORTA= character;
PORTD |= 1<<RS;//сообщаем ЖК дисплею что мы будем передавать данные (не команду)
PORTD |= 1<<E;//сообщаем ЖК дисплею чтобы он начал прием данных
_delay_ms(50);
PORTD &= ~1<<E;//сообщаем ЖК дисплею что мы закончили передачу данных
PORTA = 0;
}

void send_a_string(char *string_of_characters)
{
while(*string_of_characters > 0)
{
send_a_character(*string_of_characters++);
}
}

Теперь код программы без комментариев.

/*
C Program for Distance Measurement using Ultrasonic Sensor and AVR Microcontroller
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 1000000
#include <util/delay.h>
#include <stdlib.h>
#define enable 5
#define registerselection 6
void send_a_command(unsigned char command);
void send_a_character(unsigned char character);
void send_a_string(char *string_of_characters);
static volatile int pulse = 0;
static volatile int i = 0;
int main(void)
{
DDRA = 0xFF;
DDRB = 0xFF;
DDRD = 0b11111011;
_delay_ms(50);
GICR|=(1<<INT0);
MCUCR|=(1<<ISC00);
TCCR1A = 0;
int16_t COUNTA = 0;
char SHOWA [16];
send_a_command(0x01); //Clear Screen 0x01 = 00000001
_delay_ms(50);
send_a_command(0x38);
_delay_ms(50);
send_a_command(0b00001111);
_delay_ms(50);
sei();

while(1)
{
PORTD|=(1<<PIND0);
_delay_us(15);
PORTD &=~(1<<PIND0);
COUNTA = pulse/58;
send_a_string ("CIRCUIT DIGEST");
send_a_command(0x80 + 0x40 + 0);
send_a_string ("DISTANCE=");
itoa(COUNTA,SHOWA,10);
send_a_string(SHOWA);
send_a_string ("cm ");
send_a_command(0x80 + 0);
}
}
ISR(INT0_vect)
{
if (i==1)
{
TCCR1B=0;
pulse=TCNT1;
TCNT1=0;
i=0;
}
if (i==0)
{
TCCR1B|=(1<<CS10);
i=1;
}
}
void send_a_command(unsigned char command)
{
PORTB = command;
PORTD &= ~ (1<<registerselection);
PORTD |= 1<<enable;
_delay_ms(8);
PORTD &= ~1<<enable;
PORTB = 0;
}
void send_a_character(unsigned char character)
{
PORTB = character;
PORTD |= 1<<registerselection;
PORTD |= 1<<enable;
_delay_ms(8);
PORTD &= ~1<<enable;
PORTB = 0;
}
void send_a_string(char *string_of_characters)
{
while(*string_of_characters > 0)
{
send_a_character(*string_of_characters++);
}
}

Видео, демонстрирующее работу схемы



Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *