Ну что.
Новые рассуждения.
Приветствую всех форумчан и гостей.
Давно читаю на нашем форуме о проблемах и непонимании в работе
serial порта и модбаса, rtu в частности.
Попытаюсь высказать моё видение данных моментов.
Не претендую ни на учебник, ни на предположение - просто опус.
Начну с serial, hrdware, а не soft.
Оба буфера uart - кольцевые, т.е. обращение к 1-му байту будет сразу после последнего.
Буфер работает с того места, где остановился, даже если это уже другая задача, индекс переменной не сбрасывается.
Как работает приём. Названия переменных - из библиотеки.
Буфер пуст, индексы _rx_buffer_head и _rx_buffer_tail равны, голова и хвост, разница 0.
В режиме приёма при поступлении очередного байта в аппаратный регистр вызывается прерывание,
в котором его обработчик копирует байт в буфер порта, _rx_buffer[SERIAL_RX_BUFFER_SIZE],
по индексу _rx_buffer_head, при этом увеличивая его значение на единицу.
При этом основная программа, которая всё это время работала, приостанавливается на время
выполнения этого копирования.
По окончании приёма функцией available мы можем узнать, сколько байт приняли,
т.е расстояние от головы до хвоста
При чтении, read, мы получаем значение из буфера с индексом _rx_buffer_tail, одновременно
увеличивая его на 1, постепенно подтягивая к голове, _rx_buffer_head
Как только значения головы и хвоста станут равными, попытка чтения вернёт -1.
Передача.
Для примера массив.
Отправляем его Serial.write(buf, len); // buf - сам массив, len - сколько байт отправить
Что происходит: данные копируются в буфер uart, байты по одному начинают попадать в сам
передатчик данных. При этом функция availableForWrite() может вернуть нам, сколько
свободных ячеек осталось в буфере передачи.
По окончания передачи очередного байта вызывается прерывание, обработчик
которого кладёт следующий байт данных из буфера в передатчик.
Так продолжается, до тех пор, пока буфер не опустеет, при этом availableForWrite вернёт
размер SERIAL_TX_BUFFER_SIZE-1.
Вот так, в моём видении, происходит работа HardwareSerial.
Есть ещё функции, но о них позже.
Теперь о ModBus RTU. В FlProg.
Год назад писал
первый пост темы
Процитирую:
Sancho писал(а): ↑28.11.2019{, 12:40}
Как часто замечаю, иногда пользователи не до конца понимают как устроена работа обмена по модбас, и, соответственно, требуют не реализуемые задачи.
ilusha писал(а): ↑24.11.2019{, 01:04}
Почему работает Очень медленно modbus?
Очень медленно.
Это так медленно, если я делаю
не правильно..
Синхронизация включена на 1 миллисекунду
И для общего понимания. Не спеша.
Слэйв модбас rtu. Slave ModBus RTU.
При создании кода для IDE FLProg перед первой платой вставляет дополнительные, необходимые для работы, переменные, функции.
Одной из них является функция, отвечающая за обмен по модбас. Выполняется она, как Вы догадались, один раз в цикле.
Сама по себе состоит из других, вложенных функций, отвечающих за разные задачи.
В примере - минимальный вариант - значение из регистра на выход ШИМ. Время цикла - минимальное.
Итак, вначале проверяется, есть ли что либо, а именно байты, в буфере serial( или Softserial - не принципиально).
1-й цикл. Если ответ пусто, 0, функция прерывается, программа выполняется дальше.
2-й цикл. Если есть, запоминается текущее количество доступных для считывания байт( в _modbusSlaveLastRec),
запоминается текущее время( в _modbusSlaveTime), функция прерывается, программа выполняется дальше.
3-й цикл. Если значение количества последних и сейчас доступных для считывания байт не совпадает, значит данные всё еще поступают в буфер serial.
Запоминается текущее количество доступных для считывания байт(_modbusSlaveLastRec),
запоминается текущее время(_modbusSlaveTime), функция прерывается, программа выполняется дальше.
N-й цикл. Значение количества доступных для считывания из буфера serial байт и старое значение одинаковы, то проверяем, сколько прошло времени,
между последним изменением, вдруг байт где-то заплутал. Если время ожидания получения байта ещё не вышло - прерываем функцию.
N+n цикл. Время вышло, больше байтов нет, начинаем обрабатывать полученное.
Замечу - время ожидания приёма байта зависит от скорости(раньше), у Автора сейчас составляем 5 мс....
обнуляем _modbusSlaveLastRec.
Загружаем все данные из буфера serial в буфер программы, попутно подсчитывая количество байт и следя за переполнением.
/*
Здесь у Автора нет защиты от переполнения буфера и получения нежданчиков:
если в моей библиотеке serial размер буфера больше стандартного, буфер модбаса переполнится,
выставится только флаг - bBuffOverflow, но данные продолжат записываться в ячейки, которые буферу не принадлежат...
Я в таких случаях просто вызываю read(), опустошая буфер serial. Легко чинится в коде перестановкой строк....
*/
Если количество полученных байт >= минимальной посылки, проверяем, нам ли она адресована. Если нет - функция прерывается, программа выполняется дальше.
Проверяем контрольную сумму, если не правильная - функция прерывается, программа выполняется дальше.
Далее, исходя из номера функции, делаем необходимые действия - записываем данные при необходимости и готовим ответ.
Передаём ответ мастеру.
Функция закончена, обмен проведён, программа выполняется дальше.
Что я изменяю:
При приёме, особенно, когда присутствует большое количество слэйвов, каждому приходится переваривать пакеты,
отправленные не ему. А пакеты ответа других слэйвов не флпрог мастеру могут иметь приличный размер.
Однако, после завершения приёма посылки в буфер UART(N+n цикл. из цитаты сверху), прежде чем копировать в буфер самой программы,
можно посмотреть на первый байт, адрес устройства. Это peek(). И если мы убедимся, что это не нам, просто сбрасываю буфер.
Для этого пришлось дописать в библиотеку ещё одну простейшую функцию:
в хедер
virtual void reset(void);
в реализацию
void HardwareSerial::reset(){
_rx_buffer_head = _rx_buffer_tail;
}
Всё. Нет необходимости копировать без надобности.
При передаче. FLProg. пока весь буфер не будет отправлен - программа не выполняется, т.к. после
Serial.write( _modbusMasterBuffer, _modbusMasterBufferSize );
есть
delay(1);
и
Serial.flush();
Последняя функция крутится в цикле, пока все данные из буфера uart не будут отправлены, соответственно, цикл стоит на паузе!
Мог предположить, что это нужно при управлении пином передачи, т.е. перед передачей включаем, потом, после окончания, выкл.
Однако сейчас я вижу:
Код: Выделить всё
UCSR0A=UCSR0A |(1 << TXC0);
digitalWrite(2, HIGH );
delay (1);
Serial.write( _modbusSlaveBuffer, _modbusSlaveBufferSize );
while (!(UCSR0A & (1 << TXC0)));
delay (1);
digitalWrite(2, LOW );
Serial.flush();
Ну да ладно...
Представляете, у Вас 55 байт на скорости 9600 приостанавливают программу на время около 50мс. А если битрэйт медленнее?
Теперь вспомним о availableForWrite() .
Немного поколдуем, дописав в библиотеку новую функцию:
Код: Выделить всё
virtual boolean finishWrite(void);
boolean HardwareSerial::finishWrite(void)
{
return (SERIAL_TX_BUFFER_SIZE - 1) == availableForWrite();
}
Что нам это даёт? Мы можем включить пин передачи, отправить буфер в uart, а в теле программы отслеживать, пока пин в 1,
что возвращает finishWrite(). Как только возвращает 1, можно с задержкой, равной времени передачи двух байт, выключить пин упр.
Почему нужна задержка - возможно буфер пуст, но передатчик отправляет последний байт.
Как в оружии - магазин вроде и пуст, но патрон в патроннике может быть.
Необходимая задержка по натуральным тестам - на 9600 - 2000 мкс, на 115200 - 170.
Пример для теста:
Код: Выделить всё
if (_gtv1 == 1) {
digitalWrite(9, 1);
Serial.println("qwrtyrafghgzsjhgkuygbzkjvhggzgb");
}
if(digitalRead(9)){
if (Serial.finishWrite()){
if(_isTimerMicros(qwe,170)){digitalWrite(9, 0);}
}
else qwe=micros();
}
Ну вот вроде и всё, касательно uart и modbus в роли слэйва, из того, чем хотел поделиться.
Надеюсь, кому-то это пригодится.
О режиме modbus master rtu постараюсь написать в ближайшее время. Опишу своё видение, откуда ноги растут у "тормоза".
Ну, и как я с этим борюсь.
Извиняюсь замногабукв, за возможную скомканность.