Странное поведение (не удалось вытащить штифт НИЗКИЙ)

Я пытался реализовать свой собственный код для управления шаговым двигателем на arduino uno, однако заметил, что контакты всегда были ВЫСОКИМИ, несмотря на то, что на них было написано. Я начал стирать код, чтобы оставить только важную часть для устранения неполадок, и остался со следующим

#define IN1  8
#define IN2  9
#define IN3  10
#define IN4  11
long int *period;    

void setup() {
pinMode(IN1, OUTPUT); 
pinMode(IN2, OUTPUT); 
pinMode(IN3, OUTPUT); 
pinMode(IN4, OUTPUT); 
*period=256;
}

void loop() {
 digitalWrite(IN1, LOW); 
 digitalWrite(IN2, LOW);
 digitalWrite(IN3, LOW);
 digitalWrite(IN4, HIGH);
}

Операции с периодом остались от предыдущего кода, и я не особо задумывался о них, но как только я их удалил, код начал работать. Более того, код работал, если значение периода было ниже 256. Как только оно достигло 256, штифты не опускались ниже.

Теперь проблема решена, однако мне интересно, почему значение переменной может вызывать такое поведение?

, 👍2

Обсуждение

Вы создали указатель, но не сообщили компилятору, куда он указывает. Затем вы записываете значение туда, куда указывает указатель., @Jot

пока 255 не перезапишет 1 байт в случайном месте памяти. 256 перезаписывает два байта, @Juraj

@Juraj всегда записывается 4 байта (или количество байтов, содержащих длинное целое число), даже если записываются значения <256 (чем другие байты MSB), они будут заполнены 0., @Michel Keijzers

@MichelKeijzers, возможно, байт, затронутый 256, должен быть равен нулю. с 255 он остается нулевым. с 256 это 1. просто бывает, что скетч работает, если он использует 255 и не работает с 256. Я объяснил разницу, @Juraj


2 ответа


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

7

Вы создаете переменную-указатель, а не обычную переменную.

Эта переменная-указатель, если не указано иное, указывает на адрес 0x00. Он занимает 4 байта.

Адреса от 0x00 до 0x1F — это внутренние регистры ЦП с R0 по R31. Ваша переменная-указатель указывает на R0 плюс еще три адреса над ним (всего длинный 4 байта).

Поэтому, когда вы записываете в свой указатель, вы напрямую записываете в регистры ЦП R0, R1, R2 и R3.

Согласно ABI AVR-GCC:

  • R0 — рабочий регистр.
  • R1 — нулевой регистр
  • R2 и усилитель; R3 – это регистры общего назначения (для сохранения вызовов).

Итак, если вы записываете 255 в свою переменную указателя, вы делаете:

R0 = 255
R1 = 0
R2 = 0
R3 = 0

И в этом нет ничего страшного: что-то может пойти не так, но ничего серьезного.

Однако запись 256 приводит к:

R0 = 0
R1 = 1
R2 = 0
R3 = 0

Важно то, что нулевой регистр R1 теперь равен 1, а не нулю. ABI заявляет:

R1 всегда содержит ноль. Во время insn содержимое может быть уничтожено, например, командой MUL, которая использует регистры R0/R1 в качестве неявного выходного регистра. Если insn уничтожает R1, то после этого insn должен восстановить R1 до нуля. Этот регистр должен быть сохранен в прологах ISR, а затем должен быть установлен в ноль, поскольку R1 может содержать значения, отличные от нуля. Эпилог ISR восстанавливает значение. Во встроенном ассемблере вы можете использовать __zero_reg__ для нулевого регистра.

Поэтому R1 должен быть равен нулю, за исключением короткого момента, когда вы используете его для чего-то другого. Поскольку вы устанавливаете его равным 1 и оставляете равным единице, любым другим операциям, которые затем полагаются на то, что этот регистр равен нулю, будет трудно понять, что они делают.

