• Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

ESP не контролирует поток UART

mcmega

Member
Всем привет!
Столкнулся с проблемой...
Сделал так сказать WiFi программатор, который прошивает AVR микроконтроллеры. Загрузчик в AVR записан тоже свой. К загрузчику вопросов нет вообще, шьёт хоть из терминала, работает четко, написан на асме.

Работа загрузчика и процесс прошивки MCU
Параметры UART: 115200 / 8 / 1 / None
Управляющие символы:

0x3E - загрузчик запущен (">")
0x13 - необходимо приостановить передачу ("xOff")
0x11 - необходимо возобновить передачи ("xOn")
0x3C - загрузчик успешно завершил работу ("<")
0x21 - ошибка контрольной суммы, загрузчик ожидает прием HEX с самого начала ("!")

Передача НЕХ-файла:
1. Сбросить MCU (использована нога ESP через конденсатор)
2. Дождаться символа xOn, начать посимвольную передачу (буфера MCU - 128 Байт)
3. После заполнения буфера MCU выдаст символ xOff (необходимо приостановить передачу, идет запись Flash)
4. После записи 128 Байт во Flash MCU выдаст символ xOn (необходимо возобновить передачу)
5. После полной записи прошивки MCU выдаст символ "<"

В терминале это будет выглядеть примерно так:
> 0x11 0x13 ... 0x11 0x13 0x11 <


mcuHex - файл прошивки mcu.hex в FS
Теперь немного кода Arduino:
Код:
bool uartFlowCtrl = false;

// Программирование MCU, работает в главном цикле loop
void mcuProg() {
  if (mcuHex.available()) {
    if (Serial.available()) {
      inChar = (char)Serial.read();
      switch (inChar) {
        case '>':
        case 0x11:                        // XON (шлем данные)
          uartFlowCtrl = true;
          break;
        case 0x13:                        // XOFF (ждем)
          uartFlowCtrl = false;
          break;
        case '!':                         // Ошибка контрольной суммы
          mcuFlashTries++;                // Увеличиваем счетчик попыток
          uartInitMcuUpdate();            // Заново инициализируем режима прошивки MCU
          break;
        case '<':                         // Запись успешно завершена
          uartFinishMcuUpdate();          // Удаляем mcu.hex, перезагружаем MCU
          break;
      }
    }
    if (uart_flow_control) {
      Serial.write(mcuHex.read());    // Передаем очередной байт из файла прошивки в UART
      delay(3);    // Если убрать задержку, контроль потока не работает!!!
    }
  }
}
Собственно проблема в 25 строке кода.
Я не могу позволить тупить в цикле целых 3мс потому что работает асинхронный сервер и периодически слетает из-за этого. Почему не работает управление потоком передачи?
Ради эксперимента устанавливал значение 100 и вручную посылал символы управления потоком, все работало отлично, сбоев не было, но если убрать задержку вообще, все ломается и видно что прошивка передается, а на управляющие символы никакой реакции.
Я подозреваю, что Serial.available() дает данные с запаздыванием...
Как можно решить такую проблему?
 
Последнее редактирование:

pvvx

Активный участник сообщества
Я подозреваю, что Serial.available() дает данные с запаздыванием...
А как иначе?
Вы положили в передатчик UART символ и тут-же смотрите есть ли на него ответ, а символ ещё не передан и до приема ответа ещё пройдет неизвестно сколько времени...

Передача символа по UART = минимум 10/baud сек
Прием символа по UART = минимум 10/baud сек
Итого до приема ответа у вас = 20/baud + N сек время ответа устройства.
Для 9600 это минимум = 2 мс

Исправляйте алгоритм опроса - задействуйте сравнение milles() после передачи для опроса приема и таймаута, т.к. аппаратного управления тайм-аутом приема UART в Arduno нет, хотя всё это есть в UART ESP. Писатель Arduini IDE не знаком с аппаратной частью и радиотехникой в общем - только с программированием. Без "костылей" на Arduini IDE почти ничего не работает.
 
Последнее редактирование:

Алексей.

