ModBus RTU (RS-485)

pvvx

Активный участник сообщества
У кого есть уже готовый софт.драйвер на ESP8266 для работы с внешним драйвером шины RS-485, учитывающим время переключение шины на прием/передачу и прочие правильные межпакетные паузы?

Основные проблемы:
При 115200 у нас время вывода одного символа по RS-232/485 (если задано 10 бит: 1 старт + 8 дата + 1 стоп) составляет 10/115200=0.000087 сек = 87 мкс. На такую паузу стандартный таймер может не отрабатывать в SDK, т.к. множество процедур исполняются дольше (не отдают управление в ets_run()). Возможно использовать аппаратный таймер TIMER0 c включением на NMI вектор. Но тогда и UART надо переопределять на NMI вектор...
Modbus RS-485 требует вычисления межсимвольных интервалов в 1.5 и 4.5 времени вывода символа, а так-же задержек, связанных с "устаканиваем" эха и прочих коммутационных помех в процессе переключения шины на ввод и вывод (зависят от драйвера шины RS-485)...

Делаю проект типа "сетевой бридж Modbus TCP" c установками по WEB HTTP (и по порту modbus TCP со спец. адресом устройства). В дальнейшем предполагающий расширение на работу ESP8266 как простого ПЛК c панелью оператора по HTTP...

Гнать халтуру, типа как в Ардуино и прочих "народных" проектах охоты нет. :) Т.к. уже достигнута определенная стабильность сборки LwIP для спец. Web + кусков SDK (примерно как тут - meSDK, если использовать правильно...).

PS: Открытость исходников проекта будет определятся наличием предоставления информации для него другими, в данной теме.
 
Последнее редактирование:

Tomahawk

New member
В коде программы нужно сделать расчёт интервала тишины, и при изменении скорости менять и его. В Modbus, к слову, скорость по умолчанию 19200. В интернете расчёт тишины делается по-разному, поэтому для начала нужно определиться что такое "символ" и не путаться.

Посчитаем время передачи 1 бита на заданной скорости, например так:
Код:
    int k;
    //время передачи одного бита в мкс = 1000000 мкс / скорость связи = полученные цифры ниже
    switch (UART_speed) {
        case 1200:
            k = 833;    break;
        case 2400:
            k = 416;    break;
        case 4800:
            k = 208;    break;
        case 9600:
            k = 104;    break;
        case 19200:
            k = 52;    break;
        case 38400:
            k = 26;    break;
        case 57600:
            k = 17;    break;
        case 115200:
            k = 8;    break;
        case 230400:
            k = 4;    break;
        case 460800:
            k = 2;    break;
        case 921600:
            k = 1;    break;
        default:
            k = 52;
            break;
    }
    k += 1;         //т.к. не учли остаток от деления и нам требуется "не менее" интервала тишины
    k = k * 11;    //время передачи "символа" (или кадра по-другому)
Далее получаем интервал тишины, умножая этот коэффициент на 3,5 символа и на 1,5 символа, чтобы получить максимальную паузу при передаче кадра.
 
Последнее редактирование:

sharikov

Active member
В коде программы нужно сделать расчёт интервала тишины, и при изменении скорости менять и его. В Modbus, к слову, скорость по умолчанию 19200. В интернете расчёт тишины делается по-разному...
Что-то ваш расчет странный.
Во-первых следует использовать только документы от первоисточника modbsu.org
В Modbus_over_serial_line_V1_02 прописано:
для RTU режима всегда передается 11 бит, использование четности рекомендуется если идет работа без четности передается 2 стоп бита чтобы формат всегда был 11 бит.
т.е время межкадровой паузы рассчитывается как
1/19200*11*3,5 = 2мс (почти ровно)
Для битрейтов выше 19200 всегда используется фиксированное значение таймаутов: 750us для T1,5 и 1.75ms для T3.5

На практике интервал T1,5 не проверяют: много оборудования его при передаче не соблюдает.
 

Tomahawk

