Ультразвуковой дальномер

Когда начинаешь самостоятельно что-нибудь изучать, неизбежно возникает уйма вопросов, а необходимая информация в таких же количествах возникать не хочет, особенно если предмет изучения не слишком популярен. Поэтому, считаю необходимым запостить фрагмент моего опыта в копилку муравьиного разума.
В данной статье рассмотрим учебный проект дальномера на основе ультразвукового трансивера HC-SR04, ПЛИС и четырехразрядного индикатора. Для успешного проведения эксперимента необходимо и достаточно раздобыть:
1. Трансивер HC-SR04. Не уверен, что правильно его называю, но и датчиком расстояния назвать не могу, т.к. девайс ничего не считает, а только посылает звуковой импульс и докладывает, что услышал эхо.
VCC, Trig, Echo, GND
2. Конденсатор 4.7 мкф.

3. Подходящая ПЛИС

4. Четырехзначный индикатор

Проект состоит из трех основных блоков. Блок управления трансивером (transceiver) посылает и принимает импульсы, считает время (точнее количество тактов тактового генератора) которое прошло от излучения импульса до попадания его в микрофон. Результат (количество тактов) появляется в виде двоичного числа на выходной шине. Блок преобразования преобразует количество тактов в расстояние выраженное в миллиметрах. Блок управления дисплеем выводит значение расстояния на дисплей.

Текст основного модуля проекта выглядит так:
/*************** ss_ping.v *****************/

module ss_ping (input wire clock,
                input wire echo,
                output wire trigger,
                output wire [7:0]segments,
                output wire [3:0]digits);

wire [20:0] w_time;
wire [13:0] distance;

transceiver TRC1(clock, trigger, echo, w_time);

converter CR1(clock, w_time, distance);

ss4d D1(clock, distance, segments, digits);

endmodule

/*************** ss_ping.v *****************/

Не думаю, что здесь требуются комментарии. Переходим к модулю transceiver:
/*************** transceiver.v *****************/

module transceiver(clock, trigger, echo, distance);

input wire clock;
input wire echo;
output wire trigger;
output wire [20:0] distance;

reg [22:0] clock_reg;
reg [20:0] distance_reg;
reg trigger_reg;

assign distance = distance_reg;
assign trigger = trigger_reg;

always @ (posedge clock)
begin
clock_reg <= clock_reg + 1;
trigger_reg <= (clock_reg < 500) ? 1 : 0;
end

always @ (posedge echo)
begin
distance_reg <= clock_reg[20:0];
end

endmodule

/*************** transceiver.v *****************/

Частота импульсов на выходе trigger зависит от частоты тактового генератора clock и размера регистра clock_reg.

В моем случае (частота генератора 50 МГц и размер регистра 23 бита) получается приблизительно 6 импульсов в секунду. Длительность импульса задается выражением
trigger_reg <= (clock_reg < 500) ? 1 : 0;
. В данном случае длительность импульса составит 10 мкс. В принципе она может быть другой, потому, что сигнал на входе Trig трансивера HC‑SR04 не задает продолжительность звукового импульса, а служит только для его запуска. Размер регистра distance_reg в принципе избыточен. Его достаточно, чтобы измерять расстояния до 7 метров. Производитель трансивера вообще указывает диапазон измерения до 600 мм.
При подключении тарнсивера нас ждет небольшая засада. Если вам доводилось подключать HC-SR04 к плате Ардуино, то проблем вы скорее всего не испытывали, соединили две железки, написали программу и все заработало. Нужные библиотеки уже написаны и доступны для скачивания. В нашем варианте все немного сложнее. Если не соединить выход Echo трансивера с GND через тот самый конденсатор, то ничего не заработает. Судя по всему, сигнал, который появляется на выходе Echo не особо похож на прямоугольный импульс и схема на него не реагирует.
С отпрвкой и получением ультразвуковых импульсов разобрались, сколько тактов проходит от момента отправки импульса до получения эхо-сигнала знаем. Теперь наша задача — преобразовать количество тактов в расстояние. Этим у нас занимается модуль converter.
/****************** converter.v ******************/

module converter(clock, pulses, distance);

input wire clock;
input wire [20:0] pulses;
output wire [13:0] distance;

reg [13:0] distance_reg;

assign distance = distance_reg;