Active member
Если убрать задержку, контроль потока не работает!!!
Кто Вам сказал что контроль потока не работает?
Убирая задержку Вы просто забиваете fifo передатчика и очередной вызов
Serial.write(mcuHex.read()); становится блокирующим.
Посмотрите исходники ардуины
тут .arduino15/packages/esp8266/hardware/esp8266/2.3.0/cores/esp8266/HardwareSerial.cpp
Код:
size_t HardwareSerial::write(uint8_t c)
{
    if(!_uart || !uart_tx_enabled(_uart)) {
        return 0;
    }

    uart_write_char(_uart, c);
    return 1;
}
и вот тут .arduino15/packages/esp8266/hardware/esp8266/2.3.0/cores/esp8266/uart.c
Код:
void uart_write_char(uart_t* uart, char c)
{
    if(uart == NULL || !uart->tx_enabled) {
        return;
    }
    while((USS(uart->uart_nr) >> USTXC) >= 0x7f);
    USF(uart->uart_nr) = c;
}
вот тут while((USS(uart->uart_nr) >> USTXC) >= 0x7f); как раз ждем пока в fifo место не освободится для одного байта,
и только после этого будет вызван Ваш код Serial.available()
Зная размер fifo передатчика (по даташитам он 128 байт) и зная размер fifo в AVR-e может как то оптимизировать загрузку, не заполнять fifo на esp полностью или контролировать тот самый available() когда заполнили fifo наполовину.
AVR должен выставлять XOff заблаговременно, потому как у передатчика (у esp в нашем случае) тоже как ни странно есть fifo и на ходу он не остановится, на то он и fifo
 

mcmega

Member
Спасибо, ценное замечание!
А можете пример привести и более подробно расписать как что сделать, я не очень в теме о milles() и как правильно проверку делать.
 

mcmega

Member
Кто Вам сказал что контроль потока не работает?
Убирая задержку Вы просто забиваете fifo передатчика и очередной вызов
Serial.write(mcuHex.read()); становится блокирующим.
Посмотрите исходники ардуины
тут .arduino15/packages/esp8266/hardware/esp8266/2.3.0/cores/esp8266/HardwareSerial.cpp
Код:
size_t HardwareSerial::write(uint8_t c)
{
    if(!_uart || !uart_tx_enabled(_uart)) {
        return 0;
    }

    uart_write_char(_uart, c);
    return 1;
}
и вот тут .arduino15/packages/esp8266/hardware/esp8266/2.3.0/cores/esp8266/uart.c
Код:
void uart_write_char(uart_t* uart, char c)
{
    if(uart == NULL || !uart->tx_enabled) {
        return;
    }
    while((USS(uart->uart_nr) >> USTXC) >= 0x7f);
    USF(uart->uart_nr) = c;
}
вот тут while((USS(uart->uart_nr) >> USTXC) >= 0x7f); как раз ждем пока в fifo место не освободится для одного байта,
и только после этого будет вызван Ваш код Serial.available()
Зная размер fifo передатчика (по даташитам он 128 байт) и зная размер fifo в AVR-e может как то оптимизировать загрузку, не заполнять fifo на esp полностью или контролировать тот самый available() когда заполнили fifo наполовину.
AVR должен выставлять XOff заблаговременно, потому как у передатчика (у esp в нашем случае) тоже как ни странно есть fifo и на ходу он не остановится, на то он и fifo
Контроль потока работает, но если выставить задержку больше, но меня это не устраивает. AVR пишет постранично, размер 128 байт
 
Последнее редактирование:

pvvx

Активный участник сообщества
Кто Вам сказал что контроль потока не работает?
Убирая задержку Вы просто забиваете fifo передатчика и очередной вызов
Serial.write(mcuHex.read()); становится блокирующим.
Посмотрите исходники ардуины
тут .arduino15/packages/esp8266/hardware/esp8266/2.3.0/cores/esp8266/HardwareSerial.cpp
Код:
size_t HardwareSerial::write(uint8_t c)
{
    if(!_uart || !uart_tx_enabled(_uart)) {
        return 0;
    }

    uart_write_char(_uart, c);
    return 1;
}
и вот тут .arduino15/packages/esp8266/hardware/esp8266/2.3.0/cores/esp8266/uart.c
Код:
void uart_write_char(uart_t* uart, char c)
{
    if(uart == NULL || !uart->tx_enabled) {
        return;
    }
    while((USS(uart->uart_nr) >> USTXC) >= 0x7f);
    USF(uart->uart_nr) = c;
}
вот тут while((USS(uart->uart_nr) >> USTXC) >= 0x7f); как раз ждем пока в fifo место не освободится для одного байта,
и только после этого будет вызван Ваш код Serial.available()
Зная размер fifo передатчика (по даташитам он 128 байт) и зная размер fifo в AVR-e может как то оптимизировать загрузку, не заполнять fifo на esp полностью или контролировать тот самый available() когда заполнили fifo наполовину.
AVR должен выставлять XOff заблаговременно, потому как у передатчика (у esp в нашем случае) тоже как ни странно есть fifo и на ходу он не остановится, на то он и fifo
Вы не путайте xon-xoff режим UART ASCII c flowcontrol с RTS/CTS? :)
При чем тут FIFO? Есть функции с ожидаем вывода символа и без. В Arduinio об этом не знают - там всё равно, т.к. её цели другие.
 

