Ассемблерные вставки в STM8 для Cosmic
При программировании микроконтроллеров иногда сталкиваются с необходимостью реализации специальных задач, которые реализовать в Си невозможно. Я столкнулся с реализаций критической секции, где необходимо сохранить регистр состояний (Condition Code register) для фиксации состояние глобального прерывания. Реализовать данную задачу можно только с помощью ассемблерной вставки. Далее будет показана реализация вставки для компилятора Cosmic.
Пример 1.
Реализовать критическую секцию, которая хранит состояние глобального глобального прерывания в регистре состояний.
uint8_t CondFlag; // Переменная для хранения регистра состояний void asm_insert(void) { enableInterrupts(); // разрешаем глобальное прерывание // начало критической секции _asm("push CC"); // помещаем регистр состояний в стек disableInterrupts(); // гарантированно запрещаем глобальное прерывание _asm("pop _cc_reg"); // извлекаем регистр состояний в созданную переменную // конец критической секции GPIO_Init(GPIOE, GPIO_PIN_5, GPIO_MODE_OUT_OD_LOW_FAST); // тестовый код // еще какой-то код который должен гарантированно выполняться без прерываний // возвращаем исходное состояние регистра состояний, а за одно и прерывания #pragma asm // начало ассемблерной вставки push _CondFlag // помещаем регистра состояния в стек pop CC // загружаем исходное состояние из стека в регистр состояний #pragma endasm // конец ассемблерной вставки // конец критической секции }
Ассемблерные команды вводятся внутри конструкции _asm(" ") или находятся между служебными конструкциями #pragma asm и #pragma endasm, особенно хочется обратить внимание как происходит вставка 8 битных переменных. Для этого необходим в ассемблерной коде перед нашей переменной нужно поставить нижнее подчеркивание "_", т.е. если у нас переменная в Си называется CondFlag, то для использования ее в ассемблере мы пишем _CondFlag, и компилятор вставляет адрес нашей переменной. Прямого доступа к регистру состояний у нас нет, поэтому приходится его читать и писать через стэк. Извлекаем значение регистра в только после того как гарантированно отключили прерывания, иначе возможно возникновение прерывания в котором тоже используется критическая секция и произойдет перезапись нашей переменной и отловить данную проблему будет очень сложно.
Пример 2.
Приведу еще один пример по работе с 16-битной переменной. В качестве задачи представим, что необходимо реализовать круговой сдвиг битов.
int16_t sdvig=0xa000; void rotate_left (void) { uint8_t i; for(i=0;i<4;i++) // Повторяем круговой сдвиг влево 4 раза { #pragma asm // начало ассемблерной вставки LDW X,_sdvig // загружаем в регистр Х значение 16-битной переменной по адресу sdvig LDW Y,#$7FFF // загружаем число для проверки первого бита на 1 или 0 CPW Y,_sdvig // если число больше, чем $7FFF, то бит переноса загружается в бит С регистра СС RLCW X // производим круговой сдвиг с учетом бита С. LDW _sdvig,X // Загружаем по адресу _sdvig значение из регистра Х #pragma endasm // конец ассемблерной вставки } }
Для понимания работы советую скопировать код и посмотреть как он будет работать. Бит переполнения (Carry) (С) в регистре состояний (Condition Code) я буду называть как в документации (СС.С).
В строке 8 мы копируем значение sdvig в 16-битный регистр, далее в регистр мы загружаем константу $7FFF.
В строке 10 производим вычитание из загруженной константы числа, которое мы сдвигаем. Что происходит Если у нас, например, число $8200, то при выполнении команды вычитания из $7FFF числа $8200 произойдет переполнение и в бит СС.С будет загружена 1, если число меньше или равно $7FFF, то бит СС.С будет равено 0.
В строке 11 при вызове команды RLCW произойдет циклический сдвиг регистра Х, при этом самый старший регистр переместиться в бит СС.С, а текущий бит СС.С в младщий бит регистра Х.
В строке 12 мы загружаем значение из регистра Х по адресу sdvig.
А далее мы все повторяем.
Использование ассемблерных вставок и сопряжение с программой на Си очень простое.
Арифметические и логические сдвиги в Си
Раз речь зашла про сдвиги хотел бы еще обратить внимание на стандартную операцию сдвига в СИ. Это операция сдвига влево << и сдвига вправо >>. На это обращают мало внимания, но сдвиги делятся на арифметические и логические .В чем разница арифметического и логического сдвига? Арифметический сдвиг - это сдвиг знакового числа, а логический - беззнакового числа. Кроме того, полезно помнить что сдвиг - это быстрое деление или умножение на 2.
Создадим две переменных знаковую и беззнаковую и присвоим им одинаковые биты числа, но компилятор воспринимать их будет по-разному. Далее произведем операции сдвигов над знаковыми и беззнаковыми числами. Для удобства анализа результатов работы я сопоставил бинарное представление чисел
uint8_t ua=0xCD; // беззнаковая переменная исходное 205 или 0b11001101 int8_t sa=0xCD; // знаковая переменная исходное -51 или 0b11001101 uint8_t ua_right; // беззнаковая переменная сдвиг вправо 51 или 0b00110011 int8_t sa_right; // знаковая переменная сдвиг вправо -13 или 0b11110011 uint16_t ua_left; // беззнаковая переменная сдвиг влево 820 или 0b0000001100110100 int16_t sa_left; // знаковая переменная сдвиг влево -204 или 0b1111111100110100 ua_right=ua>>2; // результат сдвиг вправо 51 или 0b00110011 sa_right=sa>>2; // результат сдвиг вправо -13 или 0b11110011 ua_left =ua<<2; // результат сдвиг влево 820 или 0b0000001100110100 sa_left =sa<<2; // результат сдвиг влево -204 или 0b1111111100110100
Как видно, результат операции для сдвига вправо в зависимости от того знаковое или беззнаковое число отличается, но представляет из себя число деленное на 4. Для сдвига влево необходимо расширить диапазон из 8-битного числа перейти к 16-битному, тогда видно что при сдвиге влево происходит умножение на 4, в зависимости от того знаковое или беззнаковое число бинарные результаты отличаются.
Иногда при работе с АЦП в дифференциальном режиме очень удобно выполнять арифметический сдвиг, т.к. встроенный АЦП чаще всего 10-битный и представляет отрицательные число в дополнительном коде для 10-бит, но контроллер работает с дополнительным кодом в 16-битном формате, поэтому далее задача сводится к переводу дополнительный код из 10-битного в 16-битный формат.
Пример. АЦП в дифференциально режиме выдал следующие 10-бит 1.001101101 в дополнительном коде на 10-бит это будет соответствовать -403 для дополнительного кода.
Настройка АЦП с выравниванием влево даст нам 16-битное число a=1.001101101000000, а далее если это число сохраняем в знаковом формате и производим сдвиг вправо на 6 a=a>>6 т.е число станет 1111111.001101101 или -403 в дополнительном коде для 16-битного формата.
Комментарии (0) | Я собрал (0) | Подписаться
Для добавления Вашей сборки необходима регистрация