always @ (posedge clock)
begin
distance_reg <= (pulses*331)/(100000); //(pulses*331000)/(2*50000000);
end

endmodule

/****************** converter.v ******************/

Конвертер работает просто. Берет число тактов, умножает на коэффициент, выдает результат. 331000 — скорость звука в мм/с. Делим на два потому, что звук проходит измеряемое расстояние дважды. 50000000 — это частота нашего тактового генератора в Гц. Седьмой, что ли, класс средней школы.
Наконец у нас есть расстояние в миллиметрах, но оно у нас в виде двоичного числа. Как его вывести на семисегментный четырехразрядный дисплей? Готовых решений под мою задачу не нашлось. Сначала я хотел схитрить и подсунуть синтезатору Quartus-а таблицу истинности на 10000 вариантов, т.к. сгенерировать ее можно пятью строчками кода на любом ЯП. Но Quartus, зараза, вроде и не ругался, но и выдавал какую-то нерабочую фигню. Пришлось включать мозг. Для вывода десятичных чисел на индикатор их нужно преобразовать в двоично-десятичный код. А так как у нас четыре разряда, то нам нужно разложить двоичное число на четыре двоично-десятичных. Этим у нас занимается модуль b2b_4d.
/******************* b2b_4dt.v *******************/

module b2b_4dt(input wire [13:0]BIN,
               input wire CLK,
               // output wire OOR,
               output wire [3:0]DOUT0,
               output wire [3:0]DOUT1,
               output wire [3:0]DOUT2,
               output wire [3:0]DOUT3);

wire [9:0] BIN1;
wire [6:0] BIN2;
wire [3:0] BIN3;

reg [3:0]D0;
reg [3:0]D1;
reg [3:0]D2;
reg [3:0]D3;

//reg OOR_reg;

//assign OOR = OOR_reg;

assign DOUT1 = D1;
assign DOUT2 = D2;
assign DOUT3 = D3;

assign BIN1 = BIN - D3*1000;
assign BIN2 = BIN1 - D2*100;
assign BIN3 = BIN2 -  D1*10;
assign DOUT0 = BIN3;

always @(posedge CLK)
    begin
    //OOR_reg <= (BIN > 9999) ? 1 : 0;
    if ((BIN <= 9999) && (BIN > 8999)) begin D3 <= 4'b1001; end
    if ((BIN <  9000) && (BIN > 7999)) begin D3 <= 4'b1000; end
    if ((BIN <  8000) && (BIN > 6999)) begin D3 <= 4'b0111; end
    if ((BIN <  7000) && (BIN > 5999)) begin D3 <= 4'b0110; end
    if ((BIN <  6000) && (BIN > 4999)) begin D3 <= 4'b0101; end
    if ((BIN <  5000) && (BIN > 3999)) begin D3 <= 4'b0100; end
    if ((BIN <  4000) && (BIN > 2999)) begin D3 <= 4'b0011; end
    if ((BIN <  3000) && (BIN > 1999)) begin D3 <= 4'b0010; end
    if ((BIN <  2000) && (BIN >  999)) begin D3 <= 4'b0001; end
    if  (BIN <  1000)                  begin D3 <= 4'b0000; end
    
    if ((BIN1 <= 999) && (BIN1 > 899)) begin D2 <= 4'b1001; end
    if ((BIN1 <  900) && (BIN1 > 799)) begin D2 <= 4'b1000; end
    if ((BIN1 <  800) && (BIN1 > 699)) begin D2 <= 4'b0111; end
    if ((BIN1 <  700) && (BIN1 > 599)) begin D2 <= 4'b0110; end
    if ((BIN1 <  600) && (BIN1 > 499)) begin D2 <= 4'b0101; end
    if ((BIN1 <  500) && (BIN1 > 399)) begin D2 <= 4'b0100; end
    if ((BIN1 <  400) && (BIN1 > 299)) begin D2 <= 4'b0011; end
    if ((BIN1 <  300) && (BIN1 > 199)) begin D2 <= 4'b0010; end
    if ((BIN1 <  200) && (BIN1 >  99)) begin D2 <= 4'b0001; end
    if  (BIN1 <  100)                  begin D2 <= 4'b0000; end
    
    if ((BIN2 <= 99) && (BIN2 > 89)) begin D1 <= 4'b1001; end
    if ((BIN2 <  90) && (BIN2 > 79)) begin D1 <= 4'b1000; end
    if ((BIN2 <  80) && (BIN2 > 69)) begin D1 <= 4'b0111; end
    if ((BIN2 <  70) && (BIN2 > 59)) begin D1 <= 4'b0110; end
    if ((BIN2 <  60) && (BIN2 > 49)) begin D1 <= 4'b0101; end
    if ((BIN2 <  50) && (BIN2 > 39)) begin D1 <= 4'b0100; end
    if ((BIN2 <  40) && (BIN2 > 29)) begin D1 <= 4'b0011; end
    if ((BIN2 <  30) && (BIN2 > 19)) begin D1 <= 4'b0010; end
    if ((BIN2 <  20) && (BIN2 >  9)) begin D1 <= 4'b0001; end
    if  (BIN2 <  10)                 begin D1 <= 4'b0000; end
    end