New member
sharikov, обращаемся к первоисточнику по вашему совету (Modbus_over_serial_line_V1_02). Символ там называется по-английски character. Видим ту же самую фразу
2.5.1 RTU Transmission Mode When devices communicate on a MODBUS serial line using the RTU (Remote Terminal Unit) mode, each 8–bit byte in a message contains two 4–bit hexadecimal characters. The main advantage of this mode is that its greater character density allows better data throughput than ASCII mode for the same baud rate. Each message must be transmitted in a continuous stream of characters.
Перевод:
"Когда устройства общаются по MODBUS последовательной линии с помощью удаленного устройства (терминала) в режиме RTU, каждый 8-битный байт в сообщении содержит два 4-разрядных шестнадцатеричных символа. Основным преимуществом этого способа является то, что он позволяет достигнуть большей плотности символов и повысить пропускную способность, чем в режиме ASCII для той же скорости. Каждое сообщение должно быть передано в виде непрерывного потока символов."

Отсюда вопрос на засыпку: сколько бит в одном символе? Чтобы нам на 3,5 умножить.
 
Последнее редактирование:

sharikov

Active member
Отсюда вопрос на засыпку: сколько бит в одном символе? Чтобы нам на 3,5 умножить. Сейчас же вы берёте размер кадра и пытаетесь от него высчитать кол-во бит в символе.
Все написано на страницах 12-13 в разделе 2.5.1.
Для режима RTU 11 бит умножаем на 3,5. Но поскольку в стандарте задан таймаут от окончания до начала следующего а уарт выдает прерывания по окончанию нужно прибавить еще 1 - т.е 2,5 и 4,5.

По реализации:
На прием есть аппаратный детектор rx time-out с прерыванием. Если это работает проблем с приемом нет.
С передачей плохо. В uart не предусмотрен бит "transmitter idle". По биту fifo empty невозможно определить момент окончания передачи стоп битов последнего байта чтобы начать отсчет таймаута на выключение передатчика rs485.
Можно при передаче попробовать включать режим loopback и использовать прерывание rx time-out.
 

Tomahawk

New member
Соглашусь с вами, получается всё-таки 11 бит, они считают полным символом весь кадр. "If No Parity is implemented, an additional stop bit is transmitted to fill out the character frame to a full 11-bit asynchronous character:". Значит разобрались, кадр в RTU всегда 11 бит и это "символ".
 

pvvx

Активный участник сообщества
Бит в передавемом "символе" столько, сколько задано установками четности и кол-ва стоп битов. Интервал выражен в кол-ве символов.Что можно что-то там на высоких скоростях не считать - это "можно" и на стандарт не катит. Ляп в Modbus RTU и так пачка - не стандартизирована реакция на описанные задержки. Задержки описаны, а что делать по ним - нет :) Горе стандарт.
В 80% случаев, сторонние драйвера не соблюдают интервалов и протокол Modbus RTU RS-485 периодически рушится. Приходится ставить в ПО галку - китай-Mobus RTU. :)
Проанализировано большинство пром. контроллеров продающихся в России. 90% не соблюдают стандарт (имеющуюся часть). :) :)
Несколько лет назад я перестал создавать драйверы с полным вычислением задержек Modbus RTU, т.к. требуется чтобы работало, а если их поддерживать, то стороннее оборудование лагает. Modbus вообще ужасный протокол - гарантии доставки пакета в нем нет (т.е. узнать какие ошибки произошли за время передачи пакета...). Узнать что сообщение достигло устройства невозможно (берем фрейм с адресом нуль - всем :) ). Кое как работает только по TCP.
Основная причина не расчетов пауз в 1.5 символа - используемые fifo у UART.
При передаче и приему символа в UART на массе контроллеров не узнать, принялся (последний бит/полубит стопа) или передается ещё символ, т.к. fifo то уже пусто...
В итоге требуется расчет задержки в несколько передаваемых бит, после отработки прерывания приема или передачи символа в UART. Многие контролеры лажают на переключении направления шины, если используется 2-х проводный RS-485... то рано, то поздно переключают...
По этому из стандарта остается - делаем паузу заведомо больше и передаем, после передачи сразу принимает всё подряд, а разбор ведем по совмещению формата и контрольки, пока не будет громадной паузы :) Так "выродился" Modbus RTU и по такому алго работает со всем барахлом, т.к. сообшения ошибок ныне вообще никто из устройтв не воспринимает, а сразу считает что всё - всё сломано :)
Если передаем сообщения что устройство не готово или ещё чего из стандартного сообщения об ошибках, то это вызывает большую красную надпись на всех китайских "Панелей управления" и часто сбой работы его драйвера Modbus :)
Как пример - одни из самых дешевых панелей оператора Samkoon с бесплатной средой программирования (макросы описываются на СИ !) :)
 
