Где этот скетч имеет неопределенное поведение?

Простой скетч и простой вопрос: где этот скетч имеет неопределенное поведение?

Примечание. Мне не нужно решение для исправления кода. У меня уже есть код, который работает. Мне действительно нужно понять, в чем мое мнение ошибочно.

Предыстория: несколько детей писали программу для робота, следящего за линией, который имеет 3 цифровых датчика. Многие из них замуровали свои Arduino. Я сократил затронутые скетчи до минимума, чтобы проблема все еще воспроизводилась. Надеюсь, скетч достаточно понятен.

Он не выдает предупреждений в Arduino IDE 1.8.9, 1.8.15 и 2.0.4, даже если все предупреждения включены.

Тем не менее, этот код блокирует все клоны Arduino (9 экземпляров Elegoo Smart Robot Car Kit 3.0 Plus), и я потратил несколько часов, чтобы найти надежную процедуру их разблокировки. Поэтому я твердо убежден, что этот код должен иметь неопределенное поведение, за исключением того, что я его не вижу.

Процедура разблокировки:

  1. Снимите щит, если он еще не снят.
  2. Отключите Arduino от USB.
  3. Подключите Arduino к USB
  4. Нажмите кнопку сброса на плате Arduino.
  5. Снова отключите Arduino от USB (!)
  6. Подключите Arduino к USB
  7. Скомпилируйте и загрузите пустой скетч
long PIN_LINE_LEFT = 10;
long PIN_LINE_MID = 4;
long PIN_LINE_RIGHT = 2;
long DATARATE = 115200;

void setup() {
  pinMode(PIN_LINE_LEFT, INPUT);
  pinMode(PIN_LINE_MID, INPUT);
  pinMode(PIN_LINE_RIGHT, INPUT);
  Serial.begin(DATARATE);
}

void loop() {
  byte leftValue = digitalRead(PIN_LINE_LEFT);
  Serial.println(leftValue);
}

Вот мой анализ:

IMHO, скетч необычен только в одном: вместо того, чтобы #define задавать номера выводов и прочее, он объявляет их как глобальные переменные типа long.

Согласно справочнику Arduino по типам данных a long составляет 32 бита. Печать sizeof(long) подтверждает это. Таким образом, 115200 не будет переполняться на 65536 (16 бит).

Для Serial.begin() DATARATE будет преобразован в unsigned long, что, ИМХО, не должно быть здесь проблемой, так как число положительное.

Конечно же, номера выводов не обязательно должны быть длинными и должны быть объявлены как uint8_t, чтобы лучше соответствовать pinMode() и digitalRead(). Но я по-прежнему считаю, что неявное преобразование в uint8_t должно подойти, так как переполнения тоже нет.

Затем digitalRead() официально возвращает int. Результат присваивается byte, который является псевдонимом для uint8_t. Кажется, что это также преобразуется неявно. Однако значения должны быть только HIGH или LOW, то есть 1 или 0, что также не приводит к переполнению со знаком.

Что я пробовал:

Изменение DATARATE на более подходящее unsigned long DATARATE = 115200UL; ничего не меняет.

Замена byte leftValue = digitalRead(PIN_LINE_LEFT); на более подходящий int leftValue = digitalRead(PIN_LINE_LEFT); также ничего не изменила.

Замена long PIN_LINE_MID = 4; и всех остальных на более подходящий uint8_t PIN_LINE_xxx = xxx; также ничего не меняет.

ИМХО, с этими изменениями в принципе нечего исправлять, и это должна быть сверхвалидная программа на C/C++ без каких-либо неявных опасных приведений.

Serial.flush(); в цикле не требуется в соответствии с документация Serial.write(). Так что переполнения буфера не будет.

, 👍3

Обсуждение

**Комментарии были [перемещены в чат](https://chat.stackexchange.com/rooms/144480/discussion-on-question-by-thomas-weller-where-does-this-sketch-have-undefined-be ); пожалуйста, не продолжайте обсуждение здесь. ** Перед публикацией комментария под этим, пожалуйста, ознакомьтесь с [целями комментариев](/help/привилегии/комментарий). Комментарии, которые не требуют пояснений или предложений по улучшению, обычно относятся к [ответу](/help/how-to-answer), [мета] или [чату]. Комментарии, продолжающие обсуждение, могут быть удалены., @Nick Gammon


2 ответа


Лучший ответ:

2

У вашего скетча нет неопределенного поведения. Это работает:

У меня уже есть работающий код.

Ваша проблема, по-видимому, в том, что после загрузки этого скетча у вас возникли проблемы с загрузкой нового скетча:

этот код блокирует все клоны Arduino

Они не "замурованы"; как вы говорите:

Но ваш метод работает примерно в 9 из 10 случаев. Я не могу в это поверить.

Ваш скетч, который постоянно выводится на последовательный порт, по-видимому, затрудняет ответ клона на новую загрузку. Более дешевый интерфейс USB, который используют клоны, чтобы сэкономить несколько центов, очевидно, не так хорошо реагирует, как настоящий Arduino, на попытки загрузить новый скетч.

Предложенный метод в комментариях (ссылка на мой пост Я замуровал свой Arduino Uno? Проблемы с загрузкой на плату) указывает на решение, и я цитирую :

Выполните следующие действия:

  • Полностью выключите плату (отсоедините USB-кабель).
  • Нажмите и удерживайте кнопку сброса (или установите перемычку между контактом RESET и контактом GND). Это останавливает запуск скетча проблемы и активирует сторожевой таймер.
  • Удерживая кнопку сброса, повторно подключите USB-кабель.
  • Начать загрузку скетча, у которого нет этой проблемы (например, Blink)
  • После того, как среда IDE сообщит о "Выполняется загрузка" отпустите кнопку сброса (или снимите перемычку).
  • Теперь загрузка должна пройти нормально, поскольку скетч, активировавший сторожевой таймер, так и не запустился.

Приведенное выше решение состоит в том, чтобы вообще не запускать текущий скетч, тем самым избегая того, что он делает, чтобы остановить загрузку новых скетчей.

Ссылки на сторожевой таймер взяты из исходного сообщения, здесь они неуместны.


Он не выдает предупреждений в Arduino IDE 1.8.9, 1.8.15 и 2.0.4, даже если все предупреждения включены.

Это потому, что с скетчем как таковым проблем не возникает. Аппаратное обеспечение? Ну, вы покупаете более дешевое оборудование, вы можете получить некоторые проблемы. Описанный выше метод поможет вам обойти эти проблемы.

,

2

С некоторыми микросхемами USB to TTL Serial возникают проблемы с запуском загрузки, если на последовательной стороне поступает слишком много входных данных от MCU. В данном случае это Holtek UART Bridge, который не используется в платах Arduino.

Чтобы остановить скетч, удерживайте кнопку сброса, пока не начнется загрузка из IDE. Когда кнопка сброса будет отпущена, загрузчик запустится и получит загрузку.

,