pvvx

Активный участник сообщества
Спасибо, ценное замечание!
А можете пример привести и более подробно расписать как что сделать, я не очень в теме о milles() и как правильно проверку делать.
Опечаталася - millis() | Аппаратная платформа Arduino
Можно и micros() | Аппаратная платформа Arduino
При передаче, сразу после, считываете значение micros(), в цикле проверки ставите пропуск вашей функции, пока не пройдет время 20/baud после последней передачи. При опросе принятого символа, если его нет - смотрите, не прошли ли 20/baud + тайм-аут ответа. Если прошло - снова передаете, по вашему алго...
 

pvvx

Активный участник сообщества
Что то я туплю походу... как это можно использовать для решения?
Если передается N симолов, то тут сложнее - в FIFO UART может быть 128 символов. Но максимальный тайм-аут в 128*10/baud решит задачу при передаче более 128 символов.
Драйвер UART и API для ESP Arduino IDE вы всё равно переписывать не будите, хотя у UART ESP есть всё необходимое, включая задаваемую паузу тишины на линии в её тактах и вызова прерываний по данном делу, как и аппаратные XON/XOFF и FlowControl и задания прерываний по указанному кол-ву отсутствия или наличия символов в FIFO... (Arduino это не грозит :) )
 
Последнее редактирование:

mcmega

Member
Если передается N симолов, то тут сложнее - в FIFO UART может быть 128 символов. Но тайм-аут в 128*10/baud решит задачу.
Драйвер UART и API для ESP Arduino IDE вы всё равно переписывать не будите, хотя у UART ESP есть всё необходимое, включая задаваемую паузу тишины на линии в её тактах и вызова прерываний по данном делу, как и аппаратные XON/XOFF и FlowControl и задания прерываний по указанному кол-ву отсутствия или наличия символов в FIFO... (Arduino это не грозит :) )
Я передаю 1 символ и ухожу в loop, затем снова захожу в mcuProg() и так до конца прошивки. После прошивки, я меняю флаг (режим работы UART) и UART работает в штатном режиме.
т.е. передача идет посимвольно и каждый раз попадаю в mcuProg().
 
Последнее редактирование:

pvvx

Активный участник сообщества
Я передаю 1 символ и ухожу в loop, затем снова захожу в mcuProg() и так до конца прошивки. После прошивки, я меняю флаг (режим работы UART) и UART работает в штатном режиме.
т.е. передача идет посимвольно и каждый раз попадаю в mcuProg().
Дык не заходите в mcuProg() пока не вышло время от micros() после последней передачи + 20000000/baud. А при опросе наличия принятых символов проверяйте время от micros() после последней передачи + (10*(кол-во переданных символов > 128? 128 : кол-во переданных символов) *1000000 + 10000000 + время ожидания ответа от устройства)/baud и выходите из mcuProg() пока время не вышло и принятых символов нет.
Ранее, с имеющимися функциями работы с UART нет необходимости что-то предпринимать и Loop() пусть занимается другими задачами.
 

mcmega

Member
Дык не заходите в mcuProg() пока не вышло время от micros() после последней передачи + 20000000/baud. А при опросе наличия принятых символов проверяйте время от micros() после последней передачи + (10*(кол-во переданных символов > 128? 128 : кол-во переданных символов) *1000000 + 10000000 + время ожидания ответа от устройства)/baud
Но MCU имеет буфер в 128 Байт и после его заполнения выставляет запрет на передачу - символ "xOff", пишет флешь и потом символ "xOn". И т.д. Это не вяжется с таким решением...
 

Алексей.