Последнее редактирование:

sharikov

Active member
Бит в передавемом "символе" столько, сколько задано установками четности и кол-ва стоп битов. Интервал выражен в кол-ве символов.Что можно что-то там на высоких скоростях не считать - это "можно" и на стандарт не катит. Ляп в Modbus RTU и так пачка - не стандартизирована реакция на описанные задержки. Задержки описаны, а что делать по ним - нет :) Горе стандарт.
В 80% случаев, сторонние драйвера не соблюдают интервалов и протокол Modbus RTU RS-485 периодически рушится. Приходится ставить в ПО галку - китай-Mobus RTU. :)
В стандарте однозначно написано что в режиме rtu битов всегда 11 без вариантов. Если иное - это что угодно но не modbus-rtu.
Вопрос что хочется сделать. Реально нужен переходник modbus TCP - RTU. Да, чтобы работало придется немного отойти от спецификации.
Считаю что пытаться делать переходник "китай-фигни"-в-"китай-хрень" бессмысленно потому что такая задача не формализуется. Сделать продукт для неформализованной задачи невозможно.
Стандарт modbus действительно ублюдочен особенно rtu.

По этому из стандарта остается - делаем паузу заведомо больше и передаем, после передачи сразу принимает всё подряд, а разбор ведем по совмещению формата и контрольки, пока не будет громадной паузы :) Так "выродился" Modbus RTU и по такому алго работает со всем барахлом,
Описанный алгоритм рушит помехозащищенность потому что правильное выдерживание паузы после передачи (и ее проверка при приеме) имеет критическое значение в линии с помехами. modbus как раз и применяют в промышленности где с помехами всегда плохо.

Вопрос:
В новых sdk какая максимальная задержка до входа в обработчик прерываний (например uart) при высокой активности wifi ?
Не более 1мс получится без использования nmi ?
 
Последнее редактирование:

pvvx

Активный участник сообщества
Не более 1мс получится без использования nmi ?
Даже процедура выделения памяти использует запрет прерываний на всё время поиска свободного блока :) В новых SDK, с версии 1.0.0 таблицу прерываний CPU китайцы и вынесли в IRAM, чтобы дополнить обработчиком NMI для своего софт-PWM.
Так что рассчитывать, что не возникает задержек более 1 ms на стандартном (ранее использованном векторе) не приходиться. А там и сама UART. Перенос прерывания UART на NMI ещё не вентилировал, но даже нет примера в дизасм коде SDK. Есть только перенаправление от таймера (NmiTimSetFunc() и сам ужасно длинный по времени исполнения код NMI_Handler()) .

https://github.com/pvvx/esp8266web/blob/master/info/libs/main/nmi.c

Для скорости передачи выше 19200 бит, следует использовать фиксированные значения для таймеров: рекомендуется использовать значение 750μs для меж-символьного тайм-аута (T1.5) и 1.750ms для меж-кадровой задержки (Т3.5).
http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf

Так что не 1ms, а 750μs.
А про кол-во битов расскажите производителям фигней у которых написано - RS-485-ModbusRTU. Придется поддерживать все варианты (с битом четности и без, с 1 и 1.5 и 2 т 2.5 стопов = 10..12.5 бит :) )
 
Последнее редактирование:

sharikov

Active member
В новых SDK, с версии 1.0.0 таблицу прерываний CPU китайцы и вынесли в IRAM, чтобы дополнить обработчиком NMI для своего софт-PWM.
Так что рассчитывать, что не возникает задержек более 1 ms на стандартном (ранее использованном векторе) не приходиться. А там и сама UART. Перенос прерывания UART на NMI ещё не вентилировал, но даже нет примера в дизасм коде SDK.
Здесь перенос прерываний на nmi вентилировали
http://www.esp8266.com/viewtopic.php?f=9&t=3979&start=20#p25153
Свой обработчик в nmi вставить можно но наблюдается подземный стук от wifi.
И обработчик должен быть быстрым а uart висит на тормозной шине и обращения к нему выполняются аццки медленно.