Нулевой регистр — обычное дело. Во многих случаях (особенно в архитектуре RISC) вам нужно число ноль, а наличие регистра, который гарантированно всегда равен нулю, экономит много времени. Например, в MIPS регистр $0 жестко привязан к нулю и никогда не может измениться. AVR не может позволить себе такую роскошь, но компилятор GCC налагает правило для R1, которое вы должны соблюдать, поскольку существует огромное количество библиотечного кода и другого автоматически сгенерированного кода, которые полагаются на то, что этот регистр равен нулю.

Поэтому, когда он установлен в единицу, все перчатки сняты, и никто никогда не может точно знать, что происходит — вам придется дизассемблировать код (или получить промежуточный листинг из компилятора), чтобы увидеть, что именно использует R1, и выясните, каким будет эффект от того, что будет 1 вместо 0.

Например, digitalWrite имеет следующее:

        if (val == LOW) {
 370:   11 11     cpse r17, r1
 372:   05 c0     rjmp .+10          ; 0x37e <digitalWrite+0x50>

Это "СРАВНИТЬ, ПРОПУСТИТЬ, ЕСЛИ РАВНО". Он просматривает переданное вами значение (val), чтобы определить, равно ли оно нулю, используя R1 в качестве нулевого значения. Но если R1 не равен нулю, то он никогда не узнает, что то, что вы передали как НИЗКИЙ, является НИЗКИМ - вместо этого он найдет ВЫСОКИЙ как НИЗКИЙ, поскольку ВЫСОКИЙ равен 1, а R1 равен 1. Таким образом, вы в основном поменяли местами ВЫСОКИЙ и НИЗКИЙ до тех пор, пока что касается digitalWrite.

Есть также места, где требуется число 255. Это легко сгенерировать с помощью "eor r1,r1" - исключающее ИЛИ нулевого регистра. EOR отключает включенные биты и отключенные биты, поэтому ноль становится 255. За исключением того, что если ноль на самом деле равен 1, вы получите 254, а не 255, и происходят более странные вещи.

,

1

У вас есть неинициализированный указатель:

long int *period;    

Он будет указывать на ноль (или карту регистров, см. комментарий Эдгара Боннета ниже), но вы записываете в него 256. Поскольку длинный указатель указывает на 4 байта, если вы записываете значения до 255, будет заполнен только один байт (а остальные равны 0), когда вы записываете значение от 256, другой байт будет заполнен ненулевым значением.

В любом случае, неинициализированные указатели могут привести к странным эффектам, поэтому инициализируйте их следующим образом:

long int period;

long int* periodPointer = &period;

И установите содержание

*periodPointer = 256;
,

Касательно «неопределенного адреса»: здесь это не так. Будучи глобальным, он гарантированно будет инициализирован нулем средой выполнения C. В Uno нулевой адрес сопоставляется с регистровым файлом, что действительно может приводить к странным результатам., @Edgar Bonet

@EdgarBonet Спасибо (не знал о регистровом файле, я должен был знать о глобальном). Просто я всегда привык сам инициализировать каждую переменную., @Michel Keijzers

Спасибо за информацию и совет! Но я просто не понимаю, как то, что содержится в переменной или как инициализируется указатель, повлияет на состояние вывода. Код как-то влияет на пин-регистр?, @Anthropomorphous Dodecahedron

Это может быть трудно выяснить ... однако, что наиболее важно, вы изменяете 4 байта «где-то» в Arduino. Это может (теоретически) означать, что какой-то исполняемый код изменен, что изменены контактные регистры, что изменены некоторые переменные, используемые либо программой, либо библиотекой. Чтобы действительно выяснить это, вы должны проверить значение указателя и (вручную?) создать карту памяти Arduino (если это вообще возможно)., @Michel Keijzers

Адреса от 0x00 до 0x1F — это внутренние регистры ЦП с R0 по R31. Записывая в 0x00-0x03, вы влияете на R0, R1, R2 и R3. R1, согласно AVR-GCC ABI, всегда должен быть равен 0. Его *можно* использовать временно, но впоследствии вы *должны* сбросить его на ноль. Поскольку 256 установит R1 равным 1, R1 больше не равен нулю, поэтому все будущие операции, предполагающие, что он равен нулю, будут неверными. И наступает хаос., @Majenko