Active member
Вы не путайте xon-xoff режим UART ASCII c flowcontrol с RTS/CTS? :)
По коду ТС case 0x11: и case 0x13: откуда там RTS/CTS
Зная что AVR будет присылать XOff заблаговременно, когда половина его приемного буфера заполнена он и отправит нам Xoff
Если на esp отправляем (заполняем) столько сколько половина его приемного буфера AVR-a и ожидаем выхода очередного байта и контролируем получение xoff

При чем тут FIFO? Есть функции с ожидаем вывода символа и без. В Arduinio об этом не знают - там всё равно
Хоть я и совсем не любитель ардуины но в исходники можно все же посмотреть.
сначала ждут освободившегося места в фифо
while((USS(uart->uart_nr) >> USTXC) >= 0x7f);
затем отправляют байт
USF(uart->uart_nr) = c;
 

mcmega

Member
По коду ТС case 0x11: и case 0x13: откуда там RTS/CTS
Зная что AVR будет присылать XOff заблаговременно, когда половина его приемного буфера заполнена он и отправит нам Xoff
Если на esp отправляем (заполняем) столько сколько половина его приемного буфера AVR-a и ожидаем выхода очередного байта и контролируем получение xoff


Хоть я и совсем не любитель ардуины но в исходники можно все же посмотреть.
сначала ждут освободившегося места в фифо
while((USS(uart->uart_nr) >> USTXC) >= 0x7f);
затем отправляют байт
USF(uart->uart_nr) = c;
Вы не обращайте внимания на коды символов, я их назначил для управления потоком как общепринятые, а не реализовывал стандартизированный формат обмена. Хотя с терминала (если выставить контроль xOn/xOff) шьется все отлично.
 

pvvx

Активный участник сообщества
По коду ТС case 0x11: и case 0x13: откуда там RTS/CTS
Зная что AVR будет присылать XOff заблаговременно, когда половина его приемного буфера заполнена он и отправит нам Xoff
Если на esp отправляем (заполняем) столько сколько половина его приемного буфера AVR-a и ожидаем выхода очередного байта и контролируем получение xoff
А где у автора топика указано, что используется классический XON/XOFF? :)
Да и то, что вы описываете так-же является классическим XON/XOFF на UART.
+ есть синхронный и асинхронный режим :) Дуплекс/полудуплекс...
Хоть я и совсем не любитель ардуины но в исходники можно все же посмотреть.
сначала ждут освободившегося места в фифо
while((USS(uart->uart_nr) >> USTXC) >= 0x7f);
затем отправляют байт
USF(uart->uart_nr) = c;
И что это дает? :) Всё равно функция передачи является не блокирующей и не неблокирующей. Она неотмирасего. Бездарность - то блокирует на часть, то не блокирует (не ждет полного вывода). У контроллеров с DMA это ещё более печально в Arduino... Годятся только для вывода log в UART.
 
Последнее редактирование:

mcmega

Member
Согласен, что Ардуино полное г... для нормальных проектов, но пока что так...
 

mcmega

Member
pvvx, если у Вас будет время, может набросайте примерно код, что можно сделать с такой проблеме. Буду очень благодарен за помощь! Я как чувствовал что с UART в Ардуино что то не то..., простые задачи без проблем, а как коснись... одни мысли))
 

pvvx

Активный участник сообщества
pvvx, если у Вас будет время, может набросайте примерно код, что можно сделать с такой проблеме. Буду очень благодарен за помощь! Я как чувствовал что с UART в Ардуино что то не то..., простые задачи без проблем, а как коснись... одни мысли))
Я не могу вам написать ничего, т.к. не знаю протокола на котором вы работаете. Ну и нет на его разбор времени. Общий смысл вам описал - используйте ожидание времени от micros() на обработку вашей функции опроса символов после вывода символа(ов).
Что сложного - вывели символы в Serial.write() и запомнили счетчик micros()
Пока счет micros() не достиг времени вывода символа не вызываете ничего. Если достиг, то смотрите есть ли принятые и учитываете тайм-аут. И так по кругу, пока не истечет тайм аут приема символа после последней передачи.

@Алексей. - попробуйте туда прикрутить сигнал направления передачи для полудуплексной шины :) Аппаратная часть UART ESP это позволяет сделать для скоростей к пару мегабит. И любовь к Arduino тут не при чем. Просто в Arduino есть горе-писатели её функций - у них цели не сделать нормально, а совсем другие...
 
Последнее редактирование:
Сверху Снизу