Для скорости передачи выше 19200 бит, следует использовать фиксированные значения для таймеров: рекомендуется использовать значение 750μs для меж-символьного тайм-аута (T1.5) и 1.750ms для меж-кадровой задержки (Т3.5).
http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf

Так что не 1ms, а 750μs.
А про кол-во битов расскажите производителям фигней у которых написано - RS-485-ModbusRTU. Придется поддерживать все варианты (с битом четности и без, с 1 и 1.5 и 2 т 2.5 стопов = 10..12.5 бит :) )
Урежьте хотелки. 2,5 бит uart не поддерживает судя по доке которая на этом сайте.
T1,5 на приеме не проверять, поэтому про него помним только при передаче чтобы не делать межсимвольных пауз.
T3,5 необходима. Причем в начале кадра задержку можно безболезненно удлиннять а в конце ее нужно гарантировано выдерживать с нормируемой погрешностью.
1мс это по прикидкам максимум на сколько допустимо удлиннить задержку в конце кадра. Если больше - могут быть потери ответов когда слэйв быстро отвечает.
Случай когда слэйв отвечает мгновенно не ожидая паузу в конце запроса и не делая паузу в начале ответа мы не рассматриваем потому что такие устройства заведомо не соответствуют спецификации RTU.
 

pvvx

Активный участник сообщества
Здесь перенос прерываний на nmi вентилировали
http://www.esp8266.com/viewtopic.php?f=9&t=3979&start=20#p25153
Свой обработчик в nmi вставить можно но наблюдается подземный стук от wifi.
И обработчик должен быть быстрым а uart висит на тормозной шине и обращения к нему выполняются аццки медленно.
Я счас с этим вожусь (строю спец. тест). Ничего плохого пока не вижу, а в указанной писанине, как и тут :) , пока ничего дельного. Всё это давно разобрано...
Тест требуется для выявления фактических событий срабатывания прерываний от UART (точный момент, до привязки к биту rx/tx) и возможностей переключения конфигов UART на ходу передачи/приема и т.д.
А 'ацки' медленная шина позволяет работать с UART до ~ 26 000 000 * 11/ 2 = 143 000 000 baud в полном дуплексе. Это безусловно очень медленно :)

По NMI:
Вот эту фигню из обработчика nmi из SDK
Код:
 disasm 
.text:40100020
_NMIExceptionVector:
    wsr             0xD3, a0
    call0           _NMILevelVector


a_nmi_store_regs .int nmi_store_regs    
a__Pri_3_HandlerAddress .int _Pri_3_HandlerAddress
a_NMI_Handler   .int NMI_Handler        

; =============== S U B R O U T I N E =======================================
.text:4010009C
_NMILevelVector:                        ; CODE XREF: .text:40100023p
                l32r            a0, a_nmi_store_regs
                s32i.n          a2, a0, 0x28
                l32r            a2, a__Pri_3_HandlerAddress
                s32i.n          a1, a0, 0x24
                l32i.n          a2, a2, 0
                s32i.n          a3, a0, 0x2C
                xsr             0xD3, a2 ; EXCSAVE_3
                s32i.n          a4, a0, 0x30
                s32i.n          a2, a0, 0x20
                rsr.epc1        a3
                rsr.exccause    a4
                s32i.n          a3, a0, 0xC
                s32i.n          a4, a0, 0x10
                rsr.excvaddr    a3
                s32i.n          a3, a0, 0x14
                rsr             0xD1, a4 ; EXCSAVE_1
                s32i.n          a4, a0, 0x18
                s32i.n          a5, a0, 0x34
                s32i.n          a6, a0, 0x38
                s32i.n          a7, a0, 0x3C
                s32i            a8, a0, 0x40
                s32i            a9, a0, 0x44
                s32i            a10, a0, 0x48
                s32i            a11, a0, 0x4C
                s32i            a12, a0, 0x50
                s32i            a13, a0, 0x54
                s32i            a14, a0, 0x58
                s32i            a15, a0, 0x5C
                l32r            a1, a_nmi_store_regs
                movi.n          a0, 0
                movi.n          a2, 0x23
                wsr.ps          a2
                rsync
                rsr.sar         a14
                s32i.n          a14, a1, 0
                l32r            a13, a_NMI_Handler
                callx0          a13
                l32i.n          a15, a1, 0
                wsr.sar         a15
                movi.n          a2, 0x33
                wsr.ps          a2
                rsync
                l32i.n          a4, a1, 0x30
                l32i.n          a5, a1, 0x34
                l32i.n          a6, a1, 0x38
                l32i.n          a7, a1, 0x3C
                l32i            a8, a1, 0x40
                l32i            a9, a1, 0x44
                l32i            a10, a1, 0x48
                l32i            a11, a1, 0x4C
                l32i            a12, a1, 0x50
                l32i            a13, a1, 0x54
                l32i            a14, a1, 0x58
                l32i            a15, a1, 0x5C
                l32i.n          a2, a1, 0xC
                l32i.n          a3, a1, 0x10
                wsr.epc1        a2
                wsr             0xE8, a3 ; EXCCAUSE
                l32i.n          a2, a1, 0x14
                wsr             0xEE, a2 ; EXCVADDR
                l32i.n          a3, a1, 0x18
                wsr             0xD1, a3 ; EXCSAVE_1
                l32i.n          a0, a1, 0x20
                rsr.sar         a3
                movi            a2, 0x3FF
                slli            a2, a2, 0x14
                wsr.sar         a3
                movi.n          a3, 0xF
                s32i.n          a3, a2, 0 ; 0x3FF00000 = 0x0f (DPORT_BASE[0])
                l32i.n          a2, a1, 0x28
                l32i.n          a3, a1, 0x2C
                l32i.n          a1, a1, 0x24
                rfi             3
