4927 subscribers

Микроконтроллеры для начинающихю Часть 22. Сдвиги, вращения, перестановка бит

721 full read
1,7k story viewsUnique page visitors
721 read the story to the endThat's 40% of the total page views
3 minutes — average reading time

Продолжаем знакомство с системами команд микроконтроллеров. В предыдущей статье "Микроконтроллеры для начинающих. Часть 21. Команды манипуляции битами" мы рассмотрели, немного подробнее, чем обычно, битовые команды. Это была относительно сложная для новичков тема, поэтому я и уделил ей больше внимания.

Сегодня мы поговорим о командах сдвигов. Это еще более сложная для новичков тема, но и более интересная. Поэтому и разговор будет еще более подробный. Стоит отметить, что все ранее рассмотренные команды, так или иначе, напрямую соотносились с возможностями языка С. А вот про рассматриваемые сегодня так уже нельзя сказать. Да, в С есть операторы сдвига. И они даже учитывают особенности типов данных переменных, к которым применяются. Но вот вращений в языке С нет. Как нет и перестановки бит.

Это не означает, что команды вращений не генерируются компиляторами. Но специальных операторов для этого нет. В некоторых случаях кодогенераторы компиляторов или оптимизаторы могут заменить специальные последовательности кода С командами вращения, что позволяет хоть как то явно влиять на генерируемые машинные команды. Но это скорее исключения, чем правило.

Как программа на С превращается в машинные коды мы еще будем рассматривать, причем довольно подробно, в последующих статьях. А пока ограничусь этим замечанием.

Сдвиги и вращения (циклические сдвиги). Какие бывают и в чем разница.

Давайте сначала разберемся что такое сдвиги и какими они бывают. Представим, что все биты в байте сдвигаются на один вправо или влево. Так, как показано на этом рисунке

Сдвиг вправо и сдвиг влево. Иллюстрация моя
Сдвиг вправо и сдвиг влево. Иллюстрация моя
Сдвиг вправо и сдвиг влево. Иллюстрация моя

Здесь квадратиками с цифрами внутри показаны биты. Это и есть сдвиг. В первом случае, сдвиг вправо. Во втором, влево. Это можно изобразить немного по другому, например, для сдвига влево.

Сдвиг влево. Иллюстрация моя
Сдвиг влево. Иллюстрация моя
Сдвиг влево. Иллюстрация моя

С точки зрения математики сдвиг влево эквивалентен умножению числа на 2, а сдвиг вправо делению на 2. В большинстве случаев и при соблюдении некоторых условий, как мы увидим чуть позже.

Действительно, возьмем число, например, 44 (00101100) и сдвинем его вправо. Получим 22 (00010110). А после сдвига влево получим 88 (01011000). Забегая вперед можно убедиться, что это верно и для отрицательных чисел.

Казалось бы, что тут сложного? Однако, давайте вспомним, что байт это не просто абстрактный набор бит. Он является представлением числа, которое может быть положительным, отрицательным, или не иметь знака. Кроме того, появляется и вопрос, куда девается бит 7, для примера сдвига влево? И что заносится в бит 0?

Сдвиг беззнаковых чисел называется логическим. Сдвиг знаковых чисел называется арифметическим. Разница между логическими и арифметическими сдвигами заключается в работе со знаковым битом (бит7, самый старший бит, MSB). Арифметические сдвиги не меняют знак числа. Хотя встречаются и странные исключения.

Кроме того, в операциях сдвигов (и вращений, как позже увидим) принимает участие флаг переноса. Я уже говорил, что флаг переноса один из наиболее часто используемых. В том числе, не по прямому назначению. И сдвиги как раз служат отличной иллюстрацией этого.

Давайте подробнее рассмотрим, как все это работает. А потом, уже используя полученные знания, займемся вращениями.

Логический сдвиг вправо и логический сдвиг влево

Это самый простой случай, поэтому рассмотрим сдвиги вправо и влево вместе

Логический сдвиг вправо и логический сдвиг влево. Иллюстрация моя.
Логический сдвиг вправо и логический сдвиг влево. Иллюстрация моя.
Логический сдвиг вправо и логический сдвиг влево. Иллюстрация моя.

Выдвигаемый бит, в случае сдвига вправо это бит 0, а в случае сдвига влево бит 7, помещается в флаг переноса, который здесь (и в дальнейшем) обозначен как CF. Это дает возможность последовательно проанализировать каждый бит через флаг переноса.

В освобождающийся бит, 7 для сдвига вправо и 0 для сдвига влево, вдвигается 0 (бит принимает значение 0).

На аппаратном уровне (на уровне схемотехники) логические сдвиги обычно выполняются с помощью мультиплексоров. Теоретически, для этого могут использоваться и сдвиговые регистры, но это медленнее. Почему не сдвигать байт на своем месте? Для регистров процессора это возможно, если их реализовать в виде сдвиговых регистров. И такой вариант действительно существует. Но если система команд предусматривает сдвиг произвольного байта в памяти, такой подход становится невозможным.