endmodule

/******************* b2b_4dt.v *******************/

Включение сегментов индикатора в соответствии с полученным на вход числом — не сложная задача. В одной из статей на сайте должно быть ее решение на VHDL. В данном проекте включает лампочки ss_converter.
/******************* ss_converter.v *******************/

module ss_converter(input wire [3:0]DATA,
                    input wire CLOCK,
                    output wire [7:0]DOUT);

reg [7:0] dout_reg;
assign DOUT = dout_reg;

always @(posedge CLOCK)
    case (DATA)
    //                            76543210
    4'b0000: begin dout_reg <= 8'b11000000; end // 0
    4'b0001: begin dout_reg <= 8'b11111001; end // 1
    4'b0010: begin dout_reg <= 8'b10100100; end // 2
    4'b0011: begin dout_reg <= 8'b10110000; end // 3
    4'b0100: begin dout_reg <= 8'b10011001; end // 4
    4'b0101: begin dout_reg <= 8'b10010010; end // 5
    4'b0110: begin dout_reg <= 8'b10000010; end // 6
    4'b0111: begin dout_reg <= 8'b11111000; end // 7
    4'b1000: begin dout_reg <= 8'b10000000; end // 8
    4'b1001: begin dout_reg <= 8'b10010000; end // 9
    default: begin dout_reg <= 8'b11000000; end
    endcase
endmodule

/******************* ss_converter.v *******************/

Полседнее, что осталось сделать — вывести информацию на индикатор. Работает он следующим образом. Выводы D0..D7 подключены к соответствующим сегментам на всех четырех разрядах. Выводы Sel_n0..Sel_n3 управляют подачей питания на соответствующий разряд. Чтобы вывести на индикатор число, необходимо циклично включать разряды с достаточно высокой частотой. Отображением информации на индикаторе управляет модуль ss4d.

/******************* ss4d.v *******************/

module ss4d (input wire CLK,
             input wire [13:0]DATA,
             output wire [7:0]DOUT,
             output wire [3:0]DNR);

reg [14:0] f_reg;
reg         fclk;
reg  [1:0] s_reg;

wire [3:0] cr_bus0;
wire [3:0] cr_bus1;
wire [3:0] cr_bus2;
wire [3:0] cr_bus3;

wire [7:0] out_bus0;
wire [7:0] out_bus1;
wire [7:0] out_bus2;
wire [7:0] out_bus3;


reg [7:0] dout_reg;
reg [3:0] dnr_reg;
assign DOUT = dout_reg;
assign DNR = dnr_reg;

b2b_4dt DR1(DATA,
            CLK,
				cr_bus0,
				cr_bus1,
				cr_bus2,
				cr_bus3);

ss_converter CR0(cr_bus0, CLK, out_bus0);
ss_converter CR1(cr_bus1, CLK, out_bus1);
ss_converter CR2(cr_bus2, CLK, out_bus2);
ss_converter CR3(cr_bus3, CLK, out_bus3);

always @ (posedge CLK)
begin
f_reg <= f_reg + 1;
if (f_reg == 0) begin fclk <= (fclk == 0) ? 1 : 0; end
end

always @ (posedge fclk)
begin
case (s_reg)
    2'b00: begin dout_reg <= out_bus3; dnr_reg <= 4'b1110; end
    2'b01: begin dout_reg <= out_bus2; dnr_reg <= 4'b1101; end
    2'b10: begin dout_reg <= out_bus1; dnr_reg <= 4'b1011; end
    2'b11: begin dout_reg <= out_bus0; dnr_reg <= 4'b0111; end
endcase
s_reg = s_reg + 1;
end

endmodule

/******************* ss4d.v *******************/

Модуль написан не совсем правильно. Одновременно работает только одна цифра, а ss_converter-ов зачем-то четыре. Что ж. Есть повод поупражняться в оптимизации.
  • +3
  • 14 июня 2011, 23:26
  • digides

Комментарии (38)

RSS свернуть / развернуть
… поставь после введения тег «cut»
0
++. Засуньте эту простыню под кат.
0
Производитель трансивера вообще указывает диапазон измерения до 600 мм.
60см? Это максимальное? Уныло как-то :(

А вообще предлагаю перенести пост в блог про ПЛИС.
0
На самом деле, не все так печально. До 1.2 м работает стабильно. Дальше, похоже, луч становится слишком широким, ловит сразу несколько поверхностей и показания начинают «прыгать». Но если «посветить» в потолок (≈2.5м). Посторонних предметов нет и показания опять стабильные.
0
… а какие результаты?
331000 — скорость звука в мм/с
… не забывай что это воздух
0
в вашем коде есть одна не очень хорошая вещь — использование 2х источников тактовой частоты. Наверняка Quartus дает критическое сообщение. Смотрите clock_reg меняется с частотой clock, а чтение из него производится с ассинхронной тактовой частотой echo. Использование двух частот — это потенциальное место для метастабильности (это когда схема вроде и работает, но непонятно что происходит и иногда происходит сбой).
было бы лучше использовать один clock, с его помощью выделить фронт сигнала echo и уже тогда читать.

reg prev_echo;
always @(posedge clock)
prev_echo<=echo;

always @ (posedge clock)
if(prev_echo==1'b0 && echo==1'b1)
distance_reg <= clock_reg[20:0];

как-то так…
0
еще одна вещь меня просто поразила:
distance_reg <= (pulses*331)/(100000);
Интересно, сколько эта функция заняла у Вас логических элементов? Тысячу? Имхо можно как-то полегче и проще сделать…
Да вот кстати была статья про дальномер marsohod.org/index.php/projects/159-sonar
0
Я пока мало знаю про оптимизацию электронных схем. Единственное что пришло в данном случае в голову — раскладывать числа на множители и сокращать.

Жаль, что пропустил эту страницу с сайта «Марсохода». Не пришлось бы искать информацию о работе HC-SR04 на буржуйских сайтах. При чем, такого подробного описания все равно не нашел.
0
Значение скорости звука взял первое попавшееся (из страницы википедии) и не самое удачное.
0
Альтернативный метод борьбы с метастабтльностью — пропустить сигнал через пару триггеров:

reg echo_;
reg echo__;
always @(posedge clock)
 echo_<=echo;
always @(negedge clock)
 echo__<=echo_;

и работать дальше уже с отфильтрованным сигналом

transceiver TRC1(clock, trigger, echo__, w_time);
0
Кстати, на тему конденсатора — наверняка он понадобится именно из-за метастабильности. Если изменить код одним из приведённых выше способов, то кондёр можно будет убрать.
0
Для проекта достаточно мк51 1МIPS.
0
Согласен. Но этот дальномер экспериментальный. Proof of concept. Он не обязан быть практичным.
0
Встречал данные, что в подобных устройствах необходимо еще вводить температурную компенсацию. при нормальном атмосферном давлении при 15 ° С скорость звука около 340 м/с, и на каждый дополнительный градус увеличивается на 0.607м/с При 35 ° C летом, скорость звука 352m / с, а зимой при -20° С, скорость звука 319м/с.
0
Это смотря какая точность нужна.
Если по уму тогда надо делать и поправку на атмосферное давление, скорости и направления ветра, а можно еще и состав воздуха анализировать.
0
В моем мастековском дальномере датчик температуры стоит, а вот на остальное китайцы забили. Видимо, она или наиболее существенна, или проще измеряется)
0
Я бы проще поступал, сервиком выставлять (выдвигал) перед датчиком скажем на расстоянии 10 см преграду, делать контрольный замер и это будет поправочный коэффициент для последующих измерений.
0
Во-первых, на 10см у них вроде как точность страдает. Во-вторых замерить Т проще, чем ставить туда хитрую механику :)
0
))) да я к примеру сказал, как из вариантов. К тому же это не так сложно как кажется.
0
Мой менее 60см (по ману, реально 55) вообще не видит. Ну и от калибровки по такому расстоянию толку не так много — оно не учтет ни ветер, ни неравномерность среды на протяжении луча, а температуру проще так померить. Насчет давления хз.
0
Да забейте, мне кажется что при таких расстояниях погрешность будет невелика, можно действительно просто использовать температуру.
0
Он кстати вроде не предназначен для использования на открытом пространстве. Да и вообще, штучка довольно ограниченная и разрешение низкое. Единственный плюс — на порядок дешевле лазерника.
0
Я задумывался сделать датчик слепой зоны в авто как в рекламе. Просто перед сном думал. На чем интересно более реально сделать и чтобы с точки зрения компановки и установки.
Эту фиговану в зеркало заднего вида толком не вставишь. Идеи есть?
0
Я даже не знаю, что такое «датчик слепой зоны» :)
0
Машину водишь? Слепая зона это зона в которой авто едущее рядом с тобой не видно в боковом зеркале.
0
Нет, в том-то и дело :) И я не понял, какую фиговину «в зеркало толком не вставишь».
0
Ту про которую здесь идет речь — дальномер.
0
А зачем его вставлять в зеркало? Оно же вроде в салоне.
0
Признаться я не знаю где он стоит, я про тот который слепую зону проверяет. Но направлен он вдоль кузова от зеркала с горизонтальной направленностью градусов 40-60. Но там определять дальность не особо требуется, надо лишь зафиксировать, что есть объект.
0
А я не знаю, где слепая зона) Но я бы расставлял датчики от парктроника, электронику свою (или вообще парктроник и поставить).
А еще некоторые камеру туда смотреть ставят.
0
Камера неудобно, отвлекает. Надо просто чтобы светодиод помигивал при помехе.
Я думал про обычный парктроник за 13 баксов, но опять же, куда датчик вкрутить)) В дверь чтоли, хотя как вариант — в молдинг попробовать.
0
0
Чтобы получить хорошую точность, все это не помешает, но данный проект довольно примитивный и ему эти дополнительные настройки могут и не помочь. Погрешность измерений большая, и она, скорее всего, перекрывает поправки на температуру и давление.
0
Простите мою наивность, но чтоже все-таки являются входными и выходными сигналами самого модуля дальномера? Что подавать на ножку рядом с питанием, и что ожидать на выходе Echo?
0
На вход подаем единицу, и ждем когда она появится на Эхе?
0
У датчика «Trig» — это вход, а «Echo» — выход. На вход подается импульс. У меня продолжительность импульса выставлена достаточно небольшая — 10 мксек, а значение из даташита было то ли 1, то ли 10 мсек. Я, ради интереса, перебрал несколько вариантов в меньшую сторону. Оказалось, что и на 10 мксек уверенно срабатывает. На «Echo» появляется сигнал, когда датчик поймает эхо.
Ну, а у управляющей схемы «echo» — вход, а «trigger» — выход.
0
Не знаю какой у вас даташит, а в моем сказано про 500 сантиметров.
При отсутствии объектов, сенсор должен выдавать на echo импульс длиной 38 или 40 миллисекунд. У меня выдавалось значение около 200 000. Подобного в интернете не нашел, возможно мне попались бракованные сенсоры (хотя их 3 штуки, работают одинаково).

Оказалось, при питании от usb получается не более 60 сантиметров. Но когда подключил в качестве питания Крону (через регулятор на 5 В), дальность сонара сразу возросла до 3 метров 70 сантиметров. У меня под рукой был регулятор только на 1А. Похоже модулю надо около 2 А (видел у какого-то подобного сонара эту цифру).
0
Вообще расстроен, что взял эти модули. Подкупила меньшая цена, большая дальность указанная в даташите, и более компактные размеры.
Раньше использовал TS601-01. Тут дальность 3 м. 30 см. но работает очень стабильно, даже будучи направленным под достаточно большим углом к измеряемой поверхности.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.