; End of function _NMILevelVector
надо просто заменить. Полная сборка пока из SDK по nmi пока тут: https://github.com/pvvx/esp8266web/blob/master/info/libs/main/nmi.c
 
Последнее редактирование:

pvvx

Активный участник сообщества
Вывод символа '#' в UART при работающем Web (идут постоянные запросы heap.xml ~ 20ms) стандартной процедурой uart0_write_char('#') на 3076923 baud по таймеру на 5 us через NMI показал, что джиттера между символами более 10ns не наблюдается :( (но и его можно списать на помеху на синхронизацию к кое-как повешенным щупам у модуля от его WiFi).
5us.gif
Клека = 5 us
Процедуры работы с nmi свои... Тесты продолжаются :)
Как пример пока оставил в Web-свалке данный тест (если включить sdk_config.h [HASHTAG]#define[/HASHTAG] USE_NMI_VECTOR):
http://aesp8266/web.cgi?test=1&nmi=5
test=x, где x - это инит NMI с таймером =0 - одиночный импульс, =1 бесконечный повтор
nmi=us, где us - это время в us до срабатывания... скороть UART изменяется в меню TCP-UART Settings...
PS: как-бы с точной задержкой до 1 us теперь проблем нет.
 
Последнее редактирование:

pvvx

Активный участник сообщества
Вопрос:
В новых sdk какая максимальная задержка до входа в обработчик прерываний (например uart) при высокой активности wifi ?
Не более 1мс получится без использования nmi ?
Стратегическая информация, не бесплатно :) :
Максимальная задержка обычных прерываний типа аппаратного таймера 0 выловлена при WiFi Scan (meSDK 1.3.0) и составляет 1.156 ms.
isr_off_scan.gif
При обычном соединении WiFi и передаче файлов по TCP часто идет задержка в 120..162 us при CLK CPU на 80 MHz, что для CLK CPU на 160 Mhz соответственно в 2 раза меньше. Проверка простого прерывания делалась на управлении таймером выходным пином с периодом в 7 us (меньше не тянет при CLK CPU на 80 MHz). При 160 MHz предел обработчика простых прерываний к 5 us.

NMI прерывание тянет до 4-х us. При обработчике прерывания от таймера через nmi выпадений не обнаружено.
 
Последнее редактирование:

pvvx

Активный участник сообщества
Для тех кто ещё хочет реализовать modbus rtu с RS-485.
Ожидание разрыва фрейма по n-символов тишины с вызовом прерывания встроено в UART.
Это UART_RX_TOUT_THRHD c UART_RX_TOUT_EN - Set this bit to enable rx time-out function, ну и остальные флаги прерываний и т.д....
Проверено - все давно работает, но не выложено в открытый доступ - жду прецедента :).
 

pvvx