Арифметический сдвиг вправо

Раз сдвиг арифметический, значит знак числа (знаковый бит) измениться не должен.

Арифметический сдвиг вправо не изменяет знак числа. Иллюстрация моя.
Арифметический сдвиг вправо не изменяет знак числа. Иллюстрация моя.
Арифметический сдвиг вправо не изменяет знак числа. Иллюстрация моя.

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

Кстати, обратите внимание на интересную особенность. Числи минус один при такой реализации сдвига является буквально заколдованным. В классической математике (арифметике) если мы разделим целое число -1 (11111111 в двоичном виде) на целое число 2, то получим результат деления (-0.5) в виде целого числа 0 и остаток в виде целого числа -1. Однако, если мы заменим деление арифметическим сдвигом вправо, то мы никогда не получим 0. Мы будем снова получать -1 в результате (байт просто не изменится) и 1 в остатке (флаг переноса).

Поэтому арифметический сдвиг вправо действительно эквивалентен делению на 2 для положительных чисел, но не всегда эквивалентен для отрицательных. И нельзя бездумно заменять деление на степени двойки операциями сдвига.

Арифметический сдвиг влево

А вот тут все уже немного интереснее. На первый взгляд кажется, что операция должны выполняться так

На первый взгляд кажется, что арифметический сдвиг влево должен выполняться так. Иллюстрация моя.
На первый взгляд кажется, что арифметический сдвиг влево должен выполняться так. Иллюстрация моя.
На первый взгляд кажется, что арифметический сдвиг влево должен выполняться так. Иллюстрация моя.

Ведь все логично, знак числа измениться не должен?Да, это так, в теории. В первую очередь возникает вопрос, какой бит выдвигать в CF? Если это будет бит 7 (знаковый), то CF просто никогда не будет изменяться в результате операции. А если бит 6, как первый собственно значащий бит числа, то получается не очень логично. Кроме того, мы получим еще одно заколдованное число -128 (для байта), которое никогда не будет изменяться, сколько бы его не сдвигали арифметически влево.

Поэтому в большинстве случаев разработчики процессоров или вообще исключают операцию арифметического сдвига влево из системы команд, или делают ее полностью тождественной логическому сдвигу влево. И это мы в дальнейшем увидим. Кстати, это касается не только процессоров микроконтроллеров, но и Intel 80x86, где команды SAL и SHL работают абсолютно одинаково.

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

Иллюстрацию выполнения арифметического сдвига влево приводить не буду. Она в точности такая же, как для логического сдвига влево, который мы уже рассматривали.

Вращение (циклический сдвиг)

А что будет, если мы закольцуем биты при выполнении сдвига? Мы получим циклический сдвиг, или вращение. Причем тут возможны два варианта. Сначала рассмотрим более простой случай

Вращения (циклические сдвиги). Иллюстрация моя.
Вращения (циклические сдвиги). Иллюстрация моя.
Вращения (циклические сдвиги). Иллюстрация моя.

Как видно, от логических сдвигов циклические сдвиги отличаются только тем, что выдвигаемый бит является и источником заносимой в освобождающийся бит информации.

Циклические сдвиги имеют одну полезную особенность, по сравнению с логическими. Через 8 вращений (для байта) он принимает то же самое значение, которое было до выполнения этих операций. То есть, если количество вращений равно разрядности процессора. Это позволяет анализировать все биты переменной последовательно не изменяя, в конечном итоге, саму переменную. При этом не требуется никаких усилий по ее сохранению. А вот логические сдвиги в итоге обнуляют переменную.

Обратите внимание, что циклические сдвиги применимы только для беззнаковых чисел.

Вращение (циклический сдвиг) через перенос

В рассмотренном ранее случае у нас флаг переноса принимал значение выдвигаемого бита, как и при логических сдвигах. А что, если использовать его кольце передачи значений битов? Такое не только возможно, но и применяется чаще, чем просто вращение. Это вращение (циклический сдвиг) через перенос.

Вращения (циклические сдвиги) через перенос. Иллюстрация моя.
Вращения (циклические сдвиги) через перенос. Иллюстрация моя.
Вращения (циклические сдвиги) через перенос. Иллюстрация моя.

Этот вариант операций циклического сдвига является наиболее общим случаем сдвигов. За исключением арифметических сдвигов. Поэтому не редки случаи, когда набор команд процессора содержит только эти команды. С их помощью легко реализовать логические сдвиги, достаточно просто сбрасывать CF между вращениями. Более того, возможно выполнять "инверсные" логические сдвиги, когда в переменную вдвигается не 0, а 1.

Обратите внимание, что циклические сдвиги через перенос применимы только для беззнаковых чисел.