Активный участник сообщества
Тест низкого уровня драйвера приемника-передатчика по RS-485.
Пауза > 1.5 символа определяется аппаратно, на ней и висит переключение направления передачи для драйвера. Пауза далее, до истечения 3.5 символов висит на прерывании таймера (можно на NMI).
RX-TX можно объединить для связи на плате по одному проводу без драйвера шины. Сигнал направления передачи на любом GPIO и можно отключить.
rs485_100kbs.gif
На стандартных скоростях до 1 Mbit/s пауза в 3.5 символа "усваивается" ESP8266 нормально. Более - затягивает, т.к. не хватает производительности ESP8266.
rs485_5Mbs.gif
Четность и прочие ошибки на приеме драйвер тоже обрабатывает...
Непрерывная передача пакетов Modbus требуется, когда идет передача "всем" - нулевому устройству...
 
Последнее редактирование:

Прапор

New member
RX-TX можно объединить для связи на плате по одному проводу без драйвера шины. Сигнал направления передачи на любом GPIO и можно отключить.
Ничего не понял... Для чего их объединять? Во втором предложении, простите, совсем смысла не уловил )) Какова цель всех этих манипуляций?
 

Dmitry P

New member
Уважаемый pvvx, Ваш вклад просто неоценим.
А что будет прецедентом? :)
У меня есть только готовый оттестированный переходник UART-Wifi на основе Вашего проекта. Больше ничего к обмену нет :)
Для тех кто ещё хочет реализовать modbus rtu с RS-485.
Ожидание разрыва фрейма по n-символов тишины с вызовом прерывания встроено в UART.
Это UART_RX_TOUT_THRHD c UART_RX_TOUT_EN - Set this bit to enable rx time-out function, ну и остальные флаги прерываний и т.д....
Проверено - все давно работает, но не выложено в открытый доступ - жду прецедента :).
 

pvvx

Активный участник сообщества
Ничего не понял... Для чего их объединять? Во втором предложении, простите, совсем смысла не уловил )) Какова цель всех этих манипуляций?
Для связи микросхем-контроллеров по одному проводу на одной плате по интерфейсу Modbus RTU ("1 Wire Modbus RTU" (с) pvxx :) :p )
А что будет прецедентом? :)
Помощь в решении уже 2-х вопросов связанных с Modbus RTU и Modbus TCP бриджа.
1) Как осуществить в Modbus TCP обмен чаще 5-ти пакетов в секунду с внешними стеками TCP у которых внедрена система паузы подтверждения одиночного пакета TCP (ACK) на 200 ms? Это проделки "мелгомягких" и прочих.
2) Как убрать джиттер на 8 тактов битрейта UART (скорости UART - baud) для TOUT в кривой китай-реализации контроллера UART в ESP8266? Проявляется на внешние принимаемые асинхронные данные. Т.е. UART дает прерывание после приема символа на тишину через задержку от (TOUT-1)*8 до TOUT*8 тактов UART.
1.5 символа у нас 1.5*11=16.5 тактов, 3.5 символа = 28 тактов, а TOUT_INT программируется на 2..127*8 тактов, но с джиттером на 8 тактов. Для собственных выведенных символов - оно синхронно и джитера нет.
Не решив вопрос 2 невозможно сделать универсальное решение для встройки в web-свалку, анализирующее все ошибки RS-485 по стандарту Modbus RTU. Выходит только специализированное решение использующее доп. оборудование чипа ESP8266, которое желательно освободить для "телепузиков" с целями упрощения им до/на-писания программ путем тыкания кнопок "копу-паст" и "компиляция" и выкладывания результата в блоги. Других то целей у них нет...
Для них можно создать только версию не проверяющую паузу в 1.5 символа. Тогда не требуется сложное распределение на таймеры и прочее, что запутает другие процедуры, не относящиеся к RS-485.
 
Последнее редактирование:

Dmitry P

New member
PS cам занимаюсь АСУ ТП (DeltaV от Emerson) и пока не встречал применений, где нужен высокоскоростной modbus. обмена раз в секунду обычно хватает. где надо чаще, надежно и цифру - там всякие foundation fieldbus и profibus живут, где надо супер-быстро - там 4-20 mA :)
а удлинитель 485 был бы очень полезен для диспетчерских целей, для сбора с небольших контроллеров.
 
Сверху Снизу