Перестановка бит

Довольно громкий и многообещающий заголовок, но сама перестановка весьма ограничена по функционалу... Собственно говоря, речь идет об одной единственной возможности - перестановке (обмену) тетрад в байте.

Перестановка тетрад в байте. Иллюстрация моя.
Перестановка тетрад в байте. Иллюстрация моя.
Перестановка тетрад в байте. Иллюстрация моя.

Зачем такое может потребоваться? Например, при обработке данных в BCD формате. Или при умножении на число больше 16, когда можно несколько сдвига заменить на обменять тетрады и сбросить левые биты переменной.

Команды сдвигов в микроконтроллерах

Вот теперь мы готовы познакомиться с командами сдвигов в рассматриваемых в цикле статей 8-битных микроконтроллерах.

ASRF - арифметический сдвиг вправо в старших моделях PIC. Отсутствует в PIC18. Обратите внимание, что парной команды ASLF нет, о чем я и предупреждал при рассмотрении арифметических сдвигов.

LSLF - логический сдвиг влево с старших моделях PIC. Отсутствует в PIC18.

LSRF - логический сдвиг вправо. За исключением направления сдвига аналогична LSLF.

ASR - арифметический сдвиг вправо в AVR. Обратите внимание, что и в AVR отсутствует парная команда ASL.

LSL - логический сдвиг влево в AVR.

LSR - логический сдвиг вправо в AVR.

SRA - арифметический сдвиг вправо в STM8. Обратите внимание, что парная команда SLA в данном микроконтроллере имеется, но только в виде мнемоники, так как код команды аналогичен команде SLL выполняющей логический сдвиг.

SRAW - арифметический сдвиг вправо индексных регистров (16 бит). В остальном аналогична SRA. Обратите внимание, что парная команда SLAW существует тоже только в виде мнемоники, так как ее код идентичен команде SLLW.

SLL - логический сдвиг влево в STM8.

SRL - логический сдвиг вправо в STM8.

SLLW - логический сдвиг влево индексных регистров (16 бит) в STM8.

SLRW - логический сдвиг вправо индексных регистров (16 бит) в STM8.

Команды вращений (циклических сдвигов) в микроконтроллерах

В большинстве случаев речь идет о вращениях через перенос.

RLF - циклический сдвиг влево в PIC.

RRF - циклический сдвиг вправо в PIC.

RLCF - циклический сдвиг через перенос в PIC18.

RRCF - циклический сдвиг вправо через перенос в PIC18.

RLNCF - циклический сдвиг влево БЕЗ участия переноса в PIC18. Да, именно так, флаг переноса вообще никак не участвует в выполнении этой команды. Он не изменяется.

RRNCF - циклический сдвиг вправо БЕЗ участия переноса в PIC18. Да, именно так, флаг переноса вообще никак не участвует в выполнении этой команды. Он не изменяется.

ROL - циклический сдвиг влево через перенос в AVR.

ROR - циклический сдвиг вправо через перенос в AVR.

RLC - циклический сдвиг влево через перенос в STM8.

RRC - циклический сдвиг вправо через перенос в STM8.

RLCW - циклический сдвиг влево индексных регистров (16 бит) через перенос в STM8.

RRCW - циклический сдвиг вправо индексных регистров (16 бит) через перенос в STM8.

RLWA - циклический сдвиг влево индексных регистров (16 бит) через аккумулятор в STM8.

RRWA - циклический сдвиг вправо индексных регистров (16 бит) через аккумулятор в STM8.

Специфические сдвиги STM8

Команды RLWA и RRWA выполняют ранее не рассматриваемый нами вариант сдвигов. Я покажу как они работают на примере команды RLWA для регистра Y

Работа команды RLWA Y,A в STM8. Иллюстрация моя.
Работа команды RLWA Y,A в STM8. Иллюстрация моя.
Работа команды RLWA Y,A в STM8. Иллюстрация моя.

Собственно говоря, это трудно назвать обычным сдвигом, поскольку здесь сдвигаются не биты, а целые байты. Скорее это операция обмена содержимого байт. Содержимое YH копируется в аккумулятор, который копируется в YL, который копируется в YH.

Команды перестановки бит в микроконтроллерах

SWAPF - перестановка тетрад в байте в PIC.

SWAP - перестановка тетрад в байте в AVR.

SWAP - перестановка тетрад в байте в STM8.

SWAPW - перестановка БАЙТ в индексном регистре в STM8. Другими словами, например для регистра Y, поменяется местами содержимое YH и YL.

Заключение

На этом мы заканчиваем знакомство с командами сдвигов. Я рассмотрел их достаточно подробно. Нельзя сказать, что они очень сложны, но есть много нюансов, которые далеко не очевидны новичкам. Надеюсь, теперь им стало более понятно.

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

До